Search code examples
sqlt-sqlsql-server-2000

SQL 2000 Left Join Top 1 of 0 to many relationship


This question has been asked multiple times on SO but all the answers refer to SQL 2005 or later (e.g. OUTER APPLY) and we are still using SQL 2000 (for corporate reasons too complex to go into here!)

I have a table of Things and a table of Widgets with a 0 to Many relationship:

CREATE TABLE Things ( ThingId INT, ThingName VARCHAR(50) )

CREATE TABLE Widgets ( WidgetId INT, ThingId INT, WidgetName VARCHAR(50) )

INSERT INTO Things VALUES ( 1, 'Thing 1' )

INSERT INTO Things VALUES ( 2, 'Thing 2' )

INSERT INTO Things VALUES ( 3, 'Thing 3' )

INSERT INTO Widgets VALUES ( 1, 2, 'Thing 2 Widget 1' )

INSERT INTO Widgets VALUES ( 2, 2, 'Thing 2 Widget 2' )

INSERT INTO Widgets VALUES ( 3, 3, 'Thing 3 Widget 1' )

A standard LEFT OUTER JOIN returns the expected 4 rows

SELECT * FROM Things t LEFT OUTER JOIN Widgets w ON t.ThingId = w.ThingId

 ThingId | ThingName | WidgetId | ThingId | WidgetName       
---------+-----------+----------+---------+------------------
 1       | Thing 1   | NULL     | NULL    | NULL             
 2       | Thing 2   | 1        | 2       | Thing 2 Widget 1 
 2       | Thing 2   | 2        | 2       | Thing 2 Widget 2 
 3       | Thing 3   | 3        | 3       | Thing 3 Widget 1 

However, I only want the newest Widget for each Thing, i.e.:

 ThingId | ThingName | WidgetId | ThingId | WidgetName       
---------+-----------+----------+---------+------------------
 1       | Thing 1   | NULL     | NULL    | NULL             
 2       | Thing 2   | 2        | 2       | Thing 2 Widget 2 
 3       | Thing 3   | 3        | 3       | Thing 3 Widget 1 

My starting point was:

SELECT * FROM Things t LEFT OUTER JOIN (SELECT TOP 1 * FROM Widgets subw WHERE subw.ThingId = t.ThingId ORDER BY subw.WidgetId DESC) w ON t.ThingId = w.ThingId

But this is not valid because the parent t.ThingId does not exist in the sub query.

Can this be achieved using SQL 2000?


Solution

  • If (ThingId, WidgetId) combination is unique in table Widgets, then this will work correctly:

    SELECT t.*, w.* 
    FROM 
        dbo.Things AS t 
      LEFT OUTER JOIN 
          ( SELECT ThingId, MAX(WidgetId) AS WidgetId 
            FROM dbo.Widgets 
            GROUP BY ThingId
          ) AS 
        subw
          ON  subw.ThingId = t.ThingId
      LEFT OUTER JOIN
        dbo.Widgets AS w
          ON  w.ThingId = subw.ThingId
          AND w.WidgetId = subw.WidgetId ;