Oracle SQL跨组内的多个列进行计数

时间:2017-04-20 15:05:49

标签: sql oracle

拥有跨列有多个重复条目的数据集。

ID   DATE       CLIENT   TEST0    TEST1    TEST2
================================================
1    04/12/17   123      CBC      LIPID    (null)
2    04/12/17   345      LIPID    (null)   (null)
3    04/13/17   123      BMP      CBC      (null)
4    04/13/17   345      TSH      LIPID    (null)

理想情况下,我希望输出按客户端分组,然后根据TEST0,TEST1,TEST2列中的数据进行测试计数。
应该回复:

CLIENT   CBC   LIPID    BMP    TSH
====================================
123      2     1        1      0
345      0     2        0      1 

使用

select *
from
(
 select test0 as testid from orders
 union all
 select test1 as testid from orders
) t1
pivot
(
count(testid)
for testid in ('CBCWD','LIPID','BMP','TSH')
)

让我粗略地看到了那个目标,但后来我很难传递其他控件,例如有限的日期范围,或者链接到客户端表,所以我可以将客户端代码翻译成实际名称。

2 个答案:

答案 0 :(得分:0)

您可以将三个“测试”列拆分为一个,这基本上就是您的工会正在做的事情;然后将其转回到你想要的列(假设你是11g或更高);将您的样本数据放在CTE中:

with orders (ID, DT, CLIENT, TEST0, TEST1, TEST2) as (
  select 1, date '2017-04-12', 123, 'CBC', 'LIPID', null from dual
  union all select 2, date '2017-04-12', 345, 'LIPID', null, null from dual
  union all select 3, date '2017-04-13', 123, 'BMP', 'CBC', null from dual
  union all select 4, date '2017-04-13', 345, 'TSH', 'LIPID', null from dual
)
select * from (
  select client, test
  from orders
  unpivot (test for num in (test0 as '0', test1 as '1', test2 as '2'))
)
pivot (count(test) as cnt for (test)
  in ('CBC' as cbc, 'LIPID' as lipid, 'BMP' as bmp, 'TSH' as tsh));

    CLIENT    CBC_CNT  LIPID_CNT    BMP_CNT    TSH_CNT
---------- ---------- ---------- ---------- ----------
       123          2          1          1          0
       345          0          2          0          1

按日期过滤,例如,将from orders更改为另一个子树,该子查询将从表中选取的行进行重新划分。要将结果连接到另一个表,要么在该内部查询中加入,要么将整个内容设置为另一个级别,如:

select c.client_name, o.cbc_cnt, o.lpid_cnt, o.dmp_cnt, o.tsh_cnt
from (
    select * from (
      select client, test
      from (
        select *
        from orders
        where dt = date '2017-04-12'
      )
      unpivot (test for num in (test0 as '0', test1 as '1', test2 as '2'))
    )
    pivot (count(test) as cnt for (test)
      in ('CBC' as cbc, 'LIPID' as lipid, 'BMP' as bmp, 'TSH' as tsh))
) o
join your_client_table c on c.client = o.client;

如果单个值一次只能出现在列上 - 所以你不能将test0和test1都设置为CBC的行 - 那么你可以更简单地做:

select client,
  count(case when test0 = 'CBC' or test1 = 'CBC' or test2 = 'CBC' then client end) as cbc,
  count(case when test0 = 'LIPID' or test1 = 'LIPID' or test2 = 'LIPID' then client end) as lipid,
  count(case when test0 = 'BMP' or test1 = 'BMP' or test2 = 'BMP' then client end) as bmp,
  count(case when test0 = 'TSH' or test1 = 'TSH' or test2 = 'TSH' then client end) as tsh
from orders
group by client;

    CLIENT        CBC      LIPID        BMP        TSH
---------- ---------- ---------- ---------- ----------
       123          2          1          1          0
       345          0          2          0          1

但目前尚不清楚是否属实。

答案 1 :(得分:0)

对于寻找特定日期范围的查询,请尝试阅读:Oracle date "Between" Query

看起来这不是一个非常明确的表结构。您应该有一个测试列,而不是多个测试列。但是,就你所拥有的而言,你走在了正确的轨道上。

WITH ModifedTable AS (
  SELECT ID, DATE, CLIENT, TEST0 AS TEST_TYPE FROM Table WHERE TEST0 IS NOT NULL
  UNION ALL
  SELECT ID, DATE, CLIENT, TEST1 FROM Table WHERE TEST1 IS NOT NULL
  UNION ALL
  SELECT ID, DATE, CLIENT, TEST2 FROM Table WHERE TEST2 IS NOT NULL)
SELECT 
  CLIENT, 
  SUM(DECODE(TEST_TYPE, "CBC", 1, NULL)) AS CBC_COUNT,
  SUM(DECODE(TEST_TYPE, "LIPID", 1, NULL)) AS LIPID_COUNT,
  SUM(DECODE(TEST_TYPE, "BMP", 1, NULL)) AS BMP_COUNT,
  SUM(DECODE(TEST_TYPE, "TSH", 1, NULL)) AS TSH_COUNT
FROM ModifiedTable
GROUP BY CLIENT
ORDER BY CLIENT

如果您想要特定的日期范围,请在ModifiedTable子查询中对其进行过滤,或使用第二个子查询进行过滤:

WITH ModifiedTable AS (SELECT .... ),
ModifiedTableDates AS (SELECT * FROM ModifiedTable WHERE DATE BETWEEN date1 AND date2)
SELECT ... FROM ModifiedTableDates