为什么 SELECT Distinct 对于 SQL 列对于整数值 0 的行为异常?
Why does SELECT Distinct for a SQL column behave unexpectedly for the integer value of 0?
我 运行 这个在 SQL Server Express 上:
DECLARE @Logs table(Id INT, Num INT)
-- For VALUES (1,1),(2,x), (3,x),(4,x), version 1 and version 2 return the same value. EXCEPT IF x=0...Why????
INSERT INTO @Logs (Id, Num) VALUES (1,1),(2,0), (3,0),(4,0) --LINE 2
---------Version 1-------------
;WITH table2(Num,rn) as (
SELECT Num, ROW_NUMBER() OVER(ORDER BY (SELECT 1)) as rn from @Logs
)
SELECT DISTINCT(t3.Num) from table2 as t3 JOIN ( --DISTINCT on this line is only difference
SELECT t1.Num, t2.rn as last_rn, t1.rn as second_rn from
table2 as t1 JOIN table2 as t2 ON t1.rn = t2.rn-1 AND t1.Num = t2.Num
) as t4 ON t4.second_rn - 1 = t3.rn AND t3.Num = t4.Num
-----------Version 2 ------------
;WITH table2(Num,rn) as (
SELECT Num, ROW_NUMBER() OVER(ORDER BY (SELECT 1)) as rn from @Logs
)
SELECT t3.Num as ConsecutiveNums from table2 as t3 JOIN (
SELECT t1.Num, t2.rn as last_rn, t1.rn as second_rn from
table2 as t1 JOIN table2 as t2 ON t1.rn = t2.rn-1 AND t1.Num = t2.Num
) as t4 ON t4.second_rn - 1 = t3.rn AND t3.Num = t4.Num
版本 1 returns 1 个单行,值为 0。版本 2 returns 没有行。但是,两者之间唯一的区别是 SELECT
语句开头的关键字 DISTINCT
。
但是,当我将 INSERT INTO @Logs
(第 2 行)中的 0 替换为 0 以外的任何数字时,版本 1 和版本 2 return 同一行。
为什么???我检查了一个简单的 SELECT DISTINCT
在数字零上没有同样的错误(
SELECT DISTINCT(n) from (VALUES (1),(2),(0),(3),(2)) as t(n)
表现符合预期)
这里发生了几件事。
首先,SQL 表和结果集表示 无序 集。没有顺序,除非有明确的 ORDER BY
.
因此,正如 Jeroen 在评论中指出的那样,CTE 具有不确定的排序。
其次,SQL服务器没有具体化子查询。它在每次被引用时运行子查询。现在,将这一点与第一点结合起来,您就会意识到 CTE 每次在查询中被引用时都会 return 不同的结果,这取决于优化器和底层执行引擎的变幻莫测。
所以,这就是正在发生的事情。 SQL 每次引用 CTE 时,服务器都会为其选择不同的执行计划,从而导致数据不兼容——以及不同的结果。我会注意到这在 SQL Server 和 SQL Server Express 中都是正确的(参见 here)。
将联接类型更改为 left join
无法解决问题 。它可能会生成预期的执行计划,但这会隐藏问题。
正确的更改是修复 order by
,大概在这种情况下使用 order by id
:
WITH table2(Num,rn) as (
SELECT Num, ROW_NUMBER() OVER (ORDER BY (SELECT id)) as rn
FROM @Logs
)
请注意,这些由 ORDER BY
中的“联系”(通常但不总是与 window 函数一起使用)引起的错误可能很难找到和调试。我会敦促您注意排序并始终思考“键是唯一的,我是否应该添加一个主键作为键的最后一个顺序”。
我 运行 这个在 SQL Server Express 上:
DECLARE @Logs table(Id INT, Num INT)
-- For VALUES (1,1),(2,x), (3,x),(4,x), version 1 and version 2 return the same value. EXCEPT IF x=0...Why????
INSERT INTO @Logs (Id, Num) VALUES (1,1),(2,0), (3,0),(4,0) --LINE 2
---------Version 1-------------
;WITH table2(Num,rn) as (
SELECT Num, ROW_NUMBER() OVER(ORDER BY (SELECT 1)) as rn from @Logs
)
SELECT DISTINCT(t3.Num) from table2 as t3 JOIN ( --DISTINCT on this line is only difference
SELECT t1.Num, t2.rn as last_rn, t1.rn as second_rn from
table2 as t1 JOIN table2 as t2 ON t1.rn = t2.rn-1 AND t1.Num = t2.Num
) as t4 ON t4.second_rn - 1 = t3.rn AND t3.Num = t4.Num
-----------Version 2 ------------
;WITH table2(Num,rn) as (
SELECT Num, ROW_NUMBER() OVER(ORDER BY (SELECT 1)) as rn from @Logs
)
SELECT t3.Num as ConsecutiveNums from table2 as t3 JOIN (
SELECT t1.Num, t2.rn as last_rn, t1.rn as second_rn from
table2 as t1 JOIN table2 as t2 ON t1.rn = t2.rn-1 AND t1.Num = t2.Num
) as t4 ON t4.second_rn - 1 = t3.rn AND t3.Num = t4.Num
版本 1 returns 1 个单行,值为 0。版本 2 returns 没有行。但是,两者之间唯一的区别是 SELECT
语句开头的关键字 DISTINCT
。
但是,当我将 INSERT INTO @Logs
(第 2 行)中的 0 替换为 0 以外的任何数字时,版本 1 和版本 2 return 同一行。
为什么???我检查了一个简单的 SELECT DISTINCT
在数字零上没有同样的错误(
SELECT DISTINCT(n) from (VALUES (1),(2),(0),(3),(2)) as t(n)
表现符合预期)
这里发生了几件事。
首先,SQL 表和结果集表示 无序 集。没有顺序,除非有明确的 ORDER BY
.
因此,正如 Jeroen 在评论中指出的那样,CTE 具有不确定的排序。
其次,SQL服务器没有具体化子查询。它在每次被引用时运行子查询。现在,将这一点与第一点结合起来,您就会意识到 CTE 每次在查询中被引用时都会 return 不同的结果,这取决于优化器和底层执行引擎的变幻莫测。
所以,这就是正在发生的事情。 SQL 每次引用 CTE 时,服务器都会为其选择不同的执行计划,从而导致数据不兼容——以及不同的结果。我会注意到这在 SQL Server 和 SQL Server Express 中都是正确的(参见 here)。
将联接类型更改为 left join
无法解决问题 。它可能会生成预期的执行计划,但这会隐藏问题。
正确的更改是修复 order by
,大概在这种情况下使用 order by id
:
WITH table2(Num,rn) as (
SELECT Num, ROW_NUMBER() OVER (ORDER BY (SELECT id)) as rn
FROM @Logs
)
请注意,这些由 ORDER BY
中的“联系”(通常但不总是与 window 函数一起使用)引起的错误可能很难找到和调试。我会敦促您注意排序并始终思考“键是唯一的,我是否应该添加一个主键作为键的最后一个顺序”。