SELECT 赋值与ORDER BY冲突的问题

本文探讨了在SQL中使用ORDER BY语句与变量更新时出现的不一致结果问题。通过对比带与不带ORDER BY语句的执行计划,分析了问题产生的原因,并提供了解决方案。

问题描述:

使用 SELECT 语句,轮询表中的数据,并且处理变量数据时,如果有ORDER BY语句,则得不到想要的结果,但去掉ORDER BY,结果正常。

具体的问题表现参考下面的问题重现代码

 

问题重现代码

-- 测试数据

DECLARE @T TABLE(id int,value nvarchar(16))

INSERT INTO @T SELECT

1,   N'好人' UNION ALL SELECT

2,   N'坏人' UNION ALL SELECT

3,   N'吃饭' UNION ALL SELECT

4,   N'垃圾'

 

-- 赋值处理

DECLARE @str nvarchar(4000)

SET @str = N'我不是一个好人,也不是垃圾'

SELECT @str = REPLACE(@str, value, N'' + value + N'')

FROM @T

WHERE CHARINDEX(value, @str) > 0

--ORDER BY CHARINDEX(value, @str) DESC

SELECT @str

 

/* -- 结果(当赋值处理语句注释掉ORDER BY )

我不是一个好人,也不是垃圾

-- */

 

/* -- 结果(当赋值处理语句加上ORDER BY )

我不是一个好人,也不是垃圾

-- */

 

问题分析:

两个处理语句的结果不同,通过查看它们的执行计划应该可以看出原因所在,为此,通过

SET SHOWPLAN_ALL ON

输出了两种执行语句的执行计划(仅StmtText部分,有兴趣的读者在自己的电脑上测试的时候,可以去了解其他部分的信息)

StmtText

Step

DECLARE @str nvarchar(4000)  SET @str = N'我不是一个好人,也不是垃圾'

 

SELECT @str = REPLACE(@str, value, N'' + value + N'')  FROM @T   WHERE CHARINDEX(value, @str) > 0

4

 

|--

Compute Scalar(DEFINE:([Expr1002]=replace([@str], @T.[value], ''+@T.[value]+'')))

3

 

 

|--

Filter(WHERE:(charindex(@T.[value], [@str], NULL)>0))

2

 

 

 

|--

Table Scan(OBJECT:(@T))

1

 

 

 

 

 

 

 

DECLARE @str nvarchar(4000)  SET @str = N'我不是一个好人,也不是垃圾'

 

SELECT @str = REPLACE(@str, value, N'' + value + N'')  FROM @T   WHERE CHARINDEX(value, @str) > 0   ORDER BY CHARINDEX(value, @str) DESC

5

 

|--

Sort(ORDER BY:([Expr1003] DESC))

4

 

 

|--

Compute Scalar(DEFINE:([Expr1002]=replace([@str], @T.[value], ''+@T.[value]+''), [Expr1003]=charindex(@T.[value], [@str], NULL)))

3

 

 

 

|--

Filter(WHERE:(charindex(@T.[value], [@str], NULL)>0))

2

 

 

 

 

|--

Table Scan(OBJECT:(@T))

1

从上面的列表可以看出,两种处理的最大差异,在于赋值前,是否有ORDER BY 子句,从一般的理解上,可能会认为是否排序并不重要,但换个角度来看问题,就比较容易理解为什么有ORDER BY子句后得不到我们想要的结果了:

当有ORDER BY子句时,对于SELECT @str = 这种赋值处理,SQL Server认为赋值处理肯定只会保留最后一条记录的处理结果,而ORDER BY子句确定了数据顺序,也就知道最后一条记录是那个,因此只会处理ORDER BY的最后一条记录。(读者可以自行去测试一下,调整ORDER BY顺序,看看结果是否与我的推论相符)

当没有ORDER BY子句时,因为无法确定数据顺序,所以SQL Server必须扫描满足条件的每条数据来得到结果,这样每扫描一条记录都会处理一次,所以结果是我们所预知的

 

