从 SQL 到 SPL:不确定个数但有规则的列转行
MSSQL 数据库有一个外部定时生成的不规范的表,每次生成 N 对字段和一条记录,每对字段名都是下划线分隔的两部分,前半部分相同但未知,后半部分分别是固定的 name 和 age。
X1AB_name |
X1AB_age |
Y2AL_name |
Y2AL_age |
Z2AL_name |
Z2AL_age |
Todd |
10 |
Brad |
20 |
Will |
30 |
现在要把每对字段名的前半部分和对应的字段值拼成一条记录,共 N 条记录。可以先把这条记录由列转行,再把每 2 条记录拼成一行。
id |
name |
age |
X1AB |
Todd |
10 |
Y2AL |
Brad |
20 |
Z2AL |
Will |
30 |
SQL 语句:
SELECT V.id,
MAX(CASE V.subkey WHEN N'name' THEN OJ.value END) AS name,
MAX(CASE V.subkey WHEN N'age' THEN OJ.value END) AS age
FROM (SELECT *
FROM dbo.tb YT
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) J(JSON)
CROSS APPLY OPENJSON (J.JSON) OJ
CROSS APPLY (VALUES(LEFT(OJ.[key],NULLIF(CHARINDEX('_',OJ.[Key]),0)-1), STUFF(OJ.[Key],1,NULLIF(CHARINDEX('_',OJ.[Key]),0),'')))V(id,subkey)
GROUP BY V.id;
SQL 有 pivot 函数可以进行列转行,但必须写出列名,动态生成列名会非常复杂,这里只能转换思路,先把记录转成 json 串,再分别取多个”字段名: 字段值”,再用叉乘拼成多条记录,代码难度高。SQL 的转置很不灵活,这里改用 max…group by 的方法间接实现,代码也有点啰嗦。
SPL 代码要简单易懂很多:
A |
|
1 |
=mssql.query("select * from tb”) |
2 |
=A1.pivot@r(;col,row) |
3 |
=A2.group((#-1)\2) |
4 |
=A3.new(substr@l(~1.col.,"_"):id, ~1.row:name, ~2.row:age) |
A1:加载数据。
A2:pivot 函数列转行,不必写出列名,新二维表有 6 条记录 2 个字段,字段 col 存储原字段名,row 存储原字段值。
A3:简单实现每 2 行分一组,而且可以保留分组子集不汇总。# 是行号,\ 是除法取整。
A4:按位置从每组记录取值,形成新二维表。~1 表示组内第 1 条记录。
问题来源:https://stackoverflow.com/questions/78269917/unpivoting-data-in-sql-from-json
https://c.scudata.com/article/1736857154436