SQL Server 中如何用变量合并多个查询?
结论
需要删除变量声明与使用之间的 GO。GO 会结束当前批次,而局部变量无法跨批次使用。
示例中还有一个更直接的问题:@copy_loc 和 @new_loc 被声明为 varchar(7),但 'OLD LOCATION'、'COPY LOCATION' 和 'NEW LOCATION' 都超过了 7 个字符。赋值时字符串会被截断,导致 location = @copy_loc 匹配不到源数据。
建议这样处理:
- 变量的数据类型和长度应与
location列一致; - 变量声明、删除和插入需要放在同一个批次中,不要在中间使用
GO; - 用
IN同时处理'waive'、'payment'和'refund',不必重复三遍查询。
推荐写法
假设 location 列可以容纳 50 个字符:
SET XACT_ABORT ON;
BEGIN TRANSACTION;
DECLARE @copy_loc varchar(50) = 'OLD LOCATION';
DECLARE @new_loc varchar(50) = 'NEW LOCATION';
DELETE FROM circ_slip_field
WHERE location = @new_loc
AND circ_slip IN ('waive', 'payment', 'refund');
INSERT INTO circ_slip_field
(
circ_slip,
location,
section,
ord,
circ_slip_field_type,
label,
field_column,
append_to_prev,
justify,
max_field_len,
min_field_len,
data_mask
)
SELECT
circ_slip,
@new_loc,
section,
ord,
circ_slip_field_type,
label,
field_column,
append_to_prev,
justify,
max_field_len,
min_field_len,
data_mask
FROM circ_slip_field
WHERE location = @copy_loc
AND circ_slip IN ('waive', 'payment', 'refund');
COMMIT TRANSACTION;
这段代码会先删除目标 location 下三种 circ_slip 的旧记录,再从源 location 复制对应记录。SELECT 保留了原有的 circ_slip 列,因此三种类型会按各自的原值插入,不会混在一起。
为什么 varchar(7) 会导致查询无效
下面的赋值无法保存完整字符串:
DECLARE @copy_loc varchar(7);
SET @copy_loc = 'OLD LOCATION';
SELECT @copy_loc;
变量最多只能容纳 7 个字符,所以实际得到的是:
OLD LOC
查询条件也就变成了:
WHERE location = 'OLD LOC'
如果表中保存的是完整的 'OLD LOCATION',查询便找不到源记录,INSERT ... SELECT 最终会插入 0 行。
变量类型最好与表中的 location 列完全一致。如果该列使用 nvarchar,变量也应使用 nvarchar,字符串常量则要加上 N 前缀:
DECLARE @copy_loc nvarchar(50) = N'OLD LOCATION';
DECLARE @new_loc nvarchar(50) = N'NEW LOCATION';
GO 为什么会影响变量
GO 不是 SQL Server 的 T-SQL 语句,而是 SSMS、sqlcmd 等客户端识别的批次分隔符。客户端执行到 GO 时会结束当前批次,其中声明的局部变量也随之失效。
因此,下面两个批次无法共享变量:
DECLARE @new_loc varchar(50) = 'NEW LOCATION';
GO
SELECT @new_loc;
执行后一批次时,通常会出现类似”必须声明标量变量”的错误。要让后续多条语句使用同一组变量,就不能在它们之间加入 GO:
DECLARE @copy_loc varchar(50) = 'OLD LOCATION';
DECLARE @new_loc varchar(50) = 'NEW LOCATION';
DELETE ...
INSERT ...
如果脚本中有 GO,却没有出现变量未声明错误,需要检查是否只选中了部分脚本执行,或者各批次是否重新声明了变量。通过应用程序驱动或 ORM 执行 SQL 时,GO 通常不会被识别。此时应由程序拆分批次,或者直接删除 GO。
执行前的检查
正式删除数据前,可以先确认源记录是否存在:
DECLARE @copy_loc varchar(50) = 'OLD LOCATION';
SELECT *
FROM circ_slip_field
WHERE location = @copy_loc
AND circ_slip IN ('waive', 'payment', 'refund');
也可以分别统计每种类型的记录数:
SELECT circ_slip, COUNT(*) AS row_count
FROM circ_slip_field
WHERE location = 'OLD LOCATION'
AND circ_slip IN ('waive', 'payment', 'refund')
GROUP BY circ_slip;
如果想先检查执行结果再决定是否提交,可以暂时把 COMMIT TRANSACTION 改成 ROLLBACK TRANSACTION。还要确认源 location 与目标 location 不相同,否则前面的 DELETE 会连同稍后需要复制的源记录一起删除。
备注:内容仅供参考。
