2012-12-21 10 views
35

Il titolo dice tutto, perché non è possibile utilizzare una funzione con finestra in una clausola where in SQL Server?Perché nessuna funzione con finestra in dove clausole?

Questa interrogazione ha perfettamente senso:

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) 

Ma non funziona. C'è un modo migliore di un CTE/sottoquery?

EDIT

Per quello che il suo valore è la query con un 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 per le risposte che mostrano con una sottoquery, ma in realtà io Sto cercando il ragionamento che sta dietro il non poter usare le funzioni di windowing in dove clausole.

+1

Le funzioni di generazione sono parte del livello non relazionale (perché la teoria relazionale non tratta i dati ordinati); quindi vengono valutati dopo ogni altra cosa. –

risposta

40

davvero sto cercando il ragionamento alla base di non essere in grado di utilizzare le funzioni di windowing in cui clausole.

La ragione per cui non sono consentiti nella clausola WHERE è che creerebbe ambiguità. Rubare l'esempio di Itzik Ben Gan da High-Performance T-SQL Using Window Functions (p.25)

Supponiamo che il vostro tavolo era

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

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

E la query

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

Quale sarebbe il risultato giusto? Ti aspetti che il predicato col1 > 'B' sia eseguito prima o dopo la numerazione delle righe?

Il ROW_NUMBER viene valutata al momento della SELECT sul set di risultati che rimane dopo tutte le WHERE/HAVING clausole sono state trattate.

+0

Grazie, mi stava facendo impazzire, non riuscivo a capirlo. – Crisfole

+0

È la stessa ragione per cui non sono consentiti nella clausola ['GROUP BY'] (http://stackoverflow.com/questions/14111321/windowed-functions-can-only-appear-in-the-select-or order-by-clausole) ?? –

+0

@MahmoudGamal La finestra operata nel gruppo dovrebbe essere diversa dalla finestra utilizzata nella selezione poiché si trova sul risultato dopo il raggruppamento e l'avere. Supponiamo che tu possa definirlo come le righe logicamente presenti prima del gruppo, ma sarebbe semplicemente abbastanza confuso da permettere che io pensi. –

9

Non v'è alcuna necessità di CTE, basta usare la funzione di finestre in una sottoquery:

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 

Modifica, spostando il mio commento alla risposta.

Le funzioni di avvolgimento non vengono eseguite finché i dati non vengono effettivamente selezionati, ovvero dopo la clausola WHERE. Pertanto, se si tenta di utilizzare un row_number in una clausola WHERE, il valore non è ancora stato assegnato.

+0

+1 per rispondere alla domanda, ma non proprio quello che stavo cercando ...devo chiedere di nuovo, correttamente questa volta? – Crisfole

+2

@ChristopherPfohl basato sul fatto che il 'row_number' non è assegnato fino a quando i record non sono selezionati in modo da non poterlo avere nella clausola' WHERE' perché il valore non esiste ancora. – Taryn

+0

ahh ... grazie. ha senso, ma è ancora un peccato. – Crisfole

3

Non è necessariamente bisogno di usare un CTE, è possibile interrogare il set di risultati dopo l'utilizzo 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 per rispondere alla domanda, ma non proprio quello che stavo cercando ... devo chiedere di nuovo, correttamente questa volta? – Crisfole

1

Sì, purtroppo, quando si esegue una funzione SQL finestra si arrabbia con voi anche se il vostro in cui il predicato è legittimo. Fai un cte o nested seleziona il valore nella tua select statement, quindi fai riferimento al tuo CTE o seleziona nested con quel valore in seguito. Semplice esempio che dovrebbe essere auto esplicativo. Se davvero si odiano cte per alcuni problemi di prestazioni durante l'esecuzione di un set di dati di grandi dimensioni, è sempre possibile passare alla tabella temporanea o alla variabile di tabella.

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

Infine, c'è il pre-SQL vecchia maniera Server 2005, con una subquery correlata:

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 
) 

io ti do questo per completezza, semplicemente.

2

Prima di tutto è una cosa chiamata all-at-once operation

"All-at-Once Operations" significa che tutte le espressioni della stessa fase processo di query logica vengono valutate logicamente allo stesso tempo.

E grande capitolo impatto sulle funzioni della finestra:

Supponiamo di avere:

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 operazioni ci dicono queste due condizioni valutate logicamente allo stesso punto del tempo. Pertanto, SQL Server può valutare le condizioni nella clausola WHERE in ordine arbitrario, in base al piano di esecuzione stimato . Quindi la domanda principale qui è quale condizione valuta per prima.

Caso 1:

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

Risultato: 1002

Caso 2:

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

Risultato: vuoto

Quindi abbiamo un paradosso.

Questo esempio mostra perché non è possibile utilizzare le funzioni di finestra nella clausola WHERE. Puoi pensare di più a questo e trovare il motivo per cui le funzioni di finestra sono consentite per essere utilizzato solo in SELECT e clausole ORDINE BY!

Problemi correlati