I ran into a query where the EXISTS clause is not working properly. The query returns results even for items where no matching record exist, seemingly ignoring the EXISTS completely. It used to work fine and I think the troubles started after upgrading from Oracle 12.1 to 12.2.
Below is the complete query (only changed table & column names to make them a little more readable, but I left all the logic in case it's related to that):
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 )
After playing around with some test data, I think the (partially?) responsible line for the failure is the subquery AND ReferenceDate >= ( SELECT StartDate FROM StartDateTable WHERE ItemId = :itemId )
in the second WITH.
I have found that any of the following edits result in the EXISTS working as expected again:
( SELECT ReferenceDate FROM SecondDateFilter WHERE SecondDateFilter.ReferenceDate = ResultTable.ReferenceDate )
in the SELECT ... FROM ResultTable
The last solution actually fixes my problem with this query (technically not the same, but the underlying business logic checks out that the result will always be the same), but I was wondering if there was a general problem with the EXISTS clause (potentially only in Oracle 12.2?) that I should be aware of. I have a lot more queries that make use of it.
Below is a test script that duplicates the error. The query below returns 2 rows as expected, but removing the commented line gives 5 rows.
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 )
;
The SQL EXISTS condition is used in combination with a subquery and is considered to be met, if the subquery returns at least one row. It can be used in a SELECT, INSERT, UPDATE, or DELETE statement.
The EXISTS checks the existence of a result of a Subquery. The EXISTS subquery tests whether a subquery fetches at least one row. When no data is returned then this operator returns 'FALSE'. A valid EXISTS subquery must contain an outer reference and it must be a correlated Subquery.
Use EXISTS to identify the existence of a relationship without regard for the quantity. For example, EXISTS returns true if the subquery returns any rows, and [NOT] EXISTS returns true if the subquery returns no rows. The EXISTS condition is considered to be met if the subquery returns at least one row.
Syntax: SELECT column_name(s) FROM table_name WHERE EXISTS (SELECT column_name(s) FROM table_name WHERE condition);
If StudentNumber, which appears in the Students table, also exists in the Orders table, the subquery returns matching rows. As a result, the EXISTS operator returns true. Otherwise, the subquery returns no row and the EXISTS operator returns false.
Usually your NOT EXISTSclause would reference another table. For example, if you wanted to query the usuario table where the idUsuario value was not present in another table you would do: SELECT * FROM usuario u WHERE NOT EXISTS ( SELECT * FROM usuarioExclude x WHERE x.idUsuario = u.idUsuario )
If the subquery does not return any records, the EXISTS clause will evaluate as false and the EXISTS condition will not be met. Because EXISTS and NOT EXISTS are used with correlated subqueries, the subquery executes once for every row in the outer query.
Now let’s move on, the EXISTS operator is a Boolean operator that returns either true or false. The EXISTS operator is often used in a subquery to test if data exists condition. For example, in each row in the Students table, the query checks the StudentNumber in the Orders table.
As per Jonathan's comments over on Twitter, the suggested workaround is to use the unnest
hint in the outer exists subquery, as the issue is due to a bug (potentially Bug 28319114).
[...]
SELECT ReferenceDate, Value
FROM ResultTable
WHERE ItemId = 1
AND EXISTS ( SELECT /*+ UNNEST */ * FROM SecondDateFilter WHERE SecondDateFilter.ReferenceDate = ResultTable.ReferenceDate )
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With