2012-03-23 106 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