粗略的方法(目前我没有足够的时间为代码提供有效的 SQL)。
要一步完成(最好的视图),请使用 CTE 方法来错开步骤。这会生成更多代码,并且随着时间的推移可以更轻松地进行修改。这是一种权衡。
- 递归方法
第一步:
提取具有 ID 和名称的关系记录。将 OPENJSON 与已定义的表结构一起使用,其中仅提取名称(其余的可以忽略或保留为附加 JSON)。
第二步:
使用第一步的输出并转换为递归连接。使用变量连接以强制使用过程。在视图中执行此操作需要定义锚点和结束条件。不太确定,因为它很棘手。
在 CTE 部分中,这需要一个锚元素与所有其他元素联合。实际上,这是按选定的关键字段分组的。
第三步:
按关键字段输出已完成的递归。
快速演示代码
DECLARE
@Demo TABLE
(
id_col tinyint identity(1,1),
dsc_json nvarchar(max)
)
;
INSERT INTO
@Demo
(
dsc_json
)
SELECT N'[{"Name":"Celery","Type":"Vegetable"}, {"Name":"Tomato","Type":"Fruit"}]'
UNION
SELECT N'[{"Name":"Potato","Type":"Vegetable"}]'
UNION
SELECT N'[{"Name":"Cherry","Type":"Fruit"}, {"Name":"Apple","Type":"Fruit"}]'
;
SELECT
*
FROM
@Demo
;
-- extract JSON
SELECT
demo.id_col,
jsond.dsc_name,
Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name ASC ) AS val_row_asc,
Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name DESC ) AS val_row_desc
FROM
@Demo AS demo
CROSS APPLY OPENJSON( demo.dsc_json )
WITH
(
dsc_name nvarchar(100) '$.Name'
) AS jsond
;
WITH
cte_json
(
id_col,
dsc_name,
val_row_asc,
val_row_desc
)
AS
(
SELECT
demo.id_col,
jsond.dsc_name,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name ASC ) AS int ) AS val_row_asc,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name DESC ) AS int ) AS val_row_desc
FROM
@Demo AS demo
CROSS APPLY OPENJSON( demo.dsc_json )
WITH
(
dsc_name nvarchar(100) '$.Name'
) AS jsond
),
cte_concat
(
id_col,
dsc_names,
val_row_asc,
val_row_desc
)
AS
( -- anchor first
-- - emtpy string per ID
SELECT
anchor.id_col,
Cast( N'' AS nvarchar(500) ) AS names,
Cast( 0 AS int) AS val_row_asc,
Cast( -1 AS int ) AS val_row_desc
FROM
cte_json AS anchor
WHERE -- anchor criteria
val_row_asc = 1
UNION ALL
SELECT
anchor.id_col,
Cast( anchor.dsc_names + N', ' + element.dsc_name AS nvarchar(500) ) AS names,
element.val_row_asc,
element.val_row_desc
FROM
cte_json AS element
INNER JOIN cte_concat AS anchor
ON anchor.id_col = element.id_col
AND anchor.val_row_asc = element.val_row_asc -1
)
SELECT
cte.id_col,
Right( cte.dsc_names, Len( cte.dsc_names ) -2 ) AS dsc_names,
cte.val_row_desc
FROM
cte_concat AS cte
WHERE -- only latest result
cte.val_row_desc = 1
ORDER BY
cte.id_col ASC
;
允许的附加行号:
- 定义递归连接的起点和终点 = val_row_asc
- 定义最新结果的“定义” = val_row_desc
- XML 路径的东西......
由于 Zhorov 的回答,这种方法适用于所有版本,并且比递归部分更容易阅读。在上面代码的第一部分奠定的基础上工作(或直接在之后)。
WITH
cte_json
(
id_col,
dsc_name,
val_row_asc,
val_row_desc
)
AS
(
SELECT
demo.id_col,
jsond.dsc_name,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name ASC ) AS int ) AS val_row_asc,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name DESC ) AS int ) AS val_row_desc
FROM
@Demo AS demo
CROSS APPLY OPENJSON( demo.dsc_json )
WITH
(
dsc_name nvarchar(100) '$.Name'
) AS jsond
)
SELECT
cte_outer.id_col,
Stuff(
( SELECT
',' + cte_inner.dsc_name
FROM
cte_json AS cte_inner
WHERE
cte_inner.id_col = cte_outer.id_col
FOR XML PATH('')
), 1, 1, ''
) AS dsc_names
FROM
cte_json AS cte_outer
GROUP BY
cte_outer.id_col
;
- String_agg
此方法仅适用于 SQL Server 2017 及更高版本。它是上面代码的延续。
WITH
cte_json
(
id_col,
dsc_name,
val_row_asc,
val_row_desc
)
AS
(
SELECT
demo.id_col,
jsond.dsc_name,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name ASC ) AS int ) AS val_row_asc,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name DESC ) AS int ) AS val_row_desc
FROM
@Demo AS demo
CROSS APPLY OPENJSON( demo.dsc_json )
WITH
(
dsc_name nvarchar(100) '$.Name'
) AS jsond
)
SELECT
cte.id_col,
String_agg( cte.dsc_name, ',' ) AS dsc_names
FROM
cte_json AS cte
GROUP BY
cte.id_col
;