2013-01-18 11 views
6

Mam wyzwanie SQL, z którym potrzebuję pomocy.SQL - jak grupować według identyfikatora i identyfikować kolumnę o największej wartości?

Poniżej znajduje się uproszczony przykład, w moim prawdziwym przypadku mam około 500 tys. Wierszy w wolnym WIDOKU. Więc jeśli masz rozwiązanie, które również jest skuteczne, byłbym wdzięczny. Myślę, że muszę używać GROUP BY w taki czy inny sposób, ale nie jestem pewien.

Powiedzmy mam tabeli jak ten

╔═════════╦══════════╦══════════╦═══════╗ 
║ ORDERID ║ NAME ║ TYPE ║ PRICE ║ 
╠═════════╬══════════╬══════════╬═══════╣ 
║  1 ║ Broccoli ║ Food  ║ 1  ║ 
║  1 ║ Beer  ║ Beverage ║ 5  ║ 
║  1 ║ Coke  ║ Beverage ║ 2  ║ 
║  2 ║ Beef  ║ Food  ║ 2.5 ║ 
║  2 ║ Juice ║ Beverage ║ 1.5 ║ 
║  3 ║ Beer  ║ Beverage ║ 5  ║ 
║  4 ║ Tomato ║ Food  ║ 1  ║ 
║  4 ║ Apple ║ Food  ║ 1  ║ 
║  4 ║ Broccoli ║ Food  ║ 1  ║ 
╚═════════╩══════════╩══════════╩═══════╝ 

Więc to, co chcę zrobić, to:

W każdym zamówieniu, gdzie istnieją zarówno jedzenie i linia zamówienia napojów, chcę najwyższy napoju cena

Więc w tym przykładzie chciałbym mieć zestaw Wynikiem tego:

╔═════════╦═══════╦═══════╗ 
║ ORDERID ║ NAME ║ PRICE ║ 
╠═════════╬═══════╬═══════╣ 
║  1 ║ Beer ║ 5  ║ 
║  2 ║ Juice ║ 1.5 ║ 
╚═════════╩═══════╩═══════╝ 

Jak mogę to osiągnąć w skuteczny sposób?

+0

. . Ponieważ mówisz, że widok jest drogi, myślę, że powinieneś wybrać rozwiązanie Bogdana. Nigdy wcześniej nie robiłem takiego komentarza, ale podkreślasz powolność widoku i to jedyne rozwiązanie, które skanuje widok tylko raz. –

+0

Tak, to dobra, a może i lepsza odpowiedź. Wybrałem i wprowadziłem to, zanim przeszedłem przez Bogdana. Moim ostatecznym kryterium było jednak rozwiązanie tego problemu, więc poszedłem z pierwszą i najlepszą odpowiedzią. Ale rozumiem twoją opinię. – Rupal

Odpowiedz

2

Po oznaczeniu SQL Server skorzystaj z Common Table Expression i Window Functions.

;WITH filteredList 
AS 
(
    SELECT OrderID 
    FROM tableName 
    WHERE Type IN ('Food','Beverage') 
    GROUP BY OrderID 
    HAVING COUNT(DISTINCT Type) = 2 
), 
greatestList 
AS 
(
    SELECT a.OrderID, a.Name, a.Type, a.Price, 
      DENSE_RANK() OVER (PARTITION BY a.OrderID 
           ORDER BY a.Price DESC) rn 
    FROM tableName a 
      INNER JOIN filteredList b 
       ON a.OrderID = b.OrderID 
    WHERE a.Type = 'Beverage' 
) 
SELECT OrderID, Name, Type, Price 
FROM greatestList 
WHERE rn = 1 
+0

, a więc ten zwróci rekord OrderID = 3:) – WKordos

+0

oh, zmieniło się:) – WKordos

+0

To działa dobrze! – Rupal

1

Jeśli używasz SQL Server 2005 lub nowszy można użyć CTE z DENSE_RANK funkcja:

WITH CTE 
    AS (SELECT orderid, 
       name, 
       type, 
       price, 
       RN = Dense_rank() 
         OVER ( 
         PARTITION BY orderid 
         ORDER BY CASE WHEN type='Beverage' THEN 0 ELSE 1 END ASC 
         , price DESC) 
     FROM dbo.tablename t 
     WHERE EXISTS(SELECT 1 
         FROM dbo.tablename t2 
         WHERE t2.orderid = t.orderid 
           AND type = 'Food') 
     AND EXISTS(SELECT 1 
         FROM dbo.tablename t2 
         WHERE t2.orderid = t.orderid 
           AND type = 'Beverage')) 
SELECT orderid, 
     name, 
     price 
FROM CTE 
WHERE rn = 1 

