2013-01-18 12 views
6

Ho una sfida SQL con la quale ho bisogno di un piccolo aiuto.SQL: come raggruppare per ID e identificare la colonna con il valore più alto?

Di seguito è riportato un esempio semplificato, nel mio caso reale ho circa 500k righe in una VIEW lenta. Quindi, se hai una soluzione efficace, la apprezzerei. Sto pensando di usare GROUP BY in un modo o nell'altro, ma non ne sono sicuro.

Diciamo che ho una tabella come questa

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

Quindi quello che voglio fare è:

In ogni ordine, dove ci sono sia il cibo e la linea di ordinazione bevande, voglio che la più alta delle bevande prezzo

Quindi in questo esempio mi piacerebbe avere un set di risultati di questo:

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

Come posso ottenerlo in modo efficace?

+0

. . Perché tu dici che la vista è costosa, penso che avresti dovuto scegliere la soluzione di Bogdan. Non ho mai fatto un commento come questo prima, ma sottolineo la lentezza della vista e quella soluzione è l'unica che scansiona la vista solo una volta. –

+0

Sì, questa è una buona o forse una risposta migliore. L'avevo già scelto e implementato prima di passare per Bogdan. Il mio ultimo criterio era comunque quello di risolvere questo, quindi sono andato con la prima e migliore risposta. Ma capisco la tua opinione. – Rupal

risposta

2

Dato che hai taggato SQL Server, utilizza Common Table Expression e 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

e così questo restituirà il record OrderID = 3:) – WKordos

+0

oh, è stato modificato:) – WKordos

+0

Funziona bene! – Rupal

1

se si sta utilizzando Sql-Server 2005 o superiore è possibile utilizzare un CTE con DENSE_RANK funzione:

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 

Usa DENSE_RANK se si desidera che tutti gli ordini con lo stesso prezzo più alto e ROW_NUMBER se ne vuoi uno.

DEMO

3

È possibile utilizzare l'una sottoquery che ottiene il max(price) per ogni ordine sia con cibo e bevande e poi join che di nuovo al vostro tavolo per ottenere il risultato:

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 

Vedi SQL Fiddle with Demo

Il risultato è:

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

Questa è una divisione relazionale: link 1, link 2.

Se la tabella divisore (solo cibo e bevande) è statico allora si potrebbe utilizzare una delle seguenti soluzioni:

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 

Risultati (per Soluzione 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. . . Your è l'unica soluzione che analizza i dati originali solo una volta. Dato che la domanda dice che la fonte è una visione lenta, non so perché questa non viene scelta come risposta accettata. –

+0

@GordonLinoff: la seconda soluzione (PIVOT, 1 scansione) è stata proposta da [Razvan Socol] (http://ro.linkedin.com/in/razvansocol) (precedente MVP di SQL Server). Ho usato questa soluzione per una procedura di produzione memorizzata. –

+0

Razvan ha fornito una risposta a questa domanda? Una variante della seconda soluzione è come avrei risposto alla domanda. Tutte le altre risposte che vedo qui risulterebbero in più scansioni della vista. –

Problemi correlati