EXISTS无法在WITH子句中使用子查询

Sam*_*Sam 6 sql oracle oracle12c

我遇到了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 )
Run Code Online (Sandbox Code Playgroud)

在处理了一些测试数据之后,我认为导致失败的(部分?)行是AND ReferenceDate >= ( SELECT StartDate FROM StartDateTable WHERE ItemId = :itemId )第二个WITH中的子查询。

我发现以下任何编辑都会导致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 )
;
Run Code Online (Sandbox Code Playgroud)

Bon*_*ist 3

根据Jonathan 在 Twitter 上的评论,建议的解决方法是使用unnest外部存在子查询中的提示,因为该问题是由于错误(可能是错误 28319114)引起的。

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