EXISTS 不适用于 WITH 子句中的子查询

Posted

技术标签:

【中文标题】EXISTS 不适用于 WITH 子句中的子查询【英文标题】:EXISTS not working with subquery in WITH clause 【发布时间】:2019-05-29 06:50:01 【问题描述】:

我遇到了 EXISTS 子句无法正常工作的查询。即使对于不存在匹配记录的项目,查询也会返回结果,似乎完全忽略了 EXISTS。它曾经工作正常,我认为从 Oracle 12.1 升级到 12.2 后问题就开始了。

以下是完整的查询(仅更改了表和列名以使其更具可读性,但我保留了所有逻辑以防与此相关):

WITH FirstDateFilter AS (
    SELECT ReferenceDate,
           Type,
           LAG(Type, 1, 0) OVER (ORDER BY ReferenceDate) AS PreviousType
    FROM ReferenceDateTable
    WHERE ItemId = :itemId
    AND   ReferenceDate <= :endDate
    AND   Type IN (:type1, :type2)
), SecondDateFilter AS (
    SELECT ReferenceDate
    FROM FirstDateFilter
    WHERE ReferenceDate >= :startDate
    AND   ReferenceDate >= ( SELECT StartDate FROM StartDateTable WHERE ItemId = :itemId )
    AND   Type = :type1
    AND   PreviousType = :type1
)
SELECT ReferenceDate, Value
FROM ResultTable
WHERE ItemId = :itemId
AND EXISTS ( SELECT * FROM SecondDateFilter WHERE SecondDateFilter.ReferenceDate = ResultTable.ReferenceDate )

在使用了一些测试数据之后,我认为(部分?)导致失败的原因是第二个 WITH 中的子查询 AND ReferenceDate &gt;= ( SELECT StartDate FROM StartDateTable WHERE ItemId = :itemId )

我发现以下任何编辑都会导致 EXISTS 再次按预期工作:

使用 SecondDateFilter 加入 ResultTable(在 ReferenceDate 上) 将( SELECT ReferenceDate FROM SecondDateFilter WHERE SecondDateFilter.ReferenceDate = ResultTable.ReferenceDate ) 放入SELECT ... FROM ResultTable 注释掉 StartDateTable 子查询(不再过滤该表,否则它会再次起作用) 将 StartDateTable 子查询移至第一个 WITH

最后一个解决方案实际上解决了我这个查询的问题(技术上不一样,但底层业务逻辑检查结果总是一样的),但我想知道 EXISTS 子句是否存在一般问题(可能仅在 Oracle 12.2 中?)我应该知道。我有更多使用它的查询。

下面是一个重复错误的测试脚本。下面的查询按预期返回 2 行,但删除注释行会得到 5 行。

CREATE TABLE ReferenceDateTable 
    (
     ItemId number,
     ReferenceDate date, 
     Type varchar2(1)
    ); 
INSERT INTO ReferenceDateTable (ItemId, ReferenceDate, Type) VALUES (1, to_date('19000201', 'YYYYMMDD'), '1');
INSERT INTO ReferenceDateTable (ItemId, ReferenceDate, Type) VALUES (1, to_date('19000202', 'YYYYMMDD'), '1');
INSERT INTO ReferenceDateTable (ItemId, ReferenceDate, Type) VALUES (1, to_date('19000203', 'YYYYMMDD'), '2');
INSERT INTO ReferenceDateTable (ItemId, ReferenceDate, Type) VALUES (1, to_date('19000204', 'YYYYMMDD'), '1');
INSERT INTO ReferenceDateTable (ItemId, ReferenceDate, Type) VALUES (1, to_date('19000205', 'YYYYMMDD'), '1');

CREATE TABLE ResultTable 
    (
     ItemId number,
     ReferenceDate date, 
     Value number
    ); 
