即使使用 IN 语句,子查询也返回超过 1 行 - Oracle SQL

Posted

技术标签:

【中文标题】即使使用 IN 语句,子查询也返回超过 1 行 - Oracle SQL【英文标题】:Subquery Returns more than 1 row even using IN statement - Oracle SQL 【发布时间】:2019-06-20 17:43:08 【问题描述】:

我正在处理两个表,第一个,purchases,在下面(注意,这是purchases 表的剪辑:

|    ID   |    Date   | Value | Type | Satisfied By |
|:-------:|:---------:|:-----:|:----:|:------------:|
| SALE100 |  1/1/2019 |   -5  |  OUT |              |
| SALE201 |  1/9/2019 |  -10  |  OUT |              |
| SALE203 | 2/22/2019 |   -1  |  OUT |              |
| SALE205 | 3/14/2019 |   -1  |  OUT |              |

我正在尝试确定另一个表 makes 中的哪些 MAKE 项目满足这些销售额。

|    ID   |    Date    | Value | Needed For |
|:-------:|:----------:|:-----:|:----------:|
| MAKE300 | 12/24/2018 |   5   |   SALE100  |
| MAKE301 |  1/3/2019  |   3   |   SALE201  |
| MAKE399 |  1/5/2019  |   5   |   SALE201  |
| MAKE401 |  1/7/2019  |   3   |   SALE201  |
| MAKE401 |  1/7/2019  |   3   |   SALE203  |
| MAKE912 |  2/1/2019  |   1   |   SALE205  |

我正在尝试编写一个查询,使我能够确定makes 表中的哪个IDIDs 满足我的销售额。

如果它们是LISTAGG,我的最终结果会是这样的:

|    ID   |    Date   | Value | Type |        Satisfied By       |
|:-------:|:---------:|:-----:|:----:|:-------------------------:|
| SALE100 |  1/1/2019 |   -5  |  OUT |          MAKE300          |
| SALE201 |  1/9/2019 |  -10  |  OUT | MAKE301, MAKE399, MAKE401 |
| SALE203 | 2/22/2019 |   -1  |  OUT |          MAKE401          |
| SALE205 | 3/14/2019 |   -1  |  OUT |          MAKE912          |

但是,在编写以下代码行时:

(SELECT LISTAGG(makes.id, ', ') WITHIN GROUP (ORDER BY NULL) FROM makes WHERE purchased.id = needed_for.id) ELSE NULL END AS Satisfied_By

导致错误说明:

ORA-01489: 字符串连接的结果太长 01489. 00000 - “字符串连接的结果太长”

我也尝试了以下查询以获得这样的结果(这是理想的):

|    ID   |    Date   | Value | Type | Satisfied By |
|:-------:|:---------:|:-----:|:----:|:------------:|
| SALE100 |  1/1/2019 |   -5  |  OUT |    MAKE300   |
| SALE201 |  1/9/2019 |  -10  |  OUT |    MAKE301   |
| SALE201 |  1/9/2019 |  -10  |  OUT |    MAKE399   |
| SALE201 |  1/9/2019 |  -10  |  OUT |    MAKE401   |
| SALE203 | 2/22/2019 |   -1  |  OUT |    MAKE401   |
| SALE205 | 3/14/2019 |   -1  |  OUT |    MAKE912   |

CASE WHEN Type = 'OUT' THEN
(SELECT 
makes.id

FROM
makes
WHERE
makes.id IN (

  SELECT
    makes.id

  FROM
    makes

  WHERE
    sales.id = purchases.id

)) ELSE NULL END AS Satisfied_By

产生的结果

ORA-01427: 单行子查询返回多于一行 01427. 00000 - “单行子查询返回多于一行”

我在 Stack Overflow 上找到了许多有关此错误的示例,这是我从该来源采用 IN 方法的地方,但我仍然收到错误消息。任何帮助表示赞赏。

【问题讨论】:

【参考方案1】:

您的“理想”结果是一个简单的连接:

select p.id, p.dt, p.value, p.type, m.id as satisfied_by
from purchases p
join makes m on m.needed_for = p.id;

您可能希望将其设为left join,以防不匹配,如果您的数据中可能这样做的话。

使用您的数据进行快速演示:

-- CTEs for sample data
with purchases (id, dt, value, type, satisfied_by) as (
            select 'SALE100', date '2019-01-01', -5, 'OUT', null from dual
  union all select 'SALE201', date '2019-01-09', -10, 'OUT', null from dual
  union all select 'SALE203', date '2019-02-22', -1, 'OUT', null from dual
  union all select 'SALE205', date '2019-03-14', -1, 'OUT', null from dual
),
makes (id, dt, value, needed_for) as (
            select 'MAKE300', date '2018-12-24', 5, 'SALE100' from dual
  union all select 'MAKE301', date '2019-01-03', 3, 'SALE201' from dual
  union all select 'MAKE399', date '2019-01-05', 5, 'SALE201' from dual
  union all select 'MAKE401', date '2019-01-07', 3, 'SALE201' from dual
  union all select 'MAKE401', date '2019-01-07', 3, 'SALE203' from dual
  union all select 'MAKE912', date '2019-02-01', 1, 'SALE205' from dual
)
-- actual query
select p.id, p.dt, p.value, p.type, m.id as satisfied_by
from purchases p
left join makes m on m.needed_for = p.id;

ID      DT              VALUE TYP SATISFIED_BY                  
------- ---------- ---------- --- ------------------------------
SALE100 2019-01-01         -5 OUT MAKE300                       
SALE201 2019-01-09        -10 OUT MAKE301                       
SALE201 2019-01-09        -10 OUT MAKE399                       
SALE201 2019-01-09        -10 OUT MAKE401                       
SALE203 2019-02-22         -1 OUT MAKE401                       
SALE205 2019-03-14         -1 OUT MAKE912                       

listagg 版本也相当简单:

select p.id, p.dt, p.value, p.type,
  listagg(m.id, ', ') within group (order by m.id) as satisfied_by
from purchases p
left join makes m on m.needed_for = p.id
group by p.id, p.dt, p.value, p.type;

ID      DT              VALUE TYP SATISFIED_BY                  
------- ---------- ---------- --- ------------------------------
SALE100 2019-01-01         -5 OUT MAKE300                       
SALE201 2019-01-09        -10 OUT MAKE301, MAKE399, MAKE401     
SALE203 2019-02-22         -1 OUT MAKE401                       
SALE205 2019-03-14         -1 OUT MAKE912                       

从您的代码片段中并不清楚您做错了什么,但看起来您正在正确关联您的子查询;但是您实际上并不需要它们...如果您已经正确关联了listagg 版本的子查询,那么您的真实数据中实际上可能有太多匹配项;要么就是这样,要么子查询返回的数据比它应该返回的数据多,并且聚合所有这些数据打破了大小限制。


子查询的“缺失”部分是我使用了CASE WHEN TYPE = 'OUT' THEN,所以没什么特别的,但这会限制我拥有的记录数量

您可以将其包含在连接条件中:

from purchases p
left join makes m on (p.type = 'OUT' and m.needed_for = p.id)

您仍然可以对listagg 方法使用子查询:

select p.id, p.dt, p.value, p.type,
  (
    select listagg(m.id, ', ') within group (order by m.id) 
    from makes m
    where m.needed_for = p.id
    -- and p.type = 'OUT'
  ) as satisfied_by
from purchases p;

这实际上可能是您正在做的事情 - 尚不清楚该条件是否等同于您显示的 purchased.id = needed_for.id。如果您仍然从中获得 ORA-01489,那么您也将从非子查询版本中获得,并且您的匹配项太多,无法将聚合列表放入 4000 个字节中。如果它们都有效,那么我不确定拥有子查询的优势是什么——Oracle 优化器充其量可能会使它们等效,但性能似乎更有可能更糟。不过,您需要使用真实环境和数据进行测试才能确定。

无论有没有in(),非listagg 子查询都不起作用(它只是添加了另一个级别的子查询,没有实际效果):

select p.id, p.dt, p.value, p.type,
  (
    select m.id
    from makes m
    where m.needed_for = p.id
    -- and p.type = 'OUT'
  ) as satisfied_by
from purchases p;

ORA-01427: single-row subquery returns more than one row

...因为您知道并期望您将从该子查询中获得多行,至少对于某些购买而言。对于您的示例数据,如果您排除 SALE201,这确实有效,但这没有帮助。您试图将多个值塞进一个单一的标量结果中,这是行不通的,这也是您首先需要查看 listagg 的原因。

除了@Tejash 演示的 xmlagg 变体,您还可以将组合值作为集合获取,例如:

select p.id, p.dt, p.value, p.type,
  cast(multiset(
    select m.id
    from makes m
    where m.needed_for = p.id
    -- and p.type = 'OUT'
  ) as sys.odcivarchar2list) as satisfied_by
from purchases p;

ID      DT              VALUE TYP SATISFIED_BY                                      
------- ---------- ---------- --- --------------------------------------------------
SALE100 2019-01-01         -5 OUT ODCIVARCHAR2LIST('MAKE300')                       
SALE201 2019-01-09        -10 OUT ODCIVARCHAR2LIST('MAKE301', 'MAKE399', 'MAKE401') 
SALE203 2019-02-22         -1 OUT ODCIVARCHAR2LIST('MAKE401')                       
SALE205 2019-03-14         -1 OUT ODCIVARCHAR2LIST('MAKE912')                       

... 或作为您在架构中定义的表类型集合。不过,这可能更难处理,甚至离你的“理想”输出更远。这在一定程度上取决于将使用您的结果集的内容以及方式。

【讨论】:

我试图避免加入,因为它需要很长的时间来运行(makes 有大约 7500 万条记录)。但是如果我的IN 语句中没有明显的错误,那么我不知道为什么它不断地给我多行错误。 正如我所提到的,您的子查询中没有相关性。而且您知道 SALE201 的 in 子查询将返回三行...但是为什么您认为子查询会比联接更快? 如果我尝试从运行时复杂性的角度来看这个问题,难道我不能保证O(m*n) 可以加入吗?子查询的“缺失”部分是我使用了CASE WHEN TYPE = 'OUT' THEN,所以没什么特别的,但这会限制我拥有的记录数量,所以我会得到类似O((n*x)*m) 的东西,其中x 是` 很难从您的查询片段中分辨出来,这些片段似乎引用了其他表,或者具有不同名称的相同表。如果您在问题中添加minimal reproducible example,它可能会更清楚。也许您的联接条件是错误的 - 例如,如果有必要,您可以将 OUT 要求作为联接子句的一部分。 太小了,无法重现。您没有给出可以运行以重现您的问题的完整查询,并且您确实拥有的片段具有冲突和令人困惑的名称。当然,您可以替换专有数据,并且鼓励简化事情,但您需要始终如一地这样做。多对多关系是in 不起作用的原因,但我已经说过了。目前尚不清楚listagg 是否有问题或您的真实数据实际上太大。不管怎样,我已经用你提供的信息做了我所能做的,希望你能从其他贡献者那里得到更多的快乐。【参考方案2】:

您的第一个查询返回以下错误:

ORA-01489: 字符串连接的结果太长 01489. 00000 - “字符串连接的结果太长”

"Satisfied_By" 列中的串联长度超过 4000 个字符。 在连接VARCHAR 列时,您需要使用XMLAGG 以确保安全。

您可以尝试以下查询:

-- DATA PREPARATION
with purchases (id, dt, value, type, satisfied_by) as (
  select 'SALE100', date '2019-01-01', -5, 'OUT', null from dual
  union all select 'SALE201', date '2019-01-09', -10, 'OUT', null from dual
  union all select 'SALE203', date '2019-02-22', -1, 'OUT', null from dual
  union all select 'SALE205', date '2019-03-14', -1, 'OUT', null from dual
),
makes (id, dt, value, needed_for) as (
  select 'MAKE300', date '2018-12-24', 5, 'SALE100' from dual
  union all select 'MAKE301', date '2019-01-03', 3, 'SALE201' from dual
  union all select 'MAKE399', date '2019-01-05', 5, 'SALE201' from dual
  union all select 'MAKE401', date '2019-01-07', 3, 'SALE201' from dual
  union all select 'MAKE401', date '2019-01-07', 3, 'SALE203' from dual
  union all select 'MAKE912', date '2019-02-01', 1, 'SALE205' from dual
)
-- actual query
SELECT
    P.ID,
    P.DT,
    P.VALUE,
    P.TYPE,
    RTRIM(XMLAGG(XMLELEMENT(E, M.ID, ',').EXTRACT('//text()')
        ORDER BY
            M.ID
    ).GETCLOBVAL(), ',') AS SATISFIED_BY
FROM
    PURCHASES P
    LEFT JOIN MAKES M ON P.ID = M.NEEDED_FOR
GROUP BY
    P.ID,
    P.DT,
    P.VALUE,
    P.TYPE;

DB Fiddle demo

干杯!!

【讨论】:

以上是关于即使使用 IN 语句,子查询也返回超过 1 行 - Oracle SQL的主要内容,如果未能解决你的问题,请参考以下文章

带有 GROUP_CONCAT 和 CONCAT 的子查询返回 STILL 多于 1 行,错误 1242

需要 SQL Query 帮助并显示错误消息子查询返回超过 1 行

为啥 MySql 会出现“子查询返回超过 1 行”错误?

SQL Server 触发器:子查询返回超过 1 个值

MYSQL-UPDATE ERROR子查询返回超过1行[重复]

子查询返回超过 1 个值 - 我的触发器无法处理多个行更新