【问题标题】:Subquery in select - non-grouped values in 'IN' clause选择中的子查询 - 'IN' 子句中的非分组值
【发布时间】:2019-01-11 06:28:45
【问题描述】:

假设以下简化架构:

create table main_table
(
    a number,
    b number,
    c number
);

create table other_table
(
    c number,
    d number
)

现在,我想要实现的目标: 我有一个关于 main_table 的查询,按 a、b 分组。 我需要在 select 子句中的子查询中使用“c 的所有值”来从其他表中获取一些数据。 很遗憾,我无法加入另一张桌子。

伪代码是:

select mt.a,
       mt.b,
       (select /* some aggregated value */
          from other_table ot
         where ot.c in (all_values_of_c_within_group)
       )
  from main table mt
 group by mt.a, mt.b

我知道有两种方法可以解决这个问题:

  1. 在 other_table 上使用连接,然后从那里聚合值 - 不幸的是,我不能这样做,因为真正的查询是如何构造的(3 个嵌套视图、800 个 sloc、30 个分组值 - 长篇大论)
  2. 使用 listagg,然后将其与 'instr' 一起'delistagg'。伪代码:

/*(...)*/
(select /* some_aggregated_value */
   from other_table ot
  where instr(',' || listagg(
                     to_char(mt.c), ',') within group (order by 1),
              ',' || ot.c) > 0
)
/*(...)*/

但这只是糟糕的代码,它会自动阻止在 other_table.c 上使用任何可能存在的索引。

是否有正确获取“组内列的所有值”的语法?

【问题讨论】:

  • 一些示例数据和预期结果会很有用。

标签: sql oracle oracle11g


【解决方案1】:

如果没有一些数据和预期结果,尚不清楚您要达到什么目的,但我认为您可以使用集合来做您想做的事情:

SQL Fiddle

Oracle 11g R2 架构设置

create table main_table( a, b, c ) AS
  SELECT 1, 1, 1 FROM DUAL UNION ALL
  SELECT 1, 1, 2 FROM DUAL UNION ALL
  SELECT 1, 1, 3 FROM DUAL
/

create table other_table( c, d ) AS
  SELECT 1, 4 FROM DUAL UNION ALL
  SELECT 3, 6 FROM DUAL UNION ALL
  SELECT 5, 8 FROM DUAL
/

CREATE TYPE number_table AS TABLE OF NUMBER
/

查询 1

SELECT a,
       b,
       ( SELECT LISTAGG( d, ',' ) WITHIN GROUP ( ORDER BY d )
         FROM   other_table
         WHERE  c MEMBER OF m.cs
       ) ds
FROM   (
  SELECT a,
         b,
         CAST( COLLECT( c ) AS number_table ) AS cs
  FROM   main_table
  GROUP BY a, b
) m

Results

| A | B |  DS |
|---|---|-----|
| 1 | 1 | 4,6 |

查询 2:但使用 LEFT OUTER JOIN 似乎更简单:

SELECT a,
       b,
       LISTAGG( d, ',' ) WITHIN GROUP ( ORDER BY d ) ds
FROM   main_table m
       LEFT OUTER JOIN other_table o
       ON ( m.c = o.c )
GROUP BY a, b

Results

| A | B |  DS |
|---|---|-----|
| 1 | 1 | 4,6 |

【讨论】:

  • 感谢您的回答 - 这将非常有效(可能在性能方面并不完美,但绝对可以接受)。不幸的是,这强制将我的整个选择放入子查询中。如果有人提供更好的东西,我会等待接受答案。
【解决方案2】:

您可能只是能够聚合子查询,例如以 sum 作为聚合函数:

select mt.a,
       mt.b,
       sum(
         (select d
            from other_table ot
           where ot.c = mt.c)
       ) as sum_d
  from main_table mt
 group by mt.a, mt.b;

使用一些虚构的数据:

insert into main_table values (1, 2, 3);
insert into main_table values (1, 2, 4);
insert into main_table values (2, 3, 4);
insert into main_table values (2, 3, 5);
insert into main_table values (2, 3, 6);

insert into other_table values (3, 10);
insert into other_table values (4, 11);
insert into other_table values (5, 12);
insert into other_table values (6, 13);

该查询给出:

         A          B      SUM_D
---------- ---------- ----------
         2          3         36
         1          2         21

正如你所说,多了一行:

insert into main_table values (2, 3, 4);

该查询多次计算匹配的 cd 值,因此您得到的是 47 而不是 36:

         A          B      SUM_D
---------- ---------- ----------
         2          3         47
         1          2         21

您可以添加distinct:

select mt.a,
       mt.b,
       sum(distinct 
         (select d
            from other_table ot
           where ot.c = mt.c)
       ) as sum_d
  from main_table mt
 group by mt.a, mt.b;

         A          B      SUM_D
---------- ---------- ----------
         1          2         21
         2          3         36

这假定c,或至少c, d 的组合,在other_table 中是唯一的。

【讨论】:

  • 这几乎正是我想要的,但是当“C”在组中不是唯一的时,就会出现问题。对组成的数据,添加另一个插入到 main_table 值 (2, 3, 4);现在这个组的聚合列是 47 - 我希望它仍然是 36(这就是我在问题中提到“IN”子句的原因)。
  • @piezol - in() 的重复数据删除效果对您有用并不明显。您可以(我认为)使用distinct 获得相同的效果;我添加了一个示例。
  • 不同的例子意味着“ot.d 的不同值”,我需要的是“ot.D 的不同值的 mt.C 值”。无论如何,谢谢 - 我会选择 @MT0 解决方案。
【解决方案3】:

这应该可行,并且不应像 Alex 的回答那样对 other_table 施加唯一性要求。

select mt.a,
       mt.b,
       (select sum(d) /* some aggregated value */
          from other_table ot
         where ot.c in ( SELECT mt2.c 
                         FROM main_table mt2 
                         WHERE mt2.a = mt.a AND mt2.b = mt.b
                       ) 
       ) agg
  from main_table mt
 group by mt.a, mt.b;

必须为每个组再次访问main_table,但考虑到您已经在访问这些记录,我们应该讨论额外的逻辑 I/O 而不是额外的物理 I/O。

使用 Alex Poole 的测试数据(带有重复的 MAIN_TABLE 行),我在 12c 中得到了这个:

+---+---+-----+
| A | B | AGG |
+---+---+-----+
| 2 | 3 |  36 |
| 1 | 2 |  21 |
+---+---+-----+

【讨论】:

  • 是的,它会起作用——但这意味着我必须有效地重新运行整个查询才能达到我已经在“select”子句中应该拥有的组级别。在我的案例中,另外 ~~200 行代码复制粘贴到“选择”:)
猜你喜欢
  • 2014-08-30
  • 2012-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多