Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Top 1 with a left join

Given the query below there might be multiple rows in dps_markers with the same marker key but we only want to join against the first. If I take this query and remove the top 1 and ORDER BY I get a value for mbg.marker_value but run as it is it always returns null

SELECT u.id, mbg.marker_value  FROM dps_user u LEFT JOIN      (SELECT TOP 1 m.marker_value, um.profile_id      FROM dps_usr_markers um (NOLOCK)          INNER JOIN dps_markers m (NOLOCK)               ON m.marker_id= um.marker_id AND                  m.marker_key = 'moneyBackGuaranteeLength'      ORDER BY m.creation_date     ) MBG ON MBG.profile_id=u.id  WHERE u.id = 'u162231993' 
like image 201
dstarh Avatar asked Jan 09 '10 14:01

dstarh


People also ask

What is left join 1?

It's not exactly cross join , because when right table returns no rows, cross join will return also no rows, but left join on 1=1 will return all rows from left table with null values as a right table.

Is Left join one to many?

SQL LEFT JOIN examples Each location belongs to one and only one country while each country can have zero or more locations. The relationship between the countries and locations tables is one-to-many.

What does (+) mean in SQL join?

The plus sign is Oracle syntax for an outer join. There isn't a minus operator for joins. An outer join means return all rows from one table. Also return the rows from the outer joined where there's a match on the join key.


2 Answers

Use OUTER APPLY instead of LEFT JOIN:

SELECT u.id, mbg.marker_value  FROM dps_user u OUTER APPLY      (SELECT TOP 1 m.marker_value, um.profile_id      FROM dps_usr_markers um (NOLOCK)          INNER JOIN dps_markers m (NOLOCK)               ON m.marker_id= um.marker_id AND                  m.marker_key = 'moneyBackGuaranteeLength'      WHERE um.profile_id=u.id       ORDER BY m.creation_date     ) AS MBG WHERE u.id = 'u162231993'; 

Unlike JOIN, APPLY allows you to reference the u.id inside the inner query.

like image 141
Remus Rusanu Avatar answered Oct 25 '22 18:10

Remus Rusanu


The key to debugging situations like these is to run the subquery/inline view on its' own to see what the output is:

  SELECT TOP 1           dm.marker_value,           dum.profile_id     FROM DPS_USR_MARKERS dum (NOLOCK)     JOIN DPS_MARKERS dm (NOLOCK) ON dm.marker_id= dum.marker_id                                  AND dm.marker_key = 'moneyBackGuaranteeLength' ORDER BY dm.creation_date 

Running that, you would see that the profile_id value didn't match the u.id value of u162231993, which would explain why any mbg references would return null (thanks to the left join; you wouldn't get anything if it were an inner join).

You've coded yourself into a corner using TOP, because now you have to tweak the query if you want to run it for other users. A better approach would be:

   SELECT u.id,            x.marker_value       FROM DPS_USER u LEFT JOIN (SELECT dum.profile_id,                   dm.marker_value,                   dm.creation_date              FROM DPS_USR_MARKERS dum (NOLOCK)              JOIN DPS_MARKERS dm (NOLOCK) ON dm.marker_id= dum.marker_id                                           AND dm.marker_key = 'moneyBackGuaranteeLength'            ) x ON x.profile_id = u.id      JOIN (SELECT dum.profile_id,                   MAX(dm.creation_date) 'max_create_date'              FROM DPS_USR_MARKERS dum (NOLOCK)              JOIN DPS_MARKERS dm (NOLOCK) ON dm.marker_id= dum.marker_id                                           AND dm.marker_key = 'moneyBackGuaranteeLength'          GROUP BY dum.profile_id) y ON y.profile_id = x.profile_id                                    AND y.max_create_date = x.creation_date     WHERE u.id = 'u162231993' 

With that, you can change the id value in the where clause to check records for any user in the system.

like image 28
OMG Ponies Avatar answered Oct 25 '22 20:10

OMG Ponies