結合の最大値を選択するクエリ


12


ユーザーのテーブルがあります:

|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 |

カーソルに頼らずにどのようにこれを行うことができるかについてのアイデアや提案はありますか?

回答:


14

既存のクエリは使用できるものに近いものですが、いくつかの変更を加えることで簡単に結果を取得できます。クエリを変更して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フィドルはデモで。これにより結果が生成されます。

| Username | UserType | Points |  Level |
|----------|----------|--------|--------|
|     John |        A |    250 | Silver |
|     Mary |        A |    150 | Bronze |
|     Anna |        B |    600 | Bronze |

3

次のソリューションでは、Levelsテーブルを1回スキャンする共通テーブル式を使用しています。このスキャンでは、LEAD()ウィンドウ関数を使用して「次の」ポイントレベルが検出されるためMinPointsMaxPoints(行から)および(MinPoints現在のの次UserType)があります。

その後、次のように、共通テーブル式lvls、on 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]);

迅速な返答に感謝致します。あなたのクエリは私が必要とする正確な結果を与えてくれますが、上の「CROSS APPLY」を使用したbluefeetの答えよりも少し遅いようです。クロスクエリを適用する上記(偶数インデックスなし)のすぐ下に3秒かかるのに対し、私の特定のデータセットの場合は、使用して、CTEは、インデックスなしで10秒、そしてあなたがレベルで提案したインデックスと7秒程度かかり
ランボJayapalan

@LamboJayapalanこのクエリは、少なくともbluefeetと同じくらい効率的であるように見えます。この正確なインデックスを(でINCLUDE)追加しましたか?また、インデックスはありUsers (UserType, Points)ますか?(それが役立つかもしれない)
ypercubeᵀᴹ

そして、何人のユーザー(表の行Users)があり、その表の幅はどれくらいですか?
ypercubeᵀᴹ

2

初歩的な操作、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
;

2

次のような機能で、代わりにINNER JOIN使用できるパフォーマンスの問題として- を使用できると思います:LEFT JOINROW_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;

SQL Fiddleデモ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.