Truy vấn để chọn giá trị tối đa khi tham gia


12


Tôi có một bảng Người dùng:

|Username|UserType|Points|
|John    |A       |250   |
|Mary    |A       |150   |
|Anna    |B       |600   |

và cấp độ

|UserType|MinPoints|Level  |
|A       |100      |Bronze |
|A       |200      |Silver |
|A       |300      |Gold   |
|B       |500      |Bronze |

Và tôi đang tìm kiếm một truy vấn để có được mức độ cho mỗi người dùng. Một cái gì đó dọc theo dòng:

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

Như vậy kết quả sẽ là:

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

Có ai có bất kỳ ý tưởng hoặc đề xuất nào về cách tôi có thể làm điều này mà không cần dùng đến con trỏ không?

Câu trả lời:


14

Truy vấn hiện tại của bạn gần với một cái gì đó mà bạn có thể sử dụng nhưng bạn có thể nhận được kết quả dễ dàng bằng cách thực hiện một vài thay đổi. Bằng cách thay đổi truy vấn của bạn để sử dụng APPLYtoán tử và thực hiện CROSS APPLY. Điều này sẽ trả về hàng đáp ứng yêu cầu của bạn. Đây là phiên bản mà bạn có thể sử dụng:

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;

Đây là một Fiddle SQL với một bản demo . Điều này tạo ra một kết quả:

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

3

Giải pháp sau đây sử dụng biểu thức bảng chung quét Levelsbảng một lần. Trong lần quét này, mức điểm "tiếp theo" được tìm thấy bằng cách sử dụng LEAD()chức năng cửa sổ, do đó bạn có MinPoints(từ hàng) và MaxPoints(tiếp theo MinPointscho hiện tại UserType).

Sau đó, bạn chỉ có thể tham gia các biểu thức bảng chung, lvlstrên UserTypeMinPoints/ MaxPointsphạm vi, như vậy:

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;

Ưu điểm của việc sử dụng chức năng cửa sổ là bạn loại bỏ tất cả các loại giải pháp đệ quy và cải thiện hiệu suất đáng kể. Để có hiệu suất tốt nhất, bạn sẽ sử dụng chỉ mục sau trên Levelsbảng:

CREATE UNIQUE INDEX ... ON Levels (UserType, MinPoints) INCLUDE ([Level]);

Cảm ơn đã phản ứng nhanh chóng. Truy vấn của bạn không cung cấp cho tôi kết quả chính xác mà tôi cần, nhưng dường như chậm hơn một chút so với câu trả lời của bluefeet ở trên bằng cách sử dụng "CROSS ỨNG DỤNG". Đối với tập dữ liệu cụ thể của tôi, sử dụng CTE của bạn mất khoảng 10 giây mà không có chỉ mục và 7 giây với chỉ mục bạn đã đề xuất trên Levels, trong khi truy vấn Cross Apply ở trên chỉ mất dưới 3 giây (ngay cả khi không có chỉ mục)
Lambo Jayapalan

@LamboJayapalan Truy vấn này có vẻ như ít nhất là hiệu quả như của bluefeet. Bạn đã thêm chỉ số chính xác này (với INCLUDE)? Ngoài ra, bạn có một chỉ số trên Users (UserType, Points)? (nó có thể giúp)
ypercubeᵀᴹ 16/2/2016

Và có bao nhiêu người dùng (hàng trong bảng Users) và bảng đó rộng bao nhiêu?
ypercubeᵀᴹ 16/2/2016

2

Tại sao không làm điều đó bằng cách chỉ sử dụng các thao tác thô sơ, INNER THAM GIA, NHÓM THEO và 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

Tôi nghĩ rằng bạn có thể sử dụng INNER JOIN- như một vấn đề về hiệu suất mà bạn cũng có thể sử dụng LEFT JOINthay thế - với ROW_NUMBER()chức năng như thế này:

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;

Bản thử nghiệm SQL Fiddle

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.