2012-12-21 60 views
35

標題說這一切,爲什麼我不能在SQL Server的where子句中使用窗口函數?爲什麼在where子句中沒有窗口函數?

這個查詢非常有道理:

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) 

但它不工作。有沒有比CTE /子查詢更好的方法?

編輯

爲了什麼它的價值,這是一個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 

編輯

+1與子查詢顯示的答案,但我真的我正在尋找不能在where子句中使用窗口函數的原因。

+1

窗口化函數是非關係層的一部分(因爲關係理論不處理有序數據);因此他們在一切之後被評估。 –

回答

40

真的我在尋找不能在where子句中使用 窗口函數的原因。

WHERE條款中不允許使用它們的原因是它會產生歧義。偷Itzik本甘的例子從High-Performance T-SQL Using Window Functions(p。25)

假設你的表是

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

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

,且查詢

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

什麼是正確的結果?你會期望在行號之前或之後運行col1 > 'B'謂詞嗎?

ROW_NUMBER在對所有WHERE/HAVING子句進行處理後的剩餘結果集的SELECT時進行評估。

+0

謝謝,這讓我瘋狂,我無法弄清楚。 – Crisfole

+0

這是否與[GROUP BY]子句中不允許的原因相同?(http://stackoverflow.com/questions/14111321/windowed-functions-can-only-appear-in-the-select-or階逐條款)?? –

+0

@MahmoudGamal在group by中操作的窗口將需要與select中使用的窗口不同,因爲這是在分組和擁有後的結果。假設你可以將它定義爲在組之前邏輯地存在的行,但是如果允許我這麼想,Just會相當混亂。 –

9

沒有必要CTE,只需使用窗口函數在子查詢:

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 

編輯,我的評論移動到答案。直到該數據被實際選擇其是WHERE子句後不進行

窗口化功能。因此,如果您嘗試在WHERE子句中使用row_number,則該值尚未分配。

+0

+1回答這個問題,但不是我正在尋找的...我應該再次問,這次是否正確? – Crisfole

+2

@ChristopherPfohl根據我的理解,'row_number'不會被分配,直到選擇記錄,所以你不能在'WHERE'子句中擁有它,因爲這個值還不存在。 – Taryn

+0

啊......謝謝。是有道理的,但它仍然是一個遺憾。 – Crisfole

3

你不一定需要使用CTE,可以查詢(),結果使用後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回答這個問題,但不是我正在尋找的東西...我應該再次問,這次是否正確? – Crisfole

1

是不幸的是,當你做一個窗口函數SQL得到你,即使你狂哪裏謂詞是合法的。你在你的select語句中創建一個cte或嵌套的選擇值,然後在後面引用你的CTE或嵌套選擇。簡單的例子應該是自我解釋。如果您確實在執行大型數據集時遇到某些性能問題,則可以始終刪除臨時表或表變量。

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

最後,還有老式的,預SQL Server 2005的方式,與相關子查詢:

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 
) 

我給你這個的完整性,只是。

2

的所有它的東西先叫all-at-once operation

「全在一次作業」是指在同一 邏輯查詢處理階段的所有表現都在同一時間在邏輯上進行評估。

而且對窗口功能偉大篇章影響:

假設你有:

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; 

全在一次行動告訴我們,在相同的邏輯評估這兩個條件時間點。因此,SQL Server可以 評估WHERE基於 估計的執行計劃條款以任意順序,條件。所以這裏的主要問題是 先評估哪個條件。

情況1:

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

結果:1002

情況2:

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

結果:空

所以我們有一個矛盾。

這個例子表明爲什麼在WHERE子句中,我們不能使用窗口功能。 你可以多想一想,並找到爲什麼窗口函數是 只允許在選擇ORDER BY條款!