Invalid identifier in double-nested query with ORDER BY and ROWNUM - sql

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!

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.)

Related

Why I get error missing right parenthesis ORA-00907

I am stuck in one problem and I have no idea where did I made mistake.
Since I check everything and every solution but I can not see what I made wrong.
SELECT
o.OrderID,
o.Order_date,
o.status,
o.OrderAcceptanceCommentsSaved,
o.OrderFileAttachment,
o.HasErrors,
o.ErrorsResolved,
(SELECT ou.Status FROM order_unload ou WHERE ou.OrderID = o.OrderID
AND rownum <= 1 ORDER BY ou.Id DESC) AS UnloadStatus
FROM
orders o
WHERE
ProjectID = 141
ORDER BY ou.Id DESC;
The problem here is second SELECT
(SELECT ou.Status FROM order_unload ou WHERE ou.OrderID = o.OrderID
AND rownum <= 1 ORDER BY ou.Id DESC) AS UnloadStatus)
However, when I want to execute only second SELECT I also get error
o.OrderID invalid identifier
Can someone guide me and tell me where I made mistake? What is wrong with this query?
You have several problems:
The ORDER BY clause is not allowed in a correlated sub-query so the SQL engine expects the query to end before the ORDER BY and there to be a closing brace at that point. Remove the ORDER BY clause in the inner select and that error would go away (and you would get a different error).
ROWNUM is applied before the ORDER BY is evaluated so, even if the query was syntactically valid, it would not do what you wanted as you would get a random row (the first the SQL engine happens to read) which would be given a ROWNUM of 1 and then the rest of the rows discarded and then that single (random) row would be ordered. You want to order first and then get the first row.
You are using ou.id to order the outer query but the ou alias is not visible in that outer select.
You can use:
SELECT o.OrderID,
o.Order_date,
o.status,
o.OrderAcceptanceCommentsSaved,
o.OrderFileAttachment,
o.HasErrors,
o.ErrorsResolved,
ou.status AS UnloadStatus
FROM orders o
LEFT OUTER JOIN (
SELECT status,
orderid,
id,
ROW_NUMBER() OVER ( PARTITION BY orderid ORDER BY id DESC ) AS rn
FROM order_unload
) ou
ON ( o.orderid = ou.OrderID AND ou.rn = 1 )
WHERE ProjectID = 141
ORDER BY ou.Id DESC;
db<>fiddle here

performance with left join and include max in the query with values null

The data of the table exceeds 7 billion.
I want to display the max of entryDate affiliation for each participant and i want to include the null values so i used left join but the query takes long minute. Anyway it gives me the expected results.
Could anyone has a better idea or another better solution to fix the performance?
Select ParticipantID,MaxDate
From dbo.Participant Par
LEFT JOIN dbo.Affiliation Aff
ON AFF.ParticipantID=Par.ParticipantID
LEFT JOIN (
SELECT AFF.AffiliationID,
MAX(EntryDate) as MaxDate
FROM dbo.Affiliation
GROUP BY AFF.AffiliationID
)AS AFF1
ON AFF1.AffiliationID = AFF.AffiliationID
AND AFF1.MaxDate = AFF.EntryDate
I think the first join is unnecessary
SELECT ParticipantID, MaxDate
FROM dbo.Participant Par
OUTER APPLY (
SELECT MAX(EntryDate) as MaxDate
FROM dbo.Affiliation Aff
WHERE Aff.ParticipantID = ParParticipantID
) A
Also you need index on Affilation:
CREATE INDEX IX_Affiliation_ParticipantID_EntryDate ON dbo.Affiliation(ParticipantID, EntryDate)
#llyas There could be lot more considerations if you have xml show plan on or include actual execution plan and check for subtree cost
Anyways, you can use this query by using row_num function
WITH par
AS (
SELECT ParticipantID
,EntryDate AS MaxDate
,ROW_NUMBER() OVER (
PARTITION BY AffiliationID ORDER BY ENTRYDATE DESC
) rn
FROM dbo.Participant Par
LEFT JOIN dbo.Affiliation Aff
ON AFF.ParticipantID = Par.ParticipantID
)
SELECT Participantid
,Maxdate
WHERE rn = 1

Display only maximum value in query with column meno - SQL

