Trae+SQLazy 实践:移植 Oracle SQL 到达梦
一、前言
许多国内企业面临将国外数据库迁移到国产数据库的任务,这不仅涉及数据搬迁,更核心的挑战在于大量复杂 SQL 语句的移植。目前业界主要有两种方案:
1、目标数据库的兼容模式。许多国产数据库提供了对 Oracle 语法的兼容模拟,可直接运行 Oracle 的 SQL。优点是几乎无需改造、上线快,这对于 SQL 简单的 TP 业务非常适用。缺点是强依赖于目标数据库的实现,兼容覆盖度有限,通常只支持 Oracle,且对某些高级语法支持不完整(如 MODEL 子句、多列 PIVOT 等),难以适用于 SQL 复杂的 AP 业务;且临时解析会造成性能不稳定,也难以发挥目标数据库的原生特性。
2、用 AI 将 SQL 改写成目标数据库语法。优点是可以生成针对目标数据库的原生 SQL,充分发挥其能力。缺点是大语言模型存在幻觉问题,对语料素材缺乏的国产数据库不够友好,改写后的 SQL 出错率较高,需高昂的人工审核成本;而 AP 业务中大量涉及多层 CTE、嵌套窗口函数的复杂 SQL,又会进一步加剧这个问题。
采用 AI+SQLazy 的组合方案可解决上述不足:由 AI 理解原始 SQL,并翻译为结构化的 SQLazy 脚本,经过调试审计确认后,再由 SQLazy 编译为目标数据库的 SQL。SQLazy 脚本是分步执行、可审计的中间表示,每一步逻辑都清晰可读,极其便于人工审核和验证;SQLazy 的生成引擎是编译器机制,不依赖大模型,确定性的输入一定产生确定性的输出,从根本上避免了 AI 幻觉。
本文以 Trae 作为 AI 工具,实践将 Oracle SQL 移植到达梦数据库的过程。
二、工具的角色与能力
Trae 是 AI 编程工具,在本项目中作为理解与翻译的 "大脑"。它能够自动加载项目中的全局规约文件和 SQLazy 函数 / 功能参考文档,当用户输入 "/sqlazy 规划" 命令时,Trae 按四步流程(能力梳理→需求拆解→功能匹配→代码实现)结构化地输出 SQLazy 脚本,而不是直接生成 SQL。这种强制输出流程确保了方案的可审查性——每一步的选择依据都清晰可见。
SQLazy 是结构化数据计算工具,配有专属 IDE 用于编写和执行 SQLazy 脚本,在本项目中作为执行与验证的 "双手"。它能把复杂的业务逻辑编写为一条条人能读懂的 "操作指令",每条指令完成一步数据处理。SQLazy 提供三重保障:单步语义清晰、审计门槛低;分步执行、可快速定位问题;一套脚本、可编译为多种数据库的 SQL(MySQL、PostgreSQL、Oracle、达梦等),无需为不同数据库分别重写。
三、操作流程
3.1 环境准备
项目采用以下标准目录结构:
project_root/
├── 规划.md # 全局规约:格式规范、加载路径
├── sqlazy规划.md # 命令入口:/sqlazy规划触发
├── nspl/ # 交付目录:SQLazy脚本存放于此
├── 函数/ # 函数参考文档(自动加载)
└── 功能/ # 功能参考文档(自动加载)
在 Trae 中新建项目后,从 SQLazy 安装目录下的 LLM 目录复制 sqlazy 规划.md、规划.md 文件以及函数和功能两个目录到项目根目录即可。
3.2 触发方式
在 Trae 聊天框中使用 "/sqlazy 规划" 命令触发任务,后接完整的业务需求描述和 Oracle SQL 语句。Trae 会自动加载项目中的规约文件和函数 / 功能文档,按四步流程输出 SQLazy 脚本。
3.3 验证与修正
这是整条链路中最关键的环节:
1、构造少量有代表性的测试数据,手工算出期望结果。
2、在 SQLazy IDE 中分步运行脚本,对比中间结果与期望值。
3、发现问题时,将 SQLazy IDE 报出的错误信息反馈给 Trae,让其修正脚本。如此反复直到能正确运行。
验证通过后,再用 SQLazy 编译出达梦数据库的 SQL 即可,这一步非常简单,下面的实操中就不再提及操作过程了。
四、案例实操
以下四个案例涵盖了 CTE 多层嵌套与窗口函数、MODEL 子句、PIVOT 行转列、多排名函数与同比占比计算等典型 Oracle 特有语法场景。
案例一:区域产品销售占比及排名
这条 SQL 语句的功能是:筛选 2025 年销售数据,按产品和区域分组汇总销售额,然后计算每个产品在区域内的销售占比、产品全局总销售额、区域内排名,最终取各区域排名前三的产品,并计算与上一名产品的销售额差值。
用到的 Oracle 特殊函数和语法:SUM()OVER (PARTITION BY) 窗口聚合、RANK()OVER (PARTITION BY ... ORDER BY ...) 排名函数、LAG()OVER (PARTITION BY ... ORDER BY ...) 取前一行、|| 字符串拼接运算符。
【操作】在 Trae 聊天框中输入:
/sqlazy规划 把下面这句Oracle SQL语句转换成SQLazy脚本
SELECT
pa.product,
pa.region,
pa.total_sales,
pa.pct_of_region || '%' AS region_share,
pa.product_global_total,
pa.rank_in_region,
pa.total_sales - LAG(pa.total_sales, 1, 0) OVER (PARTITION BY pa.region ORDER BY pa.rank_in_region) AS sales_gap_from_previous
FROM (
SELECT
rs.product,
rs.region,
rs.total_sales,
ROUND(rs.total_sales / SUM(rs.total_sales) OVER (PARTITION BY rs.region) * 100, 2) AS pct_of_region,
SUM(rs.total_sales) OVER (PARTITION BY rs.product) AS product_global_total,
RANK() OVER (PARTITION BY rs.region ORDER BY rs.total_sales DESC) AS rank_in_region
FROM (
SELECT
product,
region,
SUM(amount) AS total_sales,
COUNT(*) AS sales_count
FROM sales_data
WHERE sale_date >= DATE '2025-01-01' AND sale_date < DATE '2026-01-01'
GROUP BY product, region
) rs
) pa
WHERE pa.rank_in_region <= 3
ORDER BY pa.region, pa.rank_in_region
【生成的 SQLazy 脚本】
t1=sales_data:筛选 (sale_date >= 2025-01-01 且 sale_date < 2026-01-01)
t2=汇总 amount 合计 命名 total_sales, 计数 命名 sales_count; 分组 product, region
t3=计算列 total_sales, 合计, 命名 region_total; 分区 region
t4=计算列 round(total_sales / region_total * 100, 2), 命名 pct_of_region
t5=计算列 total_sales, 合计, 命名 product_global_total; 分区 product
t6=排名 total_sales 降序; 命名 rank_in_region; 分区 region
t7=筛选 (rank_in_region <= 3)
t8=排序 region, rank_in_region
t9=计算列 concat(pct_of_region, "%"), 命名 region_share; (total_sales - nvl(total_sales[-1], 0)), 命名 sales_gap_from_previous; 分区 region
t10=导出表 product, region, total_sales, region_share, product_global_total, rank_in_region, sales_gap_from_previous
【纠错过程】
1 次纠错:运行第 2 行时报错 "值 [命名] 没有可以匹配的参数"。原因是 "计数" 前缺少被聚合的计算式,SQLazy 的语法要求聚合运算必须指定被聚合的字段。将 "计数 命名 sales_count" 改为 "amount 计数 命名 sales_count" 后通过。
【编译生成的达梦 SQL】
WITH t2 AS (
SELECT
product,
region,
SUM(amount) AS total_sales,
COUNT(amount) AS sales_count
FROM
(
SELECT
product,
region,
amount,
sale_date
FROM
sales_data
WHERE
(
sale_date >= CAST('2025-01-01' AS DATE)
AND sale_date < CAST('2026-01-01' AS DATE)
)
) t_1
GROUP BY
product,
region
),
t3 AS (
SELECT
product,
region,
total_sales,
sales_count,
SUM(total_sales) OVER (PARTITION BY region) AS region_total
FROM
t2
),
t5 AS (
SELECT
product,
region,
total_sales,
sales_count,
region_total,
round(total_sales / region_total * 100, 2) AS pct_of_region,
SUM(total_sales) OVER (PARTITION BY product) AS product_global_total
FROM
t3
),
sub__5 AS (
SELECT
sub__4.*,
RANK() OVER (
PARTITION BY region
ORDER BY
total_sales DESC
) AS rank_in_region
FROM
t5 sub__4
),
t9 AS (
SELECT
product,
region,
total_sales,
sales_count,
region_total,
pct_of_region,
product_global_total,
rank_in_region,
TO_CHAR(pct_of_region) + '%' AS region_share,
(
total_sales - COALESCE(
NULLIF(
LAG(total_sales, 1) OVER (
PARTITION BY region
ORDER BY
CASE
WHEN region IS NULL THEN 1
ELSE 0
END,
region ASC,
CASE
WHEN rank_in_region IS NULL THEN 1
ELSE 0
END,
rank_in_region ASC
),
''
),
NULLIF(0, '')
)
) AS sales_gap_from_previous
FROM
sub__5
WHERE
(rank_in_region <= 3)
)
SELECT
product,
region,
total_sales,
region_share,
product_global_total,
rank_in_region,
sales_gap_from_previous
FROM
t9
ORDER BY
CASE
WHEN region IS NULL THEN 1
ELSE 0
END,
region ASC,
CASE
WHEN rank_in_region IS NULL THEN 1
ELSE 0
END,
rank_in_region ASC
案例二:同比环比增长率(MODEL 子句)
这条 SQL 语句的功能是:使用 MODEL 子句按产品分区,基于年份维度,计算每个产品各季度的同比销售额(前一年同季度)和环比销售额(上一季度),并计算同比增长率和环比增长率。
用到的 Oracle 特殊函数和语法:MODEL 子句(PARTITION BY 分区、DIMENSION BY 维度、MEASURES 度量、RULES 规则)是 Oracle 独有的行列引用模型,通过 sales[year-1] 这样的位置引用语法访问偏移数据;此外还有 NVL() 空值替换、CASE WHEN 条件判断。MODEL 子句在达梦等其他数据库中没有对应物,是最难移植的 Oracle 语法之一。
【操作】在 Trae 聊天框中输入:
/sqlazy规划 把下面这句Oracle SQL语句转换成SQLazy脚本
SELECT
product,
year,
sales,
prev_year_sales,
yoy_growth_pct,
prev_quarter_sales,
qoq_growth_pct
FROM sales_tbl
MODEL
PARTITION BY (product)
DIMENSION BY (year)
MEASURES (
sales,
0 AS prev_year_sales,
0 AS yoy_growth_pct,
0 AS prev_quarter_sales,
0 AS qoq_growth_pct
)
RULES (
prev_year_sales[year] = NVL(sales[year - 1], NULL),
yoy_growth_pct[year] =
CASE
WHEN prev_year_sales[year] IS NOT NULL AND prev_year_sales[year] != 0
THEN ROUND((sales[year] - prev_year_sales[year]) / prev_year_sales[year] * 100, 2)
ELSE NULL
END,
prev_quarter_sales[year] = NVL(sales[year - 0.25], NULL),
qoq_growth_pct[year] =
CASE
WHEN prev_quarter_sales[year] IS NOT NULL AND prev_quarter_sales[year] != 0
THEN ROUND((sales[year] - prev_quarter_sales[year]) / prev_quarter_sales[year] * 100, 2)
ELSE NULL
END
)
ORDER BY product, year
【生成的 SQLazy 脚本】
t1=sales_tbl:排序 product, year
t2=计算列 sales[-4], 命名 prev_year_sales; sales[-1], 命名 prev_quarter_sales; 分区 product
t3=计算列 条件 ((prev_year_sales 非空 且 prev_year_sales != 0) 则 round((sales - prev_year_sales) / prev_year_sales * 100, 2) 否则 null), 命名 yoy_growth_pct; 条件 ((prev_quarter_sales 非空 且 prev_quarter_sales != 0) 则 round((sales - prev_quarter_sales) / prev_quarter_sales * 100, 2) 否则 null), 命名 qoq_growth_pct
t4=导出表 product, year, sales, prev_year_sales, yoy_growth_pct, prev_quarter_sales, qoq_growth_pct
【纠错过程】
1 次纠错:运行第 1 行时报错 "函数 [year] 参数错误"。原因是 year 是 SQLazy 的内置函数(取日期的年份),直接用作列名时会被解析为函数调用。将列名 year 加引号改为 'year' 后通过。
2 次纠错:运行第 3 行时报错 "连词 [且] 匹配条件项错误"。原因是 "非空" 与 "且" 的组合在条件解析器中存在语法冲突。解决方法是用 nvl 函数将 null 转为 0,然后用单个 "!= 0" 判断同时覆盖 null 检查和除零保护。将条件改为 "nvl(prev_year_sales, 0) != 0" 后通过。这一改写不仅解决了语法问题,逻辑上也更加简洁——nvl(x, 0) != 0 在 x 为 null 时返回 false(等价于 IS NOT NULL),在 x 为 0 时也返回 false(等价于除零保护)。
【最终正确脚本】
t1=sales_tbl:排序 product, 'year'
t2=计算列 sales[-4], 命名 prev_year_sales; sales[-1], 命名 prev_quarter_sales; 分区 product
t3=计算列 条件 (nvl(prev_year_sales, 0) != 0 则 round((sales - prev_year_sales) / prev_year_sales * 100, 2) 否则 null), 命名 yoy_growth_pct; 条件 (nvl(prev_quarter_sales, 0) != 0 则 round((sales - prev_quarter_sales) / prev_quarter_sales * 100, 2) 否则 null), 命名 qoq_growth_pct
t4=导出表 product, 'year', sales, prev_year_sales, yoy_growth_pct, prev_quarter_sales, qoq_growth_pct
【编译生成的达梦 SQL】
WITH t2 AS (
SELECT
product,
year,
sales,
LAG(sales, 4) OVER (
PARTITION BY product
ORDER BY
CASE
WHEN product IS NULL THEN 1
ELSE 0
END,
product ASC,
CASE
WHEN "year" IS NULL THEN 1
ELSE 0
END,
"year" ASC
) AS prev_year_sales,
LAG(sales, 1) OVER (
PARTITION BY product
ORDER BY
CASE
WHEN product IS NULL THEN 1
ELSE 0
END,
product ASC,
CASE
WHEN "year" IS NULL THEN 1
ELSE 0
END,
"year" ASC
) AS prev_quarter_sales
FROM
sales_tbl
),
t3 AS (
SELECT
product,
year,
sales,
prev_year_sales,
prev_quarter_sales,
CASE
WHEN COALESCE(NULLIF(prev_year_sales, ''), NULLIF(0, '')) <> 0 THEN round((sales - prev_year_sales) / prev_year_sales * 100, 2)
ELSE NULL
END AS yoy_growth_pct,
CASE
WHEN COALESCE(NULLIF(prev_quarter_sales, ''), NULLIF(0, '')) <> 0 THEN round(
(sales - prev_quarter_sales) / prev_quarter_sales * 100,
2
)
ELSE NULL
END AS qoq_growth_pct
FROM
t2
)
SELECT
product,
"year",
sales,
prev_year_sales,
yoy_growth_pct,
prev_quarter_sales,
qoq_growth_pct
FROM
t3
ORDER BY
CASE
WHEN product IS NULL THEN 1
ELSE 0
END,
product ASC,
CASE
WHEN "year" IS NULL THEN 1
ELSE 0
END,
"year" ASC
案例三:渠道季度销售透视(PIVOT)
这条 SQL 语句的功能是:将销售数据按产品名称、渠道名称、季度分组汇总销售额,然后通过 PIVOT 将渠道×季度的行数据旋转为多列(如 direct_q1、direct_q2、...、telesales_q4 共 16 列),每个单元格是对应产品和渠道季度的销售额合计。
用到的 Oracle 特殊函数和语法:PIVOT 子句是 Oracle 独有的行转列语法,支持 FOR (多列) IN (多值列表) 形式的多列旋转;DECODE()条件映射函数将 channel_id 映射为渠道名称;SUBSTR() 取子串函数从 calendar_quarter_desc 中提取季度。PIVOT 在达梦等数据库中的支持程度不同,且多列 PIVOT 的语法差异更大。
【操作】在 Trae 聊天框中输入:
/sqlazy规划 把下面这句Oracle SQL语句转换成SQLazy脚本
SELECT *
FROM (
SELECT
p.prod_name AS product,
DECODE(s.channel_id, 3, 'Direct', 4, 'Internet', 5, 'Catalog', 9, 'TeleSales', 'Other') AS channel_name,
SUBSTR(t.calendar_quarter_desc, 6, 2) AS quarter,
SUM(s.amount_sold) AS total_sales
FROM sales s
JOIN products p ON s.prod_id = p.prod_id
JOIN times t ON s.time_id = t.time_id
GROUP BY p.prod_name, s.channel_id, SUBSTR(t.calendar_quarter_desc, 6, 2)
)
PIVOT (
SUM(total_sales)
FOR (channel_name, quarter)
IN (
('Direct', 'Q1') AS direct_q1,
('Direct', 'Q2') AS direct_q2,
('Direct', 'Q3') AS direct_q3,
('Direct', 'Q4') AS direct_q4,
('Internet', 'Q1') AS internet_q1,
('Internet', 'Q2') AS internet_q2,
('Internet', 'Q3') AS internet_q3,
('Internet', 'Q4') AS internet_q4,
('Catalog', 'Q1') AS catalog_q1,
('Catalog', 'Q2') AS catalog_q2,
('Catalog', 'Q3') AS catalog_q3,
('Catalog', 'Q4') AS catalog_q4,
('TeleSales', 'Q1') AS telesales_q1,
('TeleSales', 'Q2') AS telesales_q2,
('TeleSales', 'Q3') AS telesales_q3,
('TeleSales', 'Q4') AS telesales_q4
)
)
ORDER BY product
【生成的 SQLazy 脚本】
t1=sales:拼接 prod_id; 关联表 products; prod_id; 拼接列 prod_name; 内关联
t2=拼接 time_id; 关联表 times; time_id; 拼接列 calendar_quarter_desc; 内关联
t3=计算列 条件 ((channel_id == 3) 则 "Direct", (channel_id == 4) 则 "Internet", (channel_id == 5) 则 "Catalog", (channel_id == 9) 则 "TeleSales" 否则 "Other"), 命名 channel_name
t4=计算列 (calendar_quarter_desc 取子串("-")), 命名 quarter
t5=计算列 concat(lower(channel_name), "_", lower(quarter)), 命名 pivot_col
t6=汇总 amount_sold 合计 命名 total_sales; 分组 prod_name 命名 product, pivot_col
t7=旋转 分组 product; 标题列 pivot_col; 取值列 total_sales; 合计
t8=排序 product
【纠错过程】
1 次纠错:运行第 1 行时报错 "内关联:字段不存在"。原因是 "内关联" 是拼接功能的默认连接方式,可以省略不写;当关联列在两表中同名时,关联列名也可省略。将脚本去掉 "内关联" 和重复的关联列名后通过。
2 次纠错:运行第 4 行时报错 "函数 [quarter] 参数错误"。与案例二中的 year 列名问题相同,quarter 是 SQLazy 的内置函数(取日期的季度),作为列名需加引号。将命名 quarter 改为命名 'quarter',以及 t5 行中引用 quarter 处改为 'quarter' 后通过。
【最终正确脚本】
t1=sales:拼接 prod_id; 关联表 products; 拼接列 prod_name
t2=拼接 time_id; 关联表 times; 拼接列 calendar_quarter_desc
t3=计算列 条件 ((channel_id == 3) 则 "Direct", (channel_id == 4) 则 "Internet", (channel_id == 5) 则 "Catalog", (channel_id == 9) 则 "TeleSales" 否则 "Other"), 命名 channel_name
t4=计算列 (calendar_quarter_desc 取子串("-")), 命名 'quarter'
t5=计算列 concat(lower(channel_name),"_",lower('quarter')), 命名 pivot_col
t6=汇总 amount_sold 合计 命名 total_sales; 分组 prod_name 命名 product, pivot_col
t7=旋转 分组 product; 标题列 pivot_col; 取值列 total_sales; 合计
t8=排序 product
【编译生成的达梦 SQL】
WITH t2 AS (
SELECT
sales.prod_id,
sales.channel_id,
sales.time_id,
sales.amount_sold,
products.prod_name,
times.calendar_quarter_desc
FROM
sales
LEFT JOIN products ON sales.prod_id = products.prod_id
LEFT JOIN times ON sales.time_id = times.time_id
),
t4 AS (
SELECT
prod_id,
channel_id,
time_id,
amount_sold,
prod_name,
calendar_quarter_desc,
CASE
WHEN (channel_id = 3) THEN 'Direct'
WHEN (channel_id = 4) THEN 'Internet'
WHEN (channel_id = 5) THEN 'Catalog'
WHEN (channel_id = 9) THEN 'TeleSales'
ELSE 'Other'
END AS channel_name,
SUBSTR(
calendar_quarter_desc,
INSTR(calendar_quarter_desc, ('-')) + LENGTH(('-'))
) AS "quarter"
FROM
t2
),
t6 AS (
SELECT
prod_name AS product,
pivot_col,
SUM(amount_sold) AS total_sales
FROM
(
SELECT
prod_id,
channel_id,
time_id,
amount_sold,
prod_name,
calendar_quarter_desc,
channel_name,
"quarter",
lower(channel_name) + '_' + lower("quarter") AS pivot_col
FROM
t4
) t_4
GROUP BY
prod_name,
pivot_col
),
t7 AS (
SELECT
*
FROM
(
SELECT
product,
pivot_col,
total_sales
FROM
t6
) PIVOT (
SUM(total_sales) FOR pivot_col IN (
'catalog_q1' AS catalog_q1,
'catalog_q2' AS catalog_q2,
'catalog_q3' AS catalog_q3,
'catalog_q4' AS catalog_q4,
'direct_q1' AS direct_q1,
'direct_q2' AS direct_q2,
'direct_q3' AS direct_q3,
'direct_q4' AS direct_q4,
'internet_q1' AS internet_q1,
'internet_q2' AS internet_q2,
'internet_q3' AS internet_q3,
'internet_q4' AS internet_q4,
'other_q1' AS other_q1,
'other_q2' AS other_q2,
'telesales_q1' AS telesales_q1,
'telesales_q2' AS telesales_q2,
'telesales_q3' AS telesales_q3,
'telesales_q4' AS telesales_q4
)
)
)
SELECT
product,
'catalog_q1',
'catalog_q2',
'catalog_q3',
'catalog_q4',
'direct_q1',
'direct_q2',
'direct_q3',
'direct_q4',
'internet_q1',
'internet_q2',
'internet_q3',
'internet_q4',
'other_q1',
'other_q2',
'telesales_q1',
'telesales_q2',
'telesales_q3',
'telesales_q4'
FROM
t7
ORDER BY
CASE
WHEN product IS NULL THEN 1
ELSE 0
END,
product ASC
案例四:科室手术统计排名
这条 SQL 语句的功能是:统计各科室医生的手术数据,计算难度等级描述、科室内排名(RANK 和 DENSE_RANK 两种)、手术量占科室比例、与上一名的差值、上一名医生姓名、科室平均手术量、以及业绩标签(是否达到科室平均)。
用到的 Oracle 特殊函数和语法:RANK()和 DENSE_RANK() 两种排名函数(区别在于并列时是否占用名次);RATIO_TO_REPORT()占比函数(Oracle 独有,计算当前值占分区合计的比例);AVG() OVER (PARTITION BY) 窗口平均;LAG()取前一行;DECODE() 条件映射;GREATEST() 取最大值函数与 DECODE 组合实现 "大于等于平均则优秀" 的判断逻辑。这些函数中,RATIO_TO_REPORT 和 GREATEST+DECODE 的组合是 Oracle 特有写法,在其他数据库中需要用不同方式实现。
【操作】在 Trae 聊天框中输入:
/sqlazy规划 把下面这句Oracle SQL语句转换成SQLazy脚本
SELECT
dept_name,
doctor_name,
surgery_count,
DECODE(difficulty_level,
1, '一级(简单)',
2, '二级(普通)',
3, '三级(中等)',
4, '四级(复杂)',
5, '五级(疑难)',
'未分级') AS difficulty_desc,
RANK() OVER (PARTITION BY dept_name ORDER BY surgery_count DESC) AS rank_in_dept,
DENSE_RANK() OVER (PARTITION BY dept_name ORDER BY surgery_count DESC) AS dense_rank_in_dept,
ROUND(RATIO_TO_REPORT(surgery_count) OVER (PARTITION BY dept_name) * 100, 2) AS pct_of_dept,
NVL(surgery_count - LAG(surgery_count, 1) OVER (PARTITION BY dept_name ORDER BY surgery_count DESC), 0) AS gap_from_prev,
LAG(doctor_name, 1) OVER (PARTITION BY dept_name ORDER BY surgery_count DESC) AS prev_doctor_name,
ROUND(AVG(surgery_count) OVER (PARTITION BY dept_name), 2) AS dept_avg,
DECODE(
GREATEST(surgery_count, AVG(surgery_count) OVER (PARTITION BY dept_name)),
surgery_count, '优秀',
'待提升'
) AS performance_tag
FROM surgery_stats
ORDER BY dept_name, surgery_count DESC
【生成的 SQLazy 脚本】
t1=surgery_stats:排序 dept_name, surgery_count 降序
t2=计算列 条件 ((difficulty_level == 1) 则 "一级(简单)", (difficulty_level == 2) 则 "二级(普通)", (difficulty_level == 3) 则 "三级(中等)", (difficulty_level == 4) 则 "四级(复杂)", (difficulty_level == 5) 则 "五级(疑难)" 否则 "未分级"), 命名 difficulty_desc
t3=排名 surgery_count 降序; 命名 rank_in_dept; 分区 dept_name
t4=排名 surgery_count 降序; 命名 dense_rank_in_dept; 中式; 分区 dept_name
t5=计算列 surgery_count, 占比, 命名 tmp_pct; surgery_count, 平均, 命名 tmp_avg; 分区 dept_name
t6=计算列 round(tmp_pct * 100, 2), 命名 pct_of_dept; round(tmp_avg, 2), 命名 dept_avg
t7=计算列 nvl(surgery_count - surgery_count[-1], 0), 命名 gap_from_prev; doctor_name[-1], 命名 prev_doctor_name; 分区 dept_name
t8=计算列 条件 (surgery_count >= dept_avg 则 "优秀" 否则 "待提升"), 命名 performance_tag
t9=导出表 dept_name, doctor_name, surgery_count, difficulty_desc, rank_in_dept, dense_rank_in_dept, pct_of_dept, gap_from_prev, prev_doctor_name, dept_avg, performance_tag
【纠错过程】
一次就生成完全正确的脚本,无需纠错。
【编译生成的达梦 SQL】
WITH t2 AS (
SELECT
dept_name,
doctor_name,
surgery_count,
difficulty_level,
CASE
WHEN (difficulty_level = 1) THEN '一级(简单)'
WHEN (difficulty_level = 2) THEN '二级(普通)'
WHEN (difficulty_level = 3) THEN '三级(中等)'
WHEN (difficulty_level = 4) THEN '四级(复杂)'
WHEN (difficulty_level = 5) THEN '五级(疑难)'
ELSE '未分级'
END AS difficulty_desc
FROM
surgery_stats
),
sub__3 AS (
SELECT
sub__2.*,
RANK() OVER (
PARTITION BY dept_name
ORDER BY
surgery_count DESC
) AS rank_in_dept
FROM
t2 sub__2
),
t3 AS (
SELECT
dept_name,
doctor_name,
surgery_count,
difficulty_level,
difficulty_desc,
rank_in_dept
FROM
sub__3
),
sub__5 AS (
SELECT
sub__4.*,
DENSE_RANK() OVER (
PARTITION BY dept_name
ORDER BY
surgery_count DESC
) AS dense_rank_in_dept
FROM
t3 sub__4
),
t5 AS (
SELECT
dept_name,
doctor_name,
surgery_count,
difficulty_level,
difficulty_desc,
rank_in_dept,
dense_rank_in_dept,
(
surgery_count * 1.0 / SUM(surgery_count) OVER (PARTITION BY dept_name)
) AS tmp_pct,
AVG(surgery_count) OVER (PARTITION BY dept_name) AS tmp_avg
FROM
sub__5
),
t7 AS (
SELECT
dept_name,
doctor_name,
surgery_count,
difficulty_level,
difficulty_desc,
rank_in_dept,
dense_rank_in_dept,
tmp_pct,
tmp_avg,
round(tmp_pct * 100, 2) AS pct_of_dept,
round(tmp_avg, 2) AS dept_avg,
COALESCE(
NULLIF(
surgery_count - LAG(surgery_count, 1) OVER (
PARTITION BY dept_name
ORDER BY
NULL
),
''
),
NULLIF(0, '')
) AS gap_from_prev,
LAG(doctor_name, 1) OVER (
PARTITION BY dept_name
ORDER BY
NULL
) AS prev_doctor_name
FROM
t5
),
t8 AS (
SELECT
dept_name,
doctor_name,
surgery_count,
difficulty_level,
difficulty_desc,
rank_in_dept,
dense_rank_in_dept,
tmp_pct,
tmp_avg,
pct_of_dept,
dept_avg,
gap_from_prev,
prev_doctor_name,
CASE
WHEN surgery_count >= dept_avg THEN '优秀'
ELSE '待提升'
END AS performance_tag
FROM
t7
)
SELECT
dept_name,
doctor_name,
surgery_count,
difficulty_desc,
rank_in_dept,
dense_rank_in_dept,
pct_of_dept,
gap_from_prev,
prev_doctor_name,
dept_avg,
performance_tag
FROM
t8
五、总结
1、用 AI+SQLazy 解决国产数据库移植的流程
通过以上四个案例,可以总结出国外数据库到国产数据库 SQL 移植的流程:
第一步:发起指令。发起 "/sqlazy 规划 把下面这句 Oracle SQL 语句转换成 SQLazy 脚本" 的指令,再附上 SQL 代码。
第二步:生成初稿。Trae 按四步流程(能力梳理→需求拆解→功能匹配→代码实现)输出 SQLazy 脚本初稿。
第三步:测试验证。在 SQLazy IDE 中导入测试数据,分步运行脚本,对比中间结果与期望值。
第四步:反馈纠错。若有语法错误或逻辑错误,把 SQLazy IDE 报出的错误信息原文反馈给 Trae,让其修正。如此反复直到能正确运行。
第五步:编译交付。验证通过后,在 SQLazy IDE 中选择目标数据库,一键编译生成对应的 SQL,交付生产环境使用。
之后保存这份 SQLazy 脚本以供下次修改和交接传承使用,不必保留难懂的 SQL 脚本。
四个案例覆盖了 Oracle 到国产数据库移植中的典型场景:CTE 多层嵌套与窗口函数(案例一)、MODEL 子句(案例二)、PIVOT 行转列(案例三)、多排名函数与占比计算(案例四)。其中案例一经历 1 次纠错后通过,案例二和案例三经历 2 次纠错后通过,案例四无错通过。
纠错过程中暴露出两类典型问题:一是 SQLazy 保留字冲突——year 和 quarter 等列名与 SQLazy 内置函数同名,需加引号区分;二是语法细节差异。这些问题都有一个共同特点:SQLazy 的报错信息非常精确,反馈给 Trae 后通常一次就能修正。
2、总结与展望
归根结底,国产化移植的核心挑战不在于数据搬迁,而在于 SQL 方言转换。AI+SQLazy 的组合提供了一条可靠路径:AI 理解语义并生成可读的中间脚本,确定性引擎编译出目标数据库的 SQL。AI 转换脚本、人工审核、引擎验证执行,三方协作,让 SQL 平滑落地到国产数据库。
这种方案覆盖面更广,虽然本文以 Oracle-> 达梦为例,但实际应用中并不只限于此,AI 可以理解几乎所有常见国外数据库,而采用编译方式生成国产数据库 SQL 时也不依赖于语料素材的多寡。再考虑到 SQLazy 脚本易调试易审计的特性,整体移植的成本也能更低,并获得更高的准确率。
更重要的是,AI+SQLazy 并不是单纯地低成本实现了 SQL 移植工作,这种方案还同时实现了“分析逻辑的持久化”,用简明易读的 SQLazy 脚本取代原本晦涩冗长的 SQL 代码,极大地提升了分析的可审计性和可传承性,代码才能成为资产,后续进一步修改或换库都变得非常轻松。
不过,我们也要清楚地看到,仅仅做 SQL 移植仅能解决功能兼容问题,而不能解决移植后带来性能问题,这受限于目标数据库本身的能力。为此,对于移植后性能锐降的 SQL,我们还可以采用 SQLazy 的基础技术——集算器 SPL 来兜底,SPL 拥有比 SQL 更多的高性能算法,经常可以跑出比 SQL 高出数量级的性能,但因其体系和 SQL 不同,并不能自动化简单移植,需要有一定程度的人工介入。好在,有性能问题的 SQL 通常是少数,整体工作量也不是很大,我们将在后续再做相应的实践。
