问题
I'm on Oracle and I need to use both, ORDER BY
and ROWNUM
, in one request. I need to double-nest my inner query, because I want to apply ORDER BY
first and select with ROWNUM=1
afterwards.
My data is filtered by O.ID
on outmost level. However, I get an error in my inner query, because O.ID
is an unknown identifier there.
What I want:
SELECT
O.INSERTDATE OrderCreateDate,
-- Determine delivery date
(SELECT INSERTDATE FROM (
SELECT OP2.FK_ORDER, DD.ID, DD.INSERTDATE FROM MY_DELIVERYDATE_TABLE DD
JOIN MY_ORDERPOS_TABLE OP2 ON DD.FK_ORDERPOS=OP2.ID
LEFT OUTER JOIN MY_ORDER_TABLE O2 ON OP2.FK_ORDER=O2.ID
WHERE OP2.FK_ORDER=O.ID AND -- This gives me "Invalid identifier O.ID"
DD.DELFLAG IS NULL AND OP2.DELFLAG IS NULL
ORDER BY DD.CLOSED ASC, ABS(TRUNC(CURRENT_DATE-TO_DATE(TO_CHAR(DD.INSERTDATE, 'DDMMYYYY'), 'DDMMYYYY'))) ASC
) WHERE ROWNUM=1) DeliveryDate
FROM MY_ORDER_TABLE O
WHERE O.ID = 620; -- ID goes here!
The only way I get this working, is when I filter in the WHERE
clause of the intermediate SELECT
query. But this is slow, of course, since the inner SQL returns the entire data without filtering.
SELECT
O.INSERTDATE OrderCreateDate,
-- Determine delivery date
(SELECT INSERTDATE FROM (
SELECT OP2.FK_ORDER, DD.ID, DD.INSERTDATE FROM MY_DELIVERYDATE_TABLE DD
JOIN MY_ORDERPOS_TABLE OP2 ON DD.FK_ORDERPOS=OP2.ID
LEFT OUTER JOIN MY_ORDER_TABLE O2 ON OP2.FK_ORDER=O2.ID
WHERE DD.DELFLAG IS NULL AND OP2.DELFLAG IS NULL
ORDER BY DD.CLOSED ASC, ABS(TRUNC(CURRENT_DATE-TO_DATE(TO_CHAR(DD.INSERTDATE, 'DDMMYYYY'), 'DDMMYYYY'))) ASC
) WHERE ROWNUM=1 AND FK_ORDER=O.ID) DeliveryDate -- Filtering here
FROM MY_ORDER_TABLE O
WHERE O.ID = 620;
How can I pass O.ID
to the inner query or how can this query be redesigned, still keeping ORDER BY
and ROWNUM
work.
My final solution as suggested by Kim Berg Hansen and improved by rims:
(I had to use MIN()
instead of MAX()
, though)
SELECT
O.INSERTDATE OrderCreateDate,
-- Determine delivery date
(SELECT MIN(DD.INSERTDATE) KEEP (DENSE_RANK FIRST ORDER BY
DD.CLOSED ASC, ABS(TRUNC(CURRENT_DATE-TRUNC(DD.INSERTDATE))) ASC)
FROM MY_DELIVERYDATE_TABLE DD
JOIN MY_ORDERPOS_TABLE OP2 ON DD.FK_ORDERPOS=OP2.ID
LEFT OUTER JOIN MY_ORDER_TABLE O2 ON OP2.FK_ORDER=O2.ID
WHERE OP2.FK_ORDER=O.ID AND
DD.DELFLAG IS NULL AND OP2.DELFLAG IS NULL
) DeliveryDate
FROM MY_ORDER_TABLE O
WHERE O.ID = 620; -- ID goes here!
回答1:
In the scalar subquery you are using, you can only reference the tables from the "main" query "one nested level down", not any further down, as you have seen. (I believe this restriction is lifted in version 12, so maybe you can just upgrade your database? ;-)
In the scalar subquery you are trying to get the value of INSERTDATE column of the first row according to your ordering. That can also be written without nesting as follows:
SELECT
O.INSERTDATE OrderCreateDate,
-- Determine delivery date
(SELECT MAX(DD.INSERTDATE) KEEP (
DENSE_RANK FIRST ORDER BY
DD.CLOSED ASC, ABS(TRUNC(CURRENT_DATE-TO_DATE(TO_CHAR(DD.INSERTDATE, 'DDMMYYYY'), 'DDMMYYYY'))) ASC
)
FROM MY_DELIVERYDATE_TABLE DD
JOIN MY_ORDERPOS_TABLE OP2 ON DD.FK_ORDERPOS=OP2.ID
LEFT OUTER JOIN MY_ORDER_TABLE O2 ON OP2.FK_ORDER=O2.ID
WHERE OP2.FK_ORDER=O.ID AND -- This will no longer give "Invalid identifier O.ID"
DD.DELFLAG IS NULL AND OP2.DELFLAG IS NULL
) DeliveryDate
FROM MY_ORDER_TABLE O
WHERE O.ID = 620; -- ID goes here!
KEEP (DENSE_RANK FIRST tells the MAX function, that it should calculate the MAX only of those rows that rank first in the ORDER BY clause. So if your ORDER BY is "unique", MAX will be applied only to one row. If your ORDER BY is not "unique" and can have duplicates, you might think about whether you want the MAX or the MIN (or add something to the ORDER BY to make it unique.)
(If you had been on Oracle version 12, an alternative to the KEEP (DENSE_RANK trick would be to use the FIRST 1 ROW ONLY clause of the SELECT statement.)
来源:https://stackoverflow.com/questions/25616529/invalid-identifier-in-double-nested-query-with-order-by-and-rownum