As mentioned in the title I want to display only row with maximum number, for this case it´s number 4 and nothing less.
select divak.MENO,count(divak.MENO)
from sledovanost
natural join tv_stanice
natural join divak
group by divak.meno
order by count(divak.MENO)desc;
My query
You can achieve this by a having-clause, in which you compare each MENO count with the maximum MENO count (retrieved by a subquery):
select divak.MENO,count(divak.MENO)
from sledovanost
natural join tv_stanice
natural join divak
group by divak.meno
having count(divak.MENO) = (
select (max(count(divak.MENO))
from sledovanost
natural join tv_stanice
natural join divak
group by divak.meno)
BTW: I'd change the query to join .. ON .. syntax; natural join, which connects tables on equally named attributes, bears the danger of "unintended connections" whenever the db schema changes.
Don't use natural join. It uses the names of the columns to match tables, and doesn't show the names in the query. It is a bug waiting to happen. Use USING or ON.
In Oracle, you would normally do this using row_number() or rank() depending on whether or not you wanted duplicates:
with t as (<your query here but name the second column>)
select t.*
from (select t.*, rank() over (order by cnt desc) as seqnum
from t
) t
where seqnum = 1;

Can we use order by in subquery? If not why sometime could use top(n) order by?

I'm an entry level trying to learn more about SQL,
I have a question "can we use order by in subquery?" I did look for some article says no we could not use.
But on the other hand, I saw examples using top(n) with order by in subquery:
select c.CustomerId,
c.OrderId
from CustomerOrder c
inner join (
select top 2
with TIES CustomerId,
COUNT(distinct OrderId) as Count
from CustomerOrder
group by CustomerId
order by Count desc
) b on c.CustomerId = b.CustomerId
So now I'm bit confused.
Could anyone advise?
Thank you very much.
Yes, you are right we cannot use order by in a inner query. Because it is acting as a table. A table in itself needs to be sorted when queried for different purposes.
In your query itself the inner query is select some records using Top 2. Eventhough these are top 2 records only, they form a table with 2 records which is enough for it to recognized as a table and join it with another table
The right query will be:-
SELECT * FROM
(
SELECT c.CustomerId, c.OrderId, DENSE_RANK() OVER(ORDER BY b.count DESC) AS RANK
FROM CustomerOrder c
INNER JOIN
(SELECT CustomerId, COUNT(distinct OrderId) as Count
FROM CustomerOrder GROUP BY CustomerId) b
ON c.CustomerId = b.CustomerId
) a
WHERE RANK IN (1,2);
Hope I have answered your question.
Yes we can use order by clause in sub query, for example i have a table named as product (check the screen shot of table http://prntscr.com/f15j3z). Chek this query on your side and revert me in case of any doubt.
select p1.* from product as p1 where product_id = (select p2.product_id from product as p2 order by product_id limit 0,1)
yes we can use order by in subquery,but it is pointless to use it.
It is better to use it in the outer query.There is no use of ordering the result of subquery, because result of inner query will become the input for outer query and it does not have to do any thing with the order of the result of subquery.

How can you add 2 joins in a subquery?

I am trying to get information from 3 tables in my database. I am trying to get 4 fields. 'kioskid', 'kioskhours', 'videotime', 'sessiontime'. In order to do this, i am trying a join in a subquery. This is what I have so far:
SELECT k.kioskid, k.hours, v.time, s.time
FROM `nsixty_kiosks` as k
LEFT JOIN (SELECT time
FROM `nsixty_videos`
ORDER BY videoid) as v
ON kioskid = k.kioskid LEFT JOIN
(SELECT kioskid, time
FROM `sessions`
ORDER BY pingid desc LIMIT 1) as s ON s.kioskid = k.kioskid
WHERE hours is NOT NULL
When I run this query, it works but it shows every row instead of just showing the last row of each kiosk id. Which is meant to show based on the line 'ORDER BY pingid desc LIMIT 1'.
Any body have some ideas?
Instead of joining to s, you can use a correlated subquery:
SELECT k.kioskid,
k.hours,
v.time,
( SELECT time
FROM sessions
WHERE sessions.kioskid = k.kioskid
ORDER
BY pingid DESC
LIMIT 1
)
FROM nsixty_kiosks AS k
LEFT
JOIN ( SELECT time
FROM `nsixty_videos`
ORDER BY videoid
) AS v
ON kioskid = k.kioskid
WHERE hours IS NOT NULL
;
N.B. I didn't fix your LEFT JOIN (...) AS v, because I don't understand what it's trying to do, but it too is broken; the ON clause doesn't refer to any of its columns, and there's no point in having an ORDER BY in a subquery unless you also have a LIMIT or whatnot in there.
Well, your join on the 'v' subquery doesn't actually reference the 'v' subquery, nor does the 'v' subquery even contain a kioskid field to JOIN on, so that's undoubtedly part of the problem.
To go much further we'd need to see schema and sample data.