2012-03-23 103 views
5

在此之前,我不想重寫。這是提供給我的,我似乎無法弄清楚這是一個普遍的錯誤還是由於腳本的特殊性而發生的某種語法瘋狂。好吧有與設置上說:這個子查詢爲什麼不起作用?

  • 的Microsoft SQL Server標準版(64位)

  • 版本10.50.2500.0

在位於一個普通的表數據庫,定義爲:

CREATE TABLE [dbo].[Regions](
    [RegionID] [int] NOT NULL, 
    [RegionGroupID] [int] NOT NULL, 
    [IsDefault] [bit] NOT NULL, 
CONSTRAINT [PK_Regions] PRIMARY KEY CLUSTERED 
(
    [RegionID] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

個插入一些值:

INSERT INTO [dbo].[Regions] 
([RegionID],[RegionGroupID],[IsDefault]) 
VALUES 
(0,1,0), 
(1,1,0), 
(2,1,0), 
(3,2,0), 
(4,2,0), 
(5,2,0), 
(6,3,0), 
(7,3,0), 
(8,3,0) 

現在運行查詢(從每個組中選擇一個,切記不重寫建議!):

SELECT RXXID FROM (
    SELECT 
     RXX.RegionID as RXXID, 
     ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM 
    FROM Regions as RXX 
) AS tmp 
WHERE tmp.RXXNUM = 1 

你應該得到:

RXXID 
----------- 
0 
3 
6 

現在堅持下去Ë更新語句(與預設爲0,畢竟一個選擇):

UPDATE Regions SET IsDefault = 0 

UPDATE Regions 
SET IsDefault = 1 
WHERE RegionID IN (
    SELECT RXXID FROM (
     SELECT 
      RXX.RegionID as RXXID, 
      ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM 
     FROM Regions as RXX 
    ) AS tmp 
    WHERE tmp.RXXNUM = 1 
) 


SELECT * FROM Regions 
ORDER BY RegionGroupID 

並得到這樣的結果:

RegionID RegionGroupID IsDefault 
----------- ------------- --------- 
0   1    1 
1   1    1 
2   1    1 
3   2    1 
4   2    1 
5   2    1 
6   3    1 
7   3    1 
8   3    1 

ZOMG跆拳道lamaz?

雖然我不聲稱是SQL大師,但這看起來既不合適也不正確。而爲了讓事情變得更加瘋狂,如果你刪除主鍵似乎工作:

刪除主鍵:

IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Regions]') AND name = N'PK_Regions') 
ALTER TABLE [dbo].[Regions] DROP CONSTRAINT [PK_Regions] 

並重新運行update語句集,結果是:

RegionID RegionGroupID IsDefault 
----------- ------------- --------- 
0   1    1 
1   1    0 
2   1    0 
3   2    1 
4   2    0 
5   2    0 
6   3    1 
7   3    0 
8   3    0 

這不是嗎?

有沒有人有任何線索這裏發生了什麼?我的猜測是某種子查詢緩存,這是一個錯誤嗎?它肯定不像SQL 應該做什麼

+0

這是BTW一個很有趣的問題! – cairnz 2012-03-23 20:39:10

回答

9

剛剛更新的CTE直接:

WITH tmp AS (
SELECT 
     RegionID as RXXID, 
     RegionGroupID, 
     IsDefault, 
     ROW_NUMBER() OVER (PARTITION BY RegionGroupID ORDER BY RegionID) AS RXXNUM 
    FROM Regions 

) 
UPDATE tmp SET IsDefault = 1 WHERE RXXNUM = 1 
select * from Regions 

增加了更多的列來說明。你可以在http://sqlfiddle.com/#!3/03913/9

不是100%確定你的例子中發生了什麼,但是由於你是通過同一列進行分區和排序的,所以你不一定會得到相同的訂單,因爲它們都是並列。你不應該通過RegionID或其他專欄來訂購,就像我在sqlfiddle上做的那樣?


回到你的問題:

如果您改變UPDATE(與聚集索引),以一個SELECT,你會得到所有9行回來。 如果你刪除PK,並執行SELECT,你只會得到3行。回到你的更新聲明。檢查執行計劃表明,他們略有不同:

First (PK) Execution plan Second (No PK) Execution plan

什麼,你可以在這裏看到的是,在第一(與PK)查詢,您可以掃描外參考,請注意聚集索引它沒有別名RXX。然後,對於頂部的每一行,查找RXX。是的,由於您的行號排序,每個RegionID可以是每個RegionGroupID的row_number()1。我猜,SQL Server會根據你的PK知道這一點,並且可以說對於每個RegionID,這個RegionID可以是行號1.因此,該語句相當有效。

在第二個查詢中,沒有索引,並且您在Region上獲得表掃描,然後使用RXX構建探測表,並以不同的方式連接(單遍,ROW_NUMBER()只能爲1行現在每個regiongroupid)。通過這種掃描方式,每個RegionID只有一個ROW_NUMBER(),但不能100%確定每次都是一樣的。

這意味着: 使用你的子查詢,它對每次執行都沒有確定性的次序,你應該避免使用多次傳遞(NESTED LOOP)連接類型,而是單次傳遞(MERGE或HASH)連接。

爲了解決這個問題,而不改變您的查詢的結構,添加OPTION(HASH JOIN)或OPTION(MERGE JOIN),以第一次更新:

所以,你需要以下更新語句(當你有在PK):

UPDATE Regions SET IsDefault = 0 

UPDATE Regions 
SET IsDefault = 1 
WHERE RegionID IN (
    SELECT RXXID FROM (
     SELECT 
      RXX.RegionID as RXXID, 
      ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM 
     FROM Regions as RXX 
    ) AS tmp 
    WHERE tmp.RXXNUM = 1 
) 
OPTION (HASH JOIN) 

SELECT * FROM Regions 
ORDER BY RegionGroupID 

下面是使用這兩種連接類型(注意行的實際數目的執行計劃:在屬性3):

Using MERGE JOIN Using HASH JOIN

+1

我意識到這並不能回答你的問題(我現在在環境中看到它,但它給了你一個解決方法,並避免了不必要的子選擇) – cairnz 2012-03-23 20:02:22

+1

如果你將ckozl的查​​詢改變爲RegionID,你會得到預期(b)結果,並且我在10.0版本中獲得了相同的結果,因爲他的ckozl正在查詢他提供的查詢。 – 2012-03-23 20:03:09

+0

你得到了一個投票的鼻子。 ROW_NUMBER()OVER(由RXX.RegionGroupID ORDER BY RXX.RegionID分區)AS RXXNUM'修復它完美。但問題的50%是爲什麼這種行爲是這樣的......因爲看起來沒有意義,如果有人沒有得到它,雖然你會得到你的努力的餅乾。做得好! – ckozl 2012-03-23 20:08:24

3

您的查詢語言如下:
對於Regions中的每一行,檢查RegionID是否存在於某個子查詢中。這意味着子查詢在Regions中的每一行都被執行。 (我知道情況並非如此,但它是查詢的語義)。

由於您使用RegionGroupID作爲訂單和分區,因此您確實不知道將返回什麼RegionID,因此每次檢查子查詢時它都可能是一個新ID。

更新:

做了更新加入對派生表,而不是代替使用的改變查詢的語義,它改變了,結果也是如此。

可正常工作:

UPDATE R 
SET IsDefault = 1 
FROM Regions as R 
    inner join 
     (
     SELECT RXXID FROM (
      SELECT 
       RXX.RegionID as RXXID, 
       ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM 
      FROM Regions as RXX 
     ) AS tmp 
     WHERE tmp.RXXNUM = 1 
    ) as C 
    on R.RegionID = C.RXXID 
+0

這個工作使用了一個NESTED LOOP joim,因爲C是首先構建的一旦)。 – cairnz 2012-03-23 20:25:59