2012-08-17 13 views
5

Diciamo che ho una tabella che contiene molte molte righe come questo:TSQL Selezionare Min & Max riga quando il raggruppamento

ID  Range   Range_begining  Profit 
---------------------------------------------------- 
1 (100-150)     100   -20 
2 (200-250)     200   40.2 
3 (100-150)     100   100 
4 (450-500)     450   -90 
... 

sto facendo una semplice query come questa:

SELECT max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    FROM 
     Orders 
    GROUP BY 
     Range_begining 

Dopo questo query viene eseguita ottengo risultati come questo:

Range  Count  AVG Profit 
------------------------------------ 
(100-150)  2    40 
(200-250)  1    40.2 
(450-500)  1    -90 
... 

abbastanza semplice :)

012.351.

Che cosa devo fare ora è quello di selezionare fila con minimo e massimo profitto dove conteggio è più grande di 10 (questo è un parametro)

sono stato in grado di ottenere il valore minimo con questo:

SELECT TOP 1 [Range], [AVG Profit] FROM (
    SELECT max([Range]) AS 'Range' 
     , count(ID) AS 'Count' 
     , round(avg([Profit]), 2) AS 'AVG Profit' 
     FROM 
      Orders 
     GROUP BY 
      Range_begining) X 
WHERE 
    [Count]>10 
ORDER BY 
    [AVG Profit] ASC --or DESC if I want max profit 

Stavo pensando di fare un UNION per la query precedente con ORDER BY DESC, ma non è la soluzione migliore.

Cosa devo fare:
Select 2 file: uno con un minimo, secondo con la massima AVG profitto quando il raggruppamento per Range.

EDIT: Se aggiungo 2 colonne mossa alla mia tabella dati principale in questo modo:

ID  Range   Range_begining  Profit  OrderDate  Company 
--------------------------------------------------------------------------------- 
1 (100-150)     100   -20  2012-01-02   1 
2 (200-250)     200   40.2  2012-03-22   0 
3 (100-150)     100   100  2012-02-05   0 
4 (450-500)     450   -90  2012-05-12   1 
... 

E poi cercare di aggiungere ulteriori 2 condizioni come questo:

; with ordering as (
    SELECT max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    , row_number() over (order by avg([Profit])) rn_min 
    , row_number() over (order by avg([Profit]) desc) rn_max 
    FROM 
     Orders 
    GROUP BY 
     Range_begining 
    HAVING COUNT(ID) > 10 
    AND [Company][email protected] 
    AND (@from= '' OR [OrderDate]>[email protected]) 
    AND (@to= '' OR [OrderDate]<[email protected]) 
) 
select [range], [count], [avg profit] 
    from ordering 
where (rn_max = 1 or rn_min = 1) 

I ottenere un errore perché [Azienda] e [Data ordine]

non è valido nella clausola HAVING poiché non è contenuto in una funzione di aggregazione o nella clausola GROUP BY.

Come posso risolvere questo problema?

EDIT2 Funzionante!

; with ordering as (
    SELECT max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    , row_number() over (order by avg([Profit])) rn_min 
    , row_number() over (order by avg([Profit]) desc) rn_max 
    FROM 
     Orders 
    WHERE 
    [Company][email protected] 
    AND (@from= '' OR [OrderDate]>[email protected]) 
    AND (@to= '' OR [OrderDate]<[email protected]) 
    GROUP BY 
     Range_begining 
    HAVING COUNT(ID) > 10 
) 
select [range], [count], [avg profit] 
    from ordering 
where (rn_max = 1 or rn_min = 1) 

EDIT 3 Posso tornare un'altra colonna con la descrizione in questo modo:

Range  AVG Profit    Description 
------------------------------------------------- 
(200-250)   40.2   Max profit here 
(450-500)   -90  Min profit, well done 

EDIT 4 Risposte rapide (sulla base di @Nikola Markovinović risposta):

; with ordering as (
    SELECT max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    , row_number() over (order by avg([Profit])) rn_min 
    , row_number() over (order by avg([Profit]) desc) rn_max 
    FROM 
     Orders 
    WHERE 
    [Company][email protected] 
    AND (@from= '' OR [OrderDate]>[email protected]) 
    AND (@to= '' OR [OrderDate]<[email protected]) 
    GROUP BY 
     Range_begining 
    HAVING COUNT(ID) > 10 
) 
    SELECT 
    CASE WHEN rn_max=1 THEN 'This is max' ELSE 'Min' END AS 'Description' 
    ,[range] 
    ,[count] 
    ,[avg profit] 
    FROM ordering 
    WHERE (rn_max = 1 or rn_min = 1) 
+2

Assicuratevi di aggiungere il tag 'SQL' alle vostre domande per ottenere più attenzione. –

risposta

7

Si potrebbe farlo in una volta usando window functions:

; with ordering as (
    SELECT max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    , row_number() over (order by avg([Profit])) rn_min 
    , row_number() over (order by avg([Profit]) desc) rn_max 
    FROM 
     Orders 
    GROUP BY 
     Range_begining 
    HAVING COUNT(ID) > 10 
) 
select [range], [count], [avg profit], 
     case when rn_max = 1 
      then 'Max profit' 
      else 'Min profit' 
     end Description 
    from ordering 
where (rn_max = 1 or rn_min = 1) 

And here is Sql Fiddle example.

+1

Ancora una volta devo ammettere che sei eccezionale !!! :) – Misiu

+0

@Misiu Ho appena eseguito alcuni test (limitati) e nel mio caso (700000 fatture, raggruppate per cliente, valore medio della fattura) entrambi gli approcci funzionano velocemente, ma row_number() è quasi due volte più veloce di tutti. –

+0

Lo sto testando. Sembra che la tua soluzione sia molto più veloce, il raggruppamento è fatto solo una volta. Mi piacerebbe conoscere SQL come fai tu;) – Misiu

2

Ecco uno SQLFiddle example.Nella vostra ultima query domanda è possibile utilizzare VISTA anziché la query nidificate:

select * from 
(SELECT TOP 1 
    max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    FROM 
     Orders 
    GROUP BY 
     Range_begining 
    having count(id)>10 
    order by round(avg([Profit]), 2) ASC 
) a 
union all 
select * from 
(
SELECT TOP 1 
    max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    FROM 
     Orders 
    GROUP BY 
     Range_begining 
    having count(id)>10 
    order by round(avg([Profit]), 2) desc 
)b 
+1

Grazie per una risposta così veloce :) Mi sto chiedendo che sarà veloce. Come puoi vedere la parte superiore e inferiore è quasi la stessa (asc/desc sta cambiando) quindi può essere fatto? Un'opzione che mi viene in mente è quella di selezionare i dati raggruppati nella tabella temporanea e quindi selezionare la riga min e max. Forse questo sarà più veloce, inoltre in You (e mia) soluzione dobbiamo raggruppare i dati 2 volte. Se ho 100 righe, va bene, ma se avrò 10KK (10 milioni) questo sarà un po 'lungo. – Misiu

Problemi correlati