为什么这个子查询似乎不起作用?

cko*_*ozl 5 t-sql sql-server-2008

在此之前,我不是在寻找重写.这是呈现给我的,我似乎无法弄清楚这是否是一般的错误或由于脚本的特殊性而发生的某种句法疯狂.好的,用设置说:

  • 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]
Run Code Online (Sandbox Code Playgroud)

插入一些值:

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)
Run Code Online (Sandbox Code Playgroud)

现在运行查询(从每个组中选择一个,记住没有重写建议!):

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
Run Code Online (Sandbox Code Playgroud)

你应该得到:

RXXID
-----------
0
3
6
Run Code Online (Sandbox Code Playgroud)

现在把它放在更新语句中(预设为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
Run Code Online (Sandbox Code Playgroud)

得到这个结果:

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
Run Code Online (Sandbox Code Playgroud)

zomg wtf 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]
Run Code Online (Sandbox Code Playgroud)

并重新运行更新语句集,结果:

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
Run Code Online (Sandbox Code Playgroud)

不是那个ab?

有没有人知道这里发生了什么?我的猜测是某种子查询缓存,这是一个错误吗?它肯定不像SQL 应该做的那样?

cai*_*rnz 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
Run Code Online (Sandbox Code Playgroud)

添加了更多列来说明.您可以在http://sqlfiddle.com/#!3/03913/9上看到这一点

不是100%肯定你的例子中发生了什么,但由于你按同一列进行分区和排序,你并不确定会得到相同的订单,因为它们都是并列的.你应该不按RegionID或其他列订购,就像我在sqlfiddle上做的那样?


回到你的问题:

如果将UPDATE(使用聚簇索引)更改为SELECT,则将返回所有9行.如果删除PK并执行SELECT,则只能获得3行.返回更新声明.检查执行计划表明它们略有不同:

第一(PK)执行计划 第二个(没有PK)执行计划

你在这里看到的是,在第一个(使用PK)查询中,你将扫描聚集索引以获得外部引用,请注意它没有别名RXX.然后对于顶部的每一行,查找RXX.是的,由于您的行号排序,每个RegionGroupID的每个RegionID可以是row_number()1.我想,SQL Server会根据您的PK知道这一点,并且可以说对于每个RegionID,此RegionID可以是行号1.因此该语句相当有效.

在第二个查询中,没有索引,并且您在Regions上获得表扫描,然后使用RXX构建探测表,并以不同方式连接(单通道,ROW_NUMBER()现在每个regiongroupid只能为1行) .这种方式在该扫描中,每个RegionID只有一个ROW_NUMBER(),但你不能100%确定它每次都是一样的.

这意味着:使用对每次执行都没有确定性顺序的子查询,应该避免使用多次传递(NESTED LOOP)连接类型,而是使用单次传递(MERGE OR HASH)连接.

要在不更改查询结构的情况下解决此问题,请将OPTION(HASH JOIN)或OPTION(MERGE JOIN)添加到第一个UPDATE:

因此,您需要以下更新语句(当您拥有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
Run Code Online (Sandbox Code Playgroud)

以下是使用这两种连接类型的执行计划(注意实际行数:属性中的3):

使用MERGE JOIN 使用HASH JOIN