2

我有一个 Oracle 表,其中的数据如下所示:

ID   BATCH   STATUS
1    1       0
2    1       0
3    1       1
4    2       0

也就是说,ID是主键,每个“批次”会有多行,每一行在STATUS列中都会有一个状态码。还有很多其他的列,但这些是重要的。

我需要编写一个查询来总结每个批次的状态代码;STATUS 列中可以包含三个可能的值,0、1 和 2,我希望输出看起来像这样:

BATCH  STATUS0  STATUS1  STATUS2
1      2        1        0
2      1        0        0

这些数字将是计数;对于第 1 批,有

  • STATUS设置为 0 的2 条记录
  • STATUS设置为 1 的1 条记录,以及
  • 没有STATUS设置为 0 的记录。

对于第 2 批,有

  • 1 条记录,其中STATUS设置为 0,并且
  • 没有STATUS设置为 1 或 2 的记录。

有没有一种方法可以在一个查询中执行此操作,而无需为每个状态代码重写查询?即我可以很容易地写一个这样的查询,并运行它三遍:

SELECT batch, COUNT(status)
FROM table
WHERE status = 0
GROUP BY batch

我可以运行它,然后在 status = 1 的地方再次运行它,然后在 status = 2 的地方再次运行它,但我希望在一个查询中完成它。

如果它有所不同,除了STATUS列之外,我可能还想以同样的方式总结另一列——另一个原因是我不想在 SELECT 语句之后执行 SELECT 语句并合并所有结果。

4

4 回答 4

6
select batch 
,      count(case when status=1 then 1 end) status1
,      count(case when status=2 then 1 end) status2
,      count(case when status=3 then 1 end) status3
from   table
group by batch;

这通常称为“枢轴”查询,我在博客上写了一篇关于如何动态生成这些查询的文章。

使用 DECODE 的版本(特定于 Oracle,但不太冗长):

select batch 
,      count(decode(status,1,1)) status1
,      count(decode(status,2,1)) status2
,      count(decode(status,3,1)) status3
from   table
group by batch;
于 2009-06-22T19:45:04.927 回答
1
select batch,
sum(select case when status = 0 then 1 else 0 end) status0,
sum(select case when status = 1 then 1 else 0 end) status1,
sum(select case when status = 2 then 1 else 0 end) status2
from table
group by batch
于 2009-06-22T19:43:29.003 回答
1
select batch,
sum((decode(status,0,1,0)) status0,
sum((decode(status,1,1,0)) status1,
sum((decode(status,2,1,0)) status2,
from table
group by batch
于 2009-06-22T19:48:11.787 回答
1

OP 询问一种方法 (SUM) 是否比另一种方法 (COUNT) 有任何性能优势。在具有 26K 行的表上运行简单的测试表明 COUNT 方法明显更快。YMMV。

DECLARE
  CURSOR B IS
     select batch_id
       FROM batch
      WHERE ROWNUM < 2000;

  v_t1  NUMBER;
  v_t2  NUMBER;
  v_c1  NUMBER;
  v_c2  NUMBER;
  v_opn INTEGER;
  v_cls INTEGER;
  v_btc VARCHAR2(100);
BEGIN
-- Loop using SUM
  v_t1 := dbms_utility.get_time;
  v_c1 := dbms_utility.get_cpu_time;
  FOR R IN B LOOP
     FOR R2 IN (SELECT batch_type_code
                     , SUM(decode(batch_status_code, 'CLOSED', 1, 0)) closed
                     , SUM(decode(batch_status_code, 'OPEN', 1, 0)) OPEN
                     , SUM(decode(batch_status_code, 'REWORK', 1, 0)) rework
                  FROM batch
                 GROUP BY batch_type_code) LOOP 
        v_opn := R2.open;
        v_cls := R2.closed;
     END LOOP;
  END LOOP;
  v_t2 := dbms_utility.get_time;
  v_c2 := dbms_utility.get_cpu_time;
  dbms_output.put_line('For loop using SUM:');
  dbms_output.put_line('CPU seconds used: '||(v_c2 - v_c1)/100);
  dbms_output.put_line('Elapsed time: '||(v_t2 - v_t1)/100);

-- Loop using COUNT
  v_t1 := dbms_utility.get_time;
  v_c1 := dbms_utility.get_cpu_time;
  FOR R IN B LOOP
     FOR R2 IN (SELECT batch_type_code
                     , COUNT(CASE WHEN batch_status_code = 'CLOSED' THEN 1 END) closed
                     , COUNT(CASE WHEN batch_status_code = 'OPEN' THEN 1 END) OPEN
                     , COUNT(CASE WHEN batch_status_code = 'REWORK' THEN 1 END) rework
                  FROM batch
                 GROUP BY batch_type_code) LOOP 
        v_opn := R2.open;
        v_cls := R2.closed;
     END LOOP;
  END LOOP;
  v_t2 := dbms_utility.get_time;
  v_c2 := dbms_utility.get_cpu_time;
  dbms_output.put_line('For loop using COUNT:');
  dbms_output.put_line('CPU seconds used: '||(v_c2 - v_c1)/100);
  dbms_output.put_line('Elapsed time: '||(v_t2 - v_t1)/100);
END;
/

这产生了以下输出:

For loop using SUM:
CPU seconds used: 40
Elapsed time: 40.09
For loop using COUNT:
CPU seconds used: 33.26
Elapsed time: 33.34

我重复了几次测试以消除缓存的任何影响。我还交换了选择语句。结果总体上是相似的。

编辑:这是我用来回答类似问题的相同测试工具。

于 2009-06-22T23:18:49.253 回答