Zastosowanie DENSE_RANK jeśli chcesz wszystkie zamówienia z tą samą najwyższą ceną i ROW_NUMBER, jeśli chcesz.

DEMO

3

Można użyć podzapytanie że dostaje max(price) dla każdego zamówienia zarówno z żywności i napojów, a następnie przystąpić że z powrotem do stolika, aby uzyskać wynik:

select t1.orderid, 
    t1.name, 
    t1.price 
from yourtable t1 
inner join 
(
    select max(price) MaxPrice, orderid 
    from yourtable t 
    where type = 'Beverage' 
    and exists (select orderid 
       from yourtable o 
       where type in ('Food', 'Beverage') 
        and t.orderid = o.orderid 
       group by orderid 
       having count(distinct type) = 2) 
    group by orderid 
) t2 
    on t1.orderid = t2.orderid 
    and t1.price = t2.MaxPrice 

Zobacz SQL Fiddle with Demo

wynikiem jest:

| ORDERID | NAME | PRICE | 
--------------------------- 
|  1 | Beer |  5 | 
|  2 | Juice | 1.5 | 
2

To jest podział relacyjny: link 1, link 2.

Jeśli tabela dzielnik (tylko jedzenie i napój) jest statyczny następnie można użyć jednego z następujących rozwiązań:

DECLARE @OrderDetail TABLE 
    ([OrderID] int, [Name] varchar(8), [Type] varchar(8), [Price] decimal(10,2)) 
; 

INSERT INTO @OrderDetail 
    ([OrderID], [Name], [Type], [Price]) 
SELECT 1, 'Broccoli', 'Food', 1.0 
UNION ALL SELECT 1, 'Beer', 'Beverage', 5.0 
UNION ALL SELECT 1, 'Coke', 'Beverage', 2.0 
UNION ALL SELECT 2, 'Beef', 'Food', 2.5 
UNION ALL SELECT 2, 'Juice', 'Beverage', 1.5 
UNION ALL SELECT 3, 'Beer', 'Beverage', 5.0 
UNION ALL SELECT 4, 'Tomato', 'Food', 1.0 
UNION ALL SELECT 4, 'Apple', 'Food', 1.0 
UNION ALL SELECT 4, 'Broccoli', 'Food', 1.0 

-- Solution 1 
SELECT od.OrderID, 
     COUNT(DISTINCT od.Type) AS DistinctTypeCount, 
     MAX(CASE WHEN od.Type='beverage' THEn od.Price END) AS MaxBeveragePrice 
FROM @OrderDetail od 
WHERE od.Type IN ('food', 'beverage') 
GROUP BY od.OrderID 
HAVING COUNT(DISTINCT od.Type) = 2 -- 'food' & 'beverage' 

-- Solution 2: better performance 
SELECT pvt.OrderID, 
     pvt.food AS MaxFoodPrice, 
     pvt.beverage AS MaxBeveragePrice 
FROM (
    SELECT od.OrderID, od.Type, od.Price 
    FROM @OrderDetail od 
    WHERE od.Type IN ('food', 'beverage') 
) src 
PIVOT (MAX(src.Price) FOR src.Type IN ([food], [beverage])) pvt 
WHERE pvt.food IS NOT NULL 
AND  pvt.beverage IS NOT NULL 

Wyniki (dla roztworu 1 & 2):

OrderID  DistinctTypeCount MaxBeveragePrice 
----------- ----------------- --------------------------------------- 
1   2     5.00 
2   2     1.50 

Table 'Worktable'. Scan count 2, logical reads 23, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
Table '#09DE7BCC'. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

OrderID  MaxFoodPrice       MaxBeveragePrice 
----------- --------------------------------------- --------------------------------------- 
1   1.00         5.00 
2   2.50         1.50 

Table '#09DE7BCC'. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
+0

+1. . .Twoja jest jedynym rozwiązaniem, które skanuje oryginalne dane tylko raz. Biorąc pod uwagę, że pytanie mówi, że źródło jest powolnym spojrzeniem, nie wiem, dlaczego nie zostało wybrane jako zaakceptowana odpowiedź. –

+0

@GordonLinoff: Drugie rozwiązanie (PIVOT, 1 skan) zostało zaproponowane przez [Razvan Socol] (http://ro.linkedin.com/in/razvansocol) (dawny SQL Server MVP). Użyłem tego rozwiązania do produkcji przechowywanej procedury. –

+0

Czy Razvan dostarczył odpowiedź na to pytanie? Wariantem drugiego rozwiązania jest odpowiedź na pytanie. Wszystkie pozostałe odpowiedzi, które tu widzę, mogą spowodować wielokrotne skanowanie widoku. –

Powiązane problemy