做 SQL Server 数据库迁移时,让开发团队紧张的往往不是几百张表,而是仓库里那些跑了很多年的 T-SQL。库存日结用 MERGE 合并数据,账户变更用 OUTPUT 写审计记录,经营报表里还有窗口函数和 PIVOT。如果这几类代码都要重写,后面跟着变的就不只是 SQL,还有异常处理、受影响行数、审计日志和报表字段。
我把这类迁移看成一次"语义对位":不急着按文件数量估工期,先找出最常用、最容易牵动业务的语法块,看目标版本能不能保留原来的表达。V9R4C019 针对 MERGE、DML OUTPUT、窗口函数、PIVOT/UNPIVOT 和 LIKE 通配符等高频 T-SQL 场景继续补强兼容能力,存量代码因此有了尽量保留原有业务语义的条件。
先选对目标版本和兼容模式
SQL Server 代码的语法习惯很明显:TOP、表变量、@name 局部变量、GO 批处理结束符、PRINT、MERGE 和过程中的 BEGIN...END 通常会成组出现。数据迁过去以后再逐条处理,很快就会变成一份没有边界的改造清单。
目标库使用 KingbaseES V9R4C019 SQL Server 兼容版,对象评估、脚本转换和应用回归也要在同一版本上完成。金仓官方的 SQL Server 至 KingbaseES 迁移指南把数据类型、SQL 语法、PL/MSSQL、客户端接口和迁移工具放在一条路径里。这个顺序很实用:先确认代码能否保留,再处理少量确实与应用逻辑相关的差异。

MERGE 保住了日结任务的原有结构
一个典型的库存日结任务,会先把当天变动写入中间表,再按商品和仓库合并到库存主表。原有 T-SQL 可以保持一条 MERGE 的结构:匹配到旧记录就更新数量,没有匹配到就新增。
ini
MERGE INTO inventory_balance AS t
USING inventory_delta AS s
ON t.warehouse_id = s.warehouse_id
AND t.product_id = s.product_id
WHEN MATCHED THEN
UPDATE SET
quantity = t.quantity + s.change_quantity,
last_batch_no = s.batch_no,
updated_at = CURRENT_TIMESTAMP
WHEN NOT MATCHED THEN
INSERT (
warehouse_id,
product_id,
quantity,
last_batch_no,
updated_at
)
VALUES (
s.warehouse_id,
s.product_id,
s.change_quantity,
s.batch_no,
CURRENT_TIMESTAMP
);
如果把这段代码拆成"先查询、再判断、最后 UPDATE 或 INSERT",事务中会多出一段不必要的分支。保留 MERGE 后,原来的匹配条件、更新表达式和新增字段仍放在一个语句中,回归测试也能继续围绕批次号、库存量和受影响行数展开。
验收时可以先核对这一批增量是否全部落到主表:
vbnet
SELECT d.batch_no,
COUNT(*) AS delta_rows,
SUM(d.change_quantity) AS quantity_delta,
SUM(b.quantity) AS current_quantity
FROM inventory_delta AS d
JOIN inventory_balance AS b
ON b.warehouse_id = d.warehouse_id
AND b.product_id = d.product_id
WHERE d.batch_no = 'DAY-20260901'
GROUP BY d.batch_no;
DML OUTPUT 让审计链不用换写法
金融、会员和供应链系统里,更新数据的同时记录更改前后值是很常见的写法。SQL Server 的 DML OUTPUT 可以从 deleted 和 inserted 中直接取值,再写入审计表。V9R4C019 对这类语法的支持,避免了把原子的一次更新改成应用层的"先读后写"。
sql
DECLARE @customer_id bigint = 10012001;
DECLARE @new_limit numeric(18,2) = 50000.00;
UPDATE customer_account
SET credit_limit = @new_limit,
updated_at = CURRENT_TIMESTAMP
OUTPUT inserted.customer_id,
deleted.credit_limit,
inserted.credit_limit,
inserted.updated_at
INTO customer_limit_log (
customer_id,
old_credit_limit,
new_credit_limit,
changed_at
)
WHERE customer_id = @customer_id;
对这段代码做回归,查的不只是账户表中的新额度,还要确认审计表只产生一条对应记录,旧值和新值与业务变更一致:
vbnet
SELECT a.customer_id,
a.credit_limit,
l.old_credit_limit,
l.new_credit_limit,
l.changed_at
FROM customer_account AS a
JOIN customer_limit_log AS l
ON l.customer_id = a.customer_id
WHERE a.customer_id = 10012001
ORDER BY l.changed_at DESC;