INSERT INTO ResultTable (ItemId, ReferenceDate, Value) VALUES (1, to_date('19000201', 'YYYYMMDD'), 1);
INSERT INTO ResultTable (ItemId, ReferenceDate, Value) VALUES (1, to_date('19000202', 'YYYYMMDD'), 2);
INSERT INTO ResultTable (ItemId, ReferenceDate, Value) VALUES (1, to_date('19000203', 'YYYYMMDD'), 3);
INSERT INTO ResultTable (ItemId, ReferenceDate, Value) VALUES (1, to_date('19000204', 'YYYYMMDD'), 4);
INSERT INTO ResultTable (ItemId, ReferenceDate, Value) VALUES (1, to_date('19000205', 'YYYYMMDD'), 5);

CREATE TABLE StartDateTable
    (
     ItemId number,
     StartDate date
    ); 
INSERT INTO StartDateTable (ItemId, StartDate) VALUES (1, to_date('19000101', 'YYYYMMDD'));

WITH FirstDateFilter AS (
    SELECT ReferenceDate,
           Type,
           LAG(Type, 1, 0) OVER (ORDER BY ReferenceDate) AS PreviousType
    FROM ReferenceDateTable
    WHERE ItemId = 1
    AND   ReferenceDate <= to_date('19000205', 'YYYYMMDD')
    AND   Type IN ('1', '2')
), SecondDateFilter AS (
    SELECT ReferenceDate
    FROM FirstDateFilter
    WHERE ReferenceDate >= to_date('19000201', 'YYYYMMDD')
    --AND   ReferenceDate >= ( SELECT StartDate FROM StartDateTable WHERE ItemId = 1 )
    AND   Type = '1'
    AND   PreviousType = '1'
)
SELECT ReferenceDate, Value
FROM ResultTable
WHERE ItemId = 1
AND EXISTS ( SELECT * FROM SecondDateFilter WHERE SecondDateFilter.ReferenceDate = ResultTable.ReferenceDate )
;

【问题讨论】:

请您在查询中添加一些示例数据(例如,使用 referencedatetable 作为 (select 1 id, 'a' col2, .... from dual union all select 2 id, 'b' .. ..) 重现了这个问题,然后我们也可以试一试。 @Boneist 添加了重现问题的示例脚本 @Boneist 我尝试使用 select from dual 输入单个查询,但我无法以这种方式重现问题 这对我来说绝对是一个错误。 Here's a db<>fiddle demonstrating the issue 但如果你把它切换回 11g,它就可以工作(两个版本都是 Oracle XE)。我还在 [LiveSql](livesql.oracle.com/apex/livesql/s/igfmwvw0id0y4xyrp8mqysx66) 中运行了语句,即 19c,这也显示了问题。我还包括了重写的两个查询(在 dbfiddle 和 LiveSQL 中)以避免使用 with 子句,问题也出现在那里。 Jonathan Lewis 看了看(感谢 Jonathan!)并回复 over on Twitter 说它在 18.3 中仍然存在问题,可能与错误 28319114:由于复杂的视图合并导致 12.2 中的错误结果有关。他建议的解决方法是在最终存在子查询中添加不嵌套提示。 【参考方案1】:

根据Jonathan's comments over on Twitter,建议的解决方法是在外部exists 子查询中使用unnest 提示,因为问题是由错误引起的(可能是错误28319114)。

[...]
SELECT ReferenceDate, Value
FROM ResultTable
WHERE ItemId = 1
AND EXISTS ( SELECT /*+ UNNEST */ * FROM SecondDateFilter WHERE SecondDateFilter.ReferenceDate = ResultTable.ReferenceDate )

【讨论】:

以上是关于EXISTS 不适用于 WITH 子句中的子查询的主要内容,如果未能解决你的问题,请参考以下文章

带有“Exists”子句的子查询,其中需要从两个表中收集信息

Oracle where exists 子句不适用于 SQL Plus

Oracle 12c 子查询的 WITH 子句中的函数

oracle exist 语句

SQL 中 EXISTS 与 NOT EXISTS

SQL中的子查询