即使使用 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
表中的哪个ID
或IDs
满足我的销售额。
如果它们是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 行