2012-12-21 9 views
35

Tytuł mówi wszystko, dlaczego nie mogę użyć funkcji okienkowej w klauzuli where w SQL Server?Dlaczego żadne funkcje okienkowe w klauzulach where?

Zapytanie to ma sens:

select id, sales_person_id, product_type, product_id, sale_amount 
from Sales_Log 
where 1 = row_number() over(partition by sales_person_id, product_type, product_id order by sale_amount desc) 

Ale to nie działa. Czy istnieje lepszy sposób niż CTE/Podzapytanie?

EDIT

Na co warto to zapytanie z CTE:

with Best_Sales as (
    select id, sales_person_id, product_type, product_id, sale_amount, row_number() over (partition by sales_person_id, product_type, product_id order by sales_amount desc) rank 
    from Sales_log 
) 
select id, sales_person_id, product_type, product_id, sale_amount 
from Best_Sales 
where rank = 1 

EDIT

+1 za odpowiedzi pokazujących z podzapytania, ale naprawdę ja Szukam przyczyny, dla której nie można używać funkcji okienkowych w klauzulach, gdzie.

+1

Funkcje okien są częścią warstwy nierelacyjnej (ponieważ teoria relacyjna nie zajmuje się uporządkowanymi danymi); w ten sposób są one oceniane po wszystkim. –

Odpowiedz

40

Naprawdę szukam uzasadnienia, że ​​nie mogę korzystać z funkcji okienkowania w klauzulach where .

Powodem, dla którego nie są one dozwolone w klauzuli WHERE jest to, że tworzy niejednoznaczność. Kradnąc Itzik Przykład Ben Gana z High-Performance T-SQL Using Window Functions (str.25)

Załóżmy, że stół był

CREATE TABLE T1 
(
col1 CHAR(1) PRIMARY KEY 
) 

INSERT INTO T1 VALUES('A'),('B'),('C'),('D'),('E'),('F') 

And zapytanie

SELECT col1 
FROM T1 
WHERE ROW_NUMBER() OVER (ORDER BY col1) <= 3 
AND col1 > 'B' 

Jaki byłby właściwą wynik? Czy spodziewałbyś się, że predykat col1 > 'B' został uruchomiony przed lub po numeracji wierszy?

ROW_NUMBER jest oceniana w czasie SELECT na zbiorze wyników pozostałej po wszystkie klauzule WHERE/HAVING zostały omówione.

+0

Dzięki, doprowadził mnie do szału, nie mogłem tego rozgryźć. – Crisfole

+0

