Birleştirmede maksimum değeri seçme sorgusu


13


Kullanıcılar tablosu var:

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

ve Seviyeler

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

Ve her kullanıcı için seviye almak için bir sorgu arıyorum. Çizgileri boyunca bir şey:

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

Sonuçlar şöyle olacak:

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

Herkes bunu imleç başvurmadan nasıl yapabilirim hakkında herhangi bir fikir veya öneriniz var mı?

Yanıtlar:


15

Mevcut sorgunuz kullanabileceğiniz bir şeye yakın, ancak birkaç değişiklik yaparak sonucu kolayca elde edebilirsiniz. APPLYİşleci kullanmak ve sorgulamak için sorgunuzu değiştirerek CROSS APPLY. Bu, gereksinimlerinizi karşılayan satırı döndürür. İşte kullanabileceğiniz bir sürüm:

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;

İşte bir demo ile bir SQL Fiddle . Bu bir sonuç üretir:

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

3

Aşağıdaki çözüm, Levelstabloyu bir kez tarayan ortak bir tablo ifadesi kullanır . Bu taramada, LEAD()pencere fonksiyonu kullanılarak "sonraki" puan seviyesi bulunur , böylece MinPoints(satırdan) ve MaxPoints( MinPointsakım için bir sonraki UserType) vardır.

Bundan sonra, ortak tablo ifadesini lvls, açık UserTypeve MinPoints/ MaxPointsaralık gibi birleştirebilirsiniz:

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;

Pencere işlevini kullanmanın avantajı, her türlü özyinelemeli çözümü ortadan kaldırmanız ve performansı önemli ölçüde artırmanızdır. En iyi performans için, Levelstabloda aşağıdaki dizini kullanırsınız :

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

Hızlı cevabınız için teşekkür ederim. Sorgunuz bana tam olarak ihtiyacım olan sonucu veriyor, ancak "CROSS APPLY" kullanarak yukarıdaki bluefeet'in cevabından biraz daha yavaş görünüyor. Özel veri
kümem için, CTE'nizi

@LamboJayapalan Bu sorgu en azından bluefeet kadar etkili olmalı. Bu tam dizini (ile INCLUDE) eklediniz mi ? Ayrıca, üzerinde bir endeksiniz var Users (UserType, Points)mı? (yardımcı olabilir)
ypercubeᵀᴹ

Ve kaç kullanıcı (tablodaki satırlar Users) var ve bu tablo ne kadar geniş?
ypercubeᵀᴹ

2

Neden sadece temel işlemleri, INNER JOIN, GROUP BY ve MAX kullanarak yapmıyorsunuz:

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

Ben-bunun yerine kullanabilirsiniz bir INNER JOINperformans sorunu olarak-kullanabilirsiniz gibi işlev LEFT JOINile düşünüyorum 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 Keman Demosu

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.