问题解决方法:

修改处理语句,使查询优化器使用与我们需要结果一致的执行方法,可以解决这个问题。

对于示例中的处理语句,可以调整如下:

DECLARE @str nvarchar(4000)

SET @str = N'我不是一个好人,也不是垃圾'

SELECT @str = REPLACE(@str, value, N'' + value + N'')

FROM(

    SELECT TOP 100 PERCENT

        value

    FROM @T

    WHERE CHARINDEX(value, @str) > 0

    ORDER BY CHARINDEX(value, @str) DESC

)A

SELECT @str

 

补充:

此问题的结论只是笔者对于查询分析的一个推论,并无相应的官方文档可以证明,所以欢迎大家发表自己的看法

 

Trackback: http://tb.blog.youkuaiyun.com/TrackBack.aspx?PostId=1233068


这个sql 为什么没有更新c_order_consumption的order_consumption_record_id? CREATE DEFINER=`root`@`%` PROCEDURE `sync_order_consumption_copy1`() BEGIN #Routine body goes here... -- 定义批次大小和起始点 SET @now = DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'); SET @batch_size = 1000; SET @min_id = (SELECT MIN(order_id) FROM c_order where delmark in (1,2,3)); SET @max_id = (SELECT MAX(order_id) FROM c_order where delmark in (1,2,3)); CREATE TEMPORARY TABLE IF NOT EXISTS id_mapping ( seq INT AUTO_INCREMENT PRIMARY KEY, source_id INT ); -- 循环处理每个批次 WHILE @min_id <= @max_id DO START TRANSACTION; TRUNCATE TABLE id_mapping; INSERT INTO id_mapping (source_id) SELECT order_consumption_id FROM c_order_consumption WHERE order_id BETWEEN @min_id AND @min_id + @batch_size - 1 AND delmark IN (1,2,3) AND order_consumption_record_id = 0 ORDER BY order_consumption_id; -- 置换订单明细 -- 消耗完 INSERT INTO c_order_consumption_record( sign, consumption_date, customer_id, agent_id, user_id, comment_id, detection, clean_hair, effect, create_time, create_by, update_time, update_by, delmark, remark ) SELECT sign, consumption_date, customer_id, agent_id, user_id, null, 0, 0, 0, create_time, '数据迁移生成', update_time, update_by, delmark, remark FROM c_order_consumption c JOIN id_mapping m ON c.order_consumption_id = m.source_id ORDER BY m.seq; -- 获取自增ID SET @first_id = LAST_INSERT_ID(); SET @row_count = ROW_COUNT(); -- 通过序号映射更新 UPDATE c_order_consumption dest JOIN id_mapping m ON dest.order_consumption_id = m.source_id SET dest.order_consumption_record_id = @first_id + m.seq - 1 -- 计算自增ID WHERE m.seq <= @row_count; -- 数据一致性校验:检查行数是否匹配 SET @order_info_count = (SELECT COUNT(1) FROM c_order_consumption dest JOIN id_mapping m ON dest.order_consumption_id = m.source_id WHERE dest.order_consumption_record_id >= @first_id AND dest.order_consumption_record_id < @first_id + @row_count ); SET @order_count = (SELECT COUNT(1) FROM c_order_consumption_record WHERE order_consumption_record_id >= @first_id AND order_consumption_record_id < @first_id + @row_count); -- 如果校验失败,回滚事务 IF @row_count != @order_info_count OR @row_count != @order_count THEN ROLLBACK; SELECT @min_id,@min_id + @batch_size - 1,'This is a log error message' AS LogMessage; SELECT @row_count, @order_info_count, @order_count AS LogMessage; SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Data inconsistency detected, rolled back batch'; ELSE COMMIT; END IF; -- 更新批次起始点 SET @min_id = @min_id + @batch_size; END WHILE; DROP TEMPORARY TABLE IF EXISTS id_mapping; SELECT @max_id,@now,DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'); -- 释放表锁 -- UNLOCK TABLES; END
11-03
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值