窗口函数和 PIVOT 继续服务于原报表
报表 SQL 的改造风险往往比普通查询更高。一方面,报表字段已经被前端、导出模板和定时任务使用;另一方面,同一统计口径常常同时用到分组排名和行列转换。
例如,这段 SQL 用 ROW_NUMBER() 找每个仓库最后一次盘点结果:
sql
WITH latest_stocktake AS (
SELECT warehouse_id,
product_id,
stocktake_at,
actual_quantity,
ROW_NUMBER() OVER (
PARTITION BY warehouse_id, product_id
ORDER BY stocktake_at DESC, stocktake_id DESC
) AS rn
FROM stocktake_record
)
SELECT warehouse_id,
product_id,
stocktake_at,
actual_quantity
FROM latest_stocktake
WHERE rn = 1;
排序条件里除了 stocktake_at,还加上唯一的 stocktake_id。同一时刻有多条盘点数据时,结果仍然有确定顺序,源库和目标库才能稳定对比。
月度报表原来使用 PIVOT 把季度销售额展开为四列,迁移后也可以保留原来的列名和结果结构:
css
SELECT warehouse_id,
[Q1],
[Q2],
[Q3],
[Q4]
FROM (
SELECT warehouse_id,
quarter_code,
sales_amount
FROM warehouse_quarter_sales
) AS src
PIVOT (
SUM(sales_amount)
FOR quarter_code IN ([Q1], [Q2], [Q3], [Q4])
) AS p;
前端报表仍然接收 Q1 到 Q4 四个字段,导出模板和图表配置也就不需要因为数据库切换而调整。UNPIVOT 用于反向将多个季度列还原成行,在导入标准明细表或者重新汇总时同样实用。
LIKE 里的字符范围不再另写正则
SQL Server 的 LIKE 除了 % 和 _,存量代码里还经常能看到方括号字符范围。仓储系统用代码规则筛选 A 到 C 类物料时,可能就是这样一句:
vbnet
SELECT material_code,
material_name,
warehouse_id
FROM material_master
WHERE material_code LIKE '[A-C][0-9][0-9]%'
ORDER BY material_code;
V9R4C019 对 SQL Server LIKE 通配符的完整支持,让这类搜索条件可以继续保留。项目组只需要把代码中用到的字符集、排序规则和样本结果放进回归清单,不用为每条范围匹配另造函数。
改动从"全面重写"收敛到连接和回归
一轮 T-SQL 梳理做完后,代码可以按处理方式分成三组:原样保留、工具自动转换、少量人工复核。MERGE、DML OUTPUT、窗口函数、PIVOT/UNPIVOT 和 SQL Server 风格的 LIKE 通配符可以进入第一组,开发人员把时间留给真正需要确认的业务对象。
上线前的回归也不用从头设计。库存日结核对批次和数量,账户额度核对主表与审计记录,窗口函数核对每组第一条,PIVOT 核对行数、四个季度列和合计值,LIKE 核对边界字符。这些都是原系统已经在用的业务语义,只需要在 V9R4C019 上重新确认一遍。
迁移计划也因此更容易排。数据库对象和存量 T-SQL 保留原结构,应用侧把改动集中在驱动、连接配置和必要的差异项上。开发团队面对的不再是一句"所有 SQL 都要改",而是一份已经按语法块分好类、能够逐项回归的迁移清单。