ユーザーの表があります:
|Username|UserType|Points|
|John |A |250 |
|Mary |A |150 |
|Anna |B |600 |
とレベル
|UserType|MinPoints|Level |
|A |100 |Bronze |
|A |200 |Silver |
|A |300 |Gold |
|B |500 |Bronze |
そして、各ユーザーのレベルを取得するためのクエリを探しています。以下に沿ったもの:
SELECT *
FROM Users U
INNER JOIN (
SELECT TOP 1 Level, U.UserName
FROM Levels L
WHERE L.MinPoints < U.Points
ORDER BY MinPoints DESC
) UL ON U.Username = UL.Username
結果は次のようになります。
|Username|UserType|Points|Level |
|John |A |250 |Silver |
|Mary |A |150 |Bronze |
|Anna |B |600 |Bronze |
カーソルを使わずにこれを行うにはどうすればよいかについて、アイデアや提案がありますか?
既存のクエリは使用できるものに近いですが、いくつかの変更を加えることで簡単に結果を得ることができます。 APPLY
演算子を使用するようにクエリを変更し、CROSS APPLY
を実装します。これにより、要件を満たす行が返されます。使用できるバージョンは次のとおりです。
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;
これが SQL Fiddleデモ付き です。これにより結果が生成されます。
| Username | UserType | Points | Level |
|----------|----------|--------|--------|
| John | A | 250 | Silver |
| Mary | A | 150 | Bronze |
| Anna | B | 600 | Bronze |
次のソリューションでは、Levels
テーブルを1回スキャンする共通テーブル式を使用しています。このスキャンでは、「次の」ポイントレベルはLEAD()
ウィンドウ関数を使用して検出されるため、MinPoints
(行から)とMaxPoints
(次のMinPoints
は現在のUserType
)です。
その後、次のように、lvls
の共通テーブル式UserType
とMinPoints
/MaxPoints
の範囲を単純に結合できます。
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;
ウィンドウ関数を使用する利点は、あらゆる種類の再帰的ソリューションを排除し、パフォーマンスを劇的に向上させることです。最高のパフォーマンスを得るには、Levels
テーブルで次のインデックスを使用します。
CREATE UNIQUE INDEX ... ON Levels (UserType, MinPoints) INCLUDE ([Level]);
初歩的な操作であるINNER JOIN、GROUP BY、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
;
_INNER JOIN
_を使用できると思います-パフォーマンスの問題として、代わりに_LEFT JOIN
_をROW_NUMBER()
関数とともに次のように使用することもできます:
_SELECT
Username, UserType, Points, Level
FROM (
SELECT u.*, l.Level,
ROW_NUMBER() OVER (PARTITION BY u.Username ORDER BY l.MinPoints DESC) seq
FROM
Users u INNER JOIN
Levels l ON u.UserType = l.UserType AND u.Points >= l.MinPoints
) dt
WHERE
seq = 1;
_