2013-01-18 4 views
6

Ich habe eine SQL-Herausforderung, mit der ich ein wenig Hilfe brauche.SQL - Wie gruppiere ich nach ID und identifiziere die Spalte mit dem höchsten Wert?

Unten ist ein vereinfachtes Beispiel, in meinem realen Fall habe ich etwa 500k Zeilen in einer langsamen Ansicht. Also, wenn Sie eine Lösung haben, die auch effektiv ist, würde ich es begrüßen. Ich denke, ich muss GROUP BY auf die eine oder andere Weise verwenden, aber ich bin mir nicht sicher.

Sagen wir, ich habe eine Tabelle wie diese

╔═════════╦══════════╦══════════╦═══════╗ 
║ 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  ║ 
╚═════════╩══════════╩══════════╩═══════╝ 

Also, was ich tun möchte, ist:

In jeder Ordnung, wo es sowohl Essen und Getränke bestellen Linie sind, möchte ich die höchste Getränke Preis

So in diesem Beispiel Ich mag würde eine Ergebnismenge davon haben:

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

Wie kann ich das effektiv erreichen?

+0

. . Weil Sie sagen, dass die Aussicht teuer ist, sollten Sie Bogdans Lösung gewählt haben. Ich habe noch nie einen solchen Kommentar gemacht, aber Sie betonen die Langsamkeit der Ansicht und diese Lösung ist die einzige, die die Ansicht nur einmal scannt. –

+0

Ja, das ist eine gute oder vielleicht auch bessere Antwort. Ich hatte das schon ausgewählt und umgesetzt, bevor ich Bogdan's durchging. Mein ultimatives Kriterium war jedoch, das zu lösen, also ging ich mit der ersten und besten Antwort. Aber ich verstehe deine Meinung. – Rupal

Antwort

2

Da Sie SQL Server markiert haben, verwenden Sie Common Table Expression und 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

und so wird dieser record OrderID = 3 zurückgeben:) – WKordos

+0

oh, es hat sich geändert:) – WKordos

+0

Das funktioniert gut! – Rupal

1

Wenn Sie mit SQL-Server 2005 oder höher können Sie ein verwenden CTE mit DENSE_RANK Funktion:

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 

Verwenden DENSE_RANK, wenn alle Aufträge wollen mit dem gleichen höchsten Preis und ROW_NUMBER, wenn Sie eins wollen.

DEMO

3

können Sie verwenden, die eine Unterabfrage, die die max(price) für jede Bestellung sowohl mit Speisen und Getränken wird und dann die auf den Tisch kommen zurück, um die Ergebnisse zu erhalten:

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 

SQL Fiddle with Demo Siehe

Das Ergebnis ist:

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

Dies ist relationale Teilung: link 1, link 2.

Wenn der Divisor-Tabelle (nur Lebensmittel und Getränk) statisch ist, dann können Sie eine dieser Lösungen verwenden:

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 

Ergebnisse (für Lösung 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. . .Yours ist die einzige Lösung, die die Originaldaten nur einmal scannt. Da die Frage besagt, dass die Quelle langsam ist, weiß ich nicht, warum dies nicht als akzeptierte Antwort gewählt wurde. –

+0

@GordonLinoff: Die zweite Lösung (PIVOT, 1 Scan) wurde von [Razvan Socol] (http://ro.linkedin.com/in/razvansocol) (früher SQL Server MVP) vorgeschlagen. Ich habe diese Lösung für eine gespeicherte Produktionsprozedur verwendet. –

+0

Hat Razvan eine Antwort auf diese Frage gegeben? Eine Variante der zweiten Lösung ist, wie ich die Frage beantwortet hätte. Alle anderen Antworten, die ich hier sehe, würden zu mehreren Scans der Ansicht führen. –

Verwandte Themen