查询以选择联接上的最大值


13


我有一个用户表:

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

是否有人对我不使用游标该怎么做有任何想法或建议?

Answers:


15

您现有的查询接近您可以使用的查询,但只需进行一些更改即可轻松获得结果。通过更改查询以使用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 |

3

以下解决方案使用一个通用表表达式,该Levels表表达式对表进行一次扫描。在此扫描中,使用LEAD()窗口功能找到“下一个”点级别,因此您MinPoints(从行中)和MaxPointsMinPoints当前行的下一个UserType)具有。

之后,您可以简单地将公用表表达式lvls,on UserTypeMinPoints/ 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的回答要慢一些。对于我的特定数据集,使用CTE不需要索引大约需要10秒钟,使用您在“级别”上建议的索引需要7秒钟,而上面的“交叉应用”查询需要不到3秒(即使没有索引)
Lambo Jayapalan

@LamboJayapalan这个查询看起来至少应该和bluefeet一样有效。您是否添加了确切的索引(带有INCLUDE)?另外,您有索引Users (UserType, Points)吗?(这可能会有所帮助)
ypercube16

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

SQL小提琴演示

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.