जुड़ने पर अधिकतम मूल्य का चयन करने की क्वेरी


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

शीघ्र जवाब देने के लिए ध्न्यवाद। आपकी क्वेरी मुझे वह सटीक परिणाम देती है जिसकी मुझे आवश्यकता है, लेकिन यह "CROSS APPLY" का उपयोग करने पर ब्लूफ़ेट के उत्तर की तुलना में थोड़ा धीमा लगता है। मेरे विशिष्ट डेटासेट के लिए, आपके CTE को इंडेक्स के बिना लगभग 10 सेकंड और लेवल पर आपके द्वारा सुझाए गए इंडेक्स के साथ 7 सेकंड का समय लगता है, जबकि उपरोक्त क्रॉस
अप्लाई

@LamboJayapalan यह प्रश्न ऐसा लगता है कि इसे कम से कम ब्लूफेट के रूप में कुशल होना चाहिए। क्या आपने यह सटीक इंडेक्स (के साथ INCLUDE) जोड़ा था ? इसके अलावा, क्या आपके पास एक सूचकांक है Users (UserType, Points)? (यह मदद कर सकता है)
ypercubeᵀᴹ

और कितने उपयोगकर्ता (तालिका में पंक्तियाँ Users) हैं और वह तालिका कितनी चौड़ी है?
ypercube y

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-as एक प्रदर्शन समस्या का उपयोग कर सकते हैं, जिसका उपयोग आप इस तरह से भी कर सकते 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 फिडेल डेमो

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.