Czy jest to ten sam powód, dla którego nie są one dozwolone w klauzuli "GROUP BY" (http://stackoverflow.com/questions/14111321/windowed-functions-can-only-appear-in-the-select-or -kolejne klauzule)? –

+0

@MahmoudGamal Okno obsługiwane w grupie przez musi być inne niż okno użyte w selekcji, tak jak w przypadku wyniku po grupowaniu i posiadaniu. Załóżmy, że możesz zdefiniować je jako wiersze logicznie obecne przed grupą, ale Just byłoby dość mylące, aby pozwolić, że myślę. –

9

Nie ma potrzeby CTE, wystarczy użyć funkcji okienkowy w podkwerendzie:

select id, sales_person_id, product_type, product_id, sale_amount 
from 
(
    select id, sales_person_id, product_type, product_id, sale_amount, 
    row_number() over(partition by sales_person_id, product_type, product_id order by sale_amount desc) rn 
    from Sales_Log 
) sl 
where rn = 1 

Edycja, przenoszenie mój komentarz do odpowiedzi.

Funkcje okienkowania nie są wykonywane, dopóki nie zostaną faktycznie wybrane dane po klauzuli WHERE. Więc jeśli spróbujesz użyć klauzuli row_number w klauzuli WHERE, wartość ta nie jest jeszcze przypisana.

+0

+1 za udzielenie odpowiedzi na pytanie, ale nie do końca to, czego szukałem ...Czy powinienem zapytać ponownie, tym razem poprawnie? – Crisfole

+2

@ChristopherPfohl w oparciu o moje rozumienie 'numer_wiersza' nie jest przypisany, dopóki nie zostaną wybrane rekordy, więc nie możesz tego mieć w klauzuli" WHERE ", ponieważ ta wartość jeszcze nie istnieje. – Taryn

+0

ahh ... dzięki. ma sens, ale wciąż szkoda. – Crisfole

3

Nie koniecznie trzeba użyć CTE, można wyszukać zestaw wyników po użyciu row_number()

select row, id, sales_person_id, product_type, product_id, sale_amount 
from (
    select 
     row_number() over(partition by sales_person_id, 
      product_type, product_id order by sale_amount desc) AS row, 
     id, sales_person_id, product_type, product_id, sale_amount 
    from Sales_Log 
    ) a 
where row = 1 
+0

+1 za udzielenie odpowiedzi na pytanie, ale nie do końca to, czego szukałem ... czy powinienem zapytać ponownie, tym razem poprawnie? – Crisfole

1

Tak niestety kiedy zrobić okienkiem funkcja SQL wścieka się na ciebie, nawet jeśli gdzie predykat jest zgodny z prawem. Tworzysz cte lub zagnieżdżony select mający wartość w swoim poleceniu select, a następnie odwołujesz się do CTE lub nested select z tą wartością później. Prosty przykład, który powinien być oczywisty. Jeśli naprawdę Nienawidzisz cte dla jakiegoś problemu z wydajnością podczas robienia dużego zestawu danych, zawsze możesz przejść do tabeli temp lub zmiennej tabeli.

declare @Person table (PersonID int identity, PersonName varchar(8)); 

insert into @Person values ('Brett'),('John'); 

declare @Orders table (OrderID int identity, PersonID int, OrderName varchar(8)); 

insert into @Orders values (1, 'Hat'),(1,'Shirt'),(1, 'Shoes'),(2,'Shirt'),(2, 'Shoes'); 

--Select 
-- p.PersonName 
--, o.OrderName 
--, row_number() over(partition by o.PersonID order by o.OrderID) 
--from @Person p 
-- join @Orders o on p.PersonID = o.PersonID 
--where row_number() over(partition by o.PersonID order by o.orderID) = 2 

-- yields: 
--Msg 4108, Level 15, State 1, Line 15 
--Windowed functions can only appear in the SELECT or ORDER BY clauses. 
; 

with a as 
    (
    Select 
    p.PersonName 
, o.OrderName 
, row_number() over(partition by o.PersonID order by o.OrderID) as rnk 
from @Person p 
    join @Orders o on p.PersonID = o.PersonID 
    ) 
select * 
from a 
where rnk >= 2 -- only orders after the first one. 
1

Wreszcie, nie jest staromodny, pre-SQL Server 2005 sposób, z podzapytanie skorelowane:

select * 
from Sales_Log sl 
where sl.id = (
    Select Top 1 id 
    from Sales_Log sl2 
    where sales_person_id = sl.sales_person_id 
     and product_type = sl.product_type 
     and product_id = sl.product_id 
    order by sale_amount desc 
) 

daję wam to kompletna, po prostu.

2

Przede wszystkim jest coś, co nazywa all-at-once operation

"All-at-Once Operacji" oznacza, że ​​wszystkie wyrażenia w tym samym logicznego fazie procesu zapytania oceniane są logicznie w tym samym czasie.

I wielki rozdział Wpływ na funkcje okna:

Załóżmy masz:

CREATE TABLE #Test (Id INT) ; 

INSERT INTO #Test VALUES (1001), (1002) ; 

SELECT Id 
FROM #Test 
WHERE Id = 1002 
    AND ROW_NUMBER() OVER(ORDER BY Id) = 1; 

All-at-Once operacje powiedzieć nam te dwa warunki oceniane logicznie na to samo Punkt czasu. Dlatego SQL Server może ocenić warunki w klauzuli WHERE w dowolnej kolejności, w oparciu o szacowany plan wykonania . Tak więc głównym pytaniem jest, który warunek jest oceniany jako pierwszy.

Przypadek 1:

If (Id = 1002) is first, then if (ROW_NUMBER() OVER(ORDER BY Id) = 1)

Rezultat: 1002

Przypadek 2:

If (ROW_NUMBER() OVER(ORDER BY Id) = 1), then check if (Id = 1002)

Wynik: pusty

Mamy więc paradoks.

Ten przykład pokazuje, dlaczego nie możemy używać klauzul "Okno" w klauzuli WHERE. Możesz więcej o tym myśleć i dowiedzieć się, dlaczego funkcje okienne to , które mogą być używane tylko w klauzulach WYBIERZ i ZAMÓW PRZEZ!

Powiązane problemy