Query to select max value on join
Your existing query is close to something that you could use but you can get the result easily by making a few changes. By altering your query to use the APPLY
operator and implementing CROSS APPLY
. This will return the row that meets your requirements. Here's a version that you could use:
SELECT
u.Username,
u.UserType,
u.Points,
lv.Level
FROM Users u
CROSS APPLY
(
SELECT TOP 1 Level
FROM Levels l
WHERE u.UserType = l.UserType
and l.MinPoints < u.Points
ORDER BY l.MinPoints desc
) lv;
Here's a SQL Fiddle with a demo. This produces a result:
| Username | UserType | Points | Level |
|----------|----------|--------|--------|
| John | A | 250 | Silver |
| Mary | A | 150 | Bronze |
| Anna | B | 600 | Bronze |
The following solution uses a common table expression that scans the Levels
table once. In this scan, the "next" points level is found using the LEAD()
window function, so you have MinPoints
(from the row) and MaxPoints
(the next MinPoints
for the current UserType
).
After that, you can simply join the common table expression, lvls
, on UserType
and the MinPoints
/MaxPoints
range, like so:
WITH lvls AS (
SELECT UserType, MinPoints, [Level],
LEAD(MinPoints, 1, 99999) OVER (
PARTITION BY UserType
ORDER BY MinPoints) AS MaxPoints
FROM Levels)
SELECT U.*, L.[Level]
FROM Users AS U
INNER JOIN lvls AS L ON
U.UserType=L.UserType AND
L.MinPoints<=U.Points AND
L.MaxPoints> U.Points;
The advantage of using the window function is that you eliminate all sorts of recursive solutions and improve performance dramatically. For best performance, you would use the following index on the Levels
table:
CREATE UNIQUE INDEX ... ON Levels (UserType, MinPoints) INCLUDE ([Level]);
Why not do it using only the rudimentary operations, INNER JOIN, GROUP BY, and MAX:
SELECT U1.*,
L1.Level
FROM Users AS U1
INNER JOIN
(
SELECT U2.Username,
MAX(L2.MinPoints) AS QualifyingMinPoints
FROM Users AS U2
INNER JOIN
Levels AS L2
ON U2.UserType = L2.UserType
WHERE L2.MinPoints <= U2.Points
GROUP BY U2.Username
) AS Q
ON U1.Username = Q.Username
INNER JOIN
Levels AS L1
ON Q.QualifyingMinPoints = L1.MinPoints
AND U1.UserType = L1.UserType
;