2012-08-17 8 views
5

Powiedzmy mam tabelę zawierającą wiele wiele wierszy tak:TSQL Wybierz Min & Max wiersz podczas grupowania

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 
... 

robię prostą kwerendę tak:

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

Po tym zapytanie jest prowadzony uzyskać wyniki tak:

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

całkiem proste :)

Co muszę teraz zrobić, to wybrać wiersz z minimalnym i maksymalnym zysku w których liczba jest większa niż 10 (jest to parametr)

I był w stanie uzyskać minimalną wartość z tego:

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 

Myślałem o zrobieniu UNION dla powyższego zapytania z ORDER BY DESC, ale nie jest to najlepsze rozwiązanie.

Co muszę zrobić:
Wybierz 2 rzędy: jeden z minimum, drugi z maksymalnym zyskiem AVG, gdy zgrupujesz według zakresu.

EDIT: Jeśli dodać 2 kolumny przejść do mojego głównego tabeli danych tak:

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 
... 

a następnie spróbuj dodać 2 więcej warunków tak:

; 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 Wystąpił błąd, ponieważ [Firma] i [Data zamówienia]

jest niepoprawny w klauzuli HAVING, ponieważ nie jest zawarty w funkcji agregującej ani klauzuli GROUP BY.

Jak mogę to naprawić?

EDIT2 Gotowy!

; 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 mogę wrócić inną kolumnę z opisem jak to:

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

EDIT 4 Szybka odpowiedź (na podstawie @Nikola Markovinović odpowiedź):

; 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

Pamiętaj, aby dodać tag "SQL" do swoich pytań, aby uzyskać więcej uwagi. –

Odpowiedz

7

Można to zrobić na raz za pomocą 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

Jeszcze raz muszę przyznać, że jesteś NIESAMOWITY !!! :) – Misiu

+0

@Misiu Właśnie przeprowadziłem (ograniczone) testy iw moim przypadku (700000 faktur, pogrupowanych według klientów, uśredniających wartość faktury) oba podejścia działają szybko, ale row_number() jest prawie dwa razy szybszy niż union all. –

+0

Testuję to. Wygląda na to, że Twoje rozwiązanie jest o wiele szybsze, grupowanie odbywa się tylko raz. Chciałbym znać SQL tak jak Ty;) – Misiu

2

Oto SQLFiddle example.W ostatnim zapytaniu zapytania można użyć HAVING zamiast zapytania zagnieżdżonego:

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

Dzięki za tak szybką odpowiedź :) Zastanawiam się, czy to będzie szybkie. Jak widać górna i dolna część jest prawie taka sama (zmienia się asc/desc), więc czy można to zrobić? Jedną z opcji, które przychodzą mi do głowy, jest wybranie zgrupowanych danych w tabeli tymczasowej, a następnie wybór wiersza min. I maks. Może to będzie szybsze, poza tym w Moim (i moim) rozwiązaniu musimy pogrupować dane 2 razy. Jeśli mam 100 wierszy to jest ok, ale jeśli będę miał 10KK (10 milionów) to będzie trochę długo. – Misiu