แบบสอบถามเพื่อเลือกค่าสูงสุดเมื่อเข้าร่วม


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 |

ไม่มีใครมีความคิดหรือคำแนะนำเกี่ยวกับวิธีการที่ฉันสามารถทำได้โดยไม่ต้องหันไปหาเคอร์เซอร์

คำตอบ:


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 กับการสาธิต สิ่งนี้สร้างผลลัพธ์:

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

3

โซลูชันต่อไปนี้ใช้นิพจน์ตารางทั่วไปที่สแกนLevelsตารางหนึ่งครั้ง ในการสแกนนี้พบระดับคะแนน "ถัดไป" โดยใช้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]);

ขอบคุณสำหรับการตอบสนองอย่างรวดเร็ว. ข้อความค้นหาของคุณให้ผลลัพธ์ตามที่ฉันต้องการ แต่ดูเหมือนช้ากว่าคำตอบของ Bluefeet เล็กน้อยโดยใช้ "CROSS APPLY" สำหรับชุดข้อมูลเฉพาะของฉันการใช้ CTE ของคุณจะใช้เวลาประมาณ 10 วินาทีโดยไม่มีดัชนีและ 7 วินาทีกับดัชนีที่คุณแนะนำในระดับขณะที่ข้อความค้นหาข้ามการสมัครด้านบนใช้เวลาไม่เกิน 3 วินาที (แม้ไม่มีดัชนี)
Lambo Jayapalan

@LamboJayapalan คำถามนี้ดูเหมือนว่าอย่างน้อยควรมีประสิทธิภาพเท่ากับ Bluefeet คุณเพิ่มดัชนีที่แน่นอนนี้ (พร้อมINCLUDE) หรือไม่? นอกจากนี้คุณมีดัชนีUsers (UserType, Points)หรือไม่ (อาจช่วยได้)
ypercubeᵀᴹ

มีผู้ใช้กี่คน (แถวในตารางUsers) และมีความกว้างเท่าใด
ypercubeᵀᴹ

2

เหตุใดจึงไม่ใช้การดำเนินการพื้นฐานเท่านั้นเข้าร่วมภายในกลุ่มตามและสูงสุด:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.