Link to home
Start Free TrialLog in
Avatar of colinasad
colinasadFlag for United Kingdom of Great Britain and Northern Ireland

asked on

How to use TOP 1 in a T-SQL sub-query?

Can someone help me with the correct syntax for a T-SQL query I am trying to apply in a SQL Server 2005 database?

I have TABLE_A that contains unique Order Items and a View VW_B that might contain multipe instances of items from TABLE_A along with further columns concerning the Invoices these Order Items might have appeared on.

I would like to create a query that selects each item from TABLE_A and details of only a single instance of them from VW_B (the latest Invoiced they featured on).

I have been trying suggestions from other web-sites but either get syntax errors or only a single row of output.

My latest attempt has been along the lines of :

SELECT A.Col1, A.Col2, B.*
FROM TABLE_A AS A
OUTER APPLY
(SELECT TOP 1 VB.INVREF, VB.INVDATE
 FROM VW_B AS VB
 ORDER BY VB.INVDATE DESC
 WHERE VB.ITEMID = A.ITEMID) AS B

The above format produces a syntax error with the keyword "WHERE" on Line 7.

Is it because I am using a View rather than a Table in my "SELECT TOP 1 ... " sub-query?

I hope someone can understand what I am trying to do and show me where I am going wrong.

Thanks & regards.
Colin.
Avatar of Daniel Van Der Werken
Daniel Van Der Werken
Flag of United States of America image

I think it's the ORDER BY in the subquery. I've never been able to have an ORDER BY in a subquery. I'm surprised that didn't show up directly as the problem.

Do the ORDER BY after the final query is created. Does that help?
Avatar of colinasad

ASKER

Thanks for the fast response; I'll try that out now.
Also, I think I made a slight mistake when I was paraphrasing my more meaningful table and column names into the simplified format I gave in my example.
I think the WHERE line should have preceded the ORDER BY line in my sub-query.
Regards.
Hello again.
Yes, I am reading elsewhere that "ORDER BY" is not valid in a sub-query.

I am obviously going about this the wrong way because I do need my sub-query (or some other data extraction method) to do some sort of ordering because I want to find the single row from my View that identifies the most recent Invoice that an Item featured in.
I would have thought that my View needed to be sorted into "Item, InvDate DESC" order so that I could then pick the "TOP 1" row for each of my items to give me their most recent Invoice Date.

Can anyone suggest another strategy rather than my failed sub-query attempts?

Many thanks.
Yes, you are correct about the WHERE and ORDER BY locations. Still, take it out of the subquery 'cause I've never been able to get away with that in my own queries.

I see you're doing the top 1 and then ordering by date and hence that's why you want the order by. You can do a similar thing by selecting the max date.

So, do this:

SELECT A.Col1, A.Col2, B.*
FROM TABLE_A AS A
OUTER APPLY
(
   SELECT 
                  VB.INVREF, 
                  VB.INVDATE
 FROM VW_B AS VB
 WHERE VB.ITEMID = A.ITEMID
   AND VB.INVDATE = (SELECT MAX(INVDATE) FROM VW_B WHERE INVREF = VB.INVREF)
) AS B

Open in new window


Anyway, I might not have it exact because I tend to try these things and tweak them to work as I work on them. You should be getting the idea here though. You want yet another subquery that gets the max date from the same table for that item, then use that max date to find the item with the max date.

Therefore, if you have like 10 dates, you get the max data for that ID then get the row with that max date which returns the row you want and is essentially the same as the TOP 1 with order by DESC.
Heh. I was typing up the answer to your question as you asked it. If you can think a valid set of data that represents what you have, we can do some test queries.
Try..

SELECT A.Col1, A.Col2, B.*
FROM TABLE_A AS A
OUTER APPLY
(SELECT TOP 1 VB.INVREF, VB.INVDATE
 FROM VW_B AS VB
 WHERE VB.ITEMID = A.ITEMID 
 ORDER BY VB.INVDATE DESC ) AS B

Open in new window

The ORDER BY clause is always the last one in a query. Pawan got it right, here is formatted:
SELECT 
	A.Col1, 
	A.Col2, 
	B.*
FROM 
	TABLE_A AS A
	OUTER APPLY
	(
		SELECT TOP 1 
			VB.INVREF, 
			VB.INVDATE
		FROM 
			VW_B AS VB
		WHERE 
			VB.ITEMID = A.ITEMID
		ORDER BY 
			VB.INVDATE DESC
	) AS B

Open in new window

Thanks very much for the further suggestions.
I won't be able to work on this again until tomorrow afternoon (UK time), but I will report back then with my progress.
Best regards.
Thanks for the suggestions, which I have just been able to test before rushing off again.

I have 7,820 items in Table_A
I have on 52 items in View_B

Not all items from Table_A occur in View_B but some if the items in View_B are duplicated because they have recently been invoiced more than once and I only want the most recent instances from View_B.

Daniel's suggestion gives me 7,843 results!
Both Pawan and Zberteoc's suggestions give 7,820 results.

I am hoping to get 52 or fewer results. I only want the items from Table_A that are also generated by View_B.

I might try breaking things down into a couple of separate Views rather than trying to achieve my result in a singe (obviously trick) SQL command.

Any further suggestions welcomed, though.

Best regards. Colin.
ASKER CERTIFIED SOLUTION
Avatar of Pawan Kumar
Pawan Kumar
Flag of India image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Replace:

OUTER APPLY

with

CROSS APPLY

OUTER APPLY is similar to LEFT JOIN while CROSS APPLY to INNER JOIN.
Thanks for that.
The CROSS APPLY seems to do the job, reducing my results down from 7,820 possible items, through 52 recent invoice events, to the specific 29 items involved in these invoices.
I will double-check that it is producing the correct invoicing event for multi-invoiced items, but I think I now have a mechanism I can work.
I think this is the first time I have used the APPLY feature so will need to read a bit more to get my head around it.
Many thanks to everyone who commented.
Well, for these kind a situation when to the solution more than one expert contributed there is the split points option. :)
Thanks for the further comment, Zberteoc.
It's been a while since I've been on EE and the interface has changed.
In the "old days" you could explicitly split your 500 points across answers.
I'll make use of the "assisted" option in future.
Thanks and regards.
Colin.