【问题标题】:Extract a field from a column (in json) and Concat them in SQL Server从列中提取一个字段(在 json 中)并在 SQL Server 中连接它们
【发布时间】:2021-01-15 10:27:43
【问题描述】:

我想在 json 列中提取和连接一个字段。

我有一个包含两个字段的表格:

  • 身份证
  • JsonColumn

在 json 列中,我可以有 x 对象,其中包含 2 个字段(NameType

例如,在我的表中我可以有:

我想要做的是在 json 列中提取和连接字段 Name。 所以我会有:

不要犹豫,分享您的意见。 (我正在考虑使用 openJson 进行一个大循环,但我担心之后查询会很长)。

【问题讨论】:

  • 第一个想法:在一个 CTE 中提取所有带有 id 的 JSON 名称。第二个 CTE 按 id 进行递归连接。输出然后按 id 与串联名称分组?
  • 我不是关系数据库方面的专家,但是为什么你还要将 JSON 存储在数据库中呢?关系数据库的想法是每个单元格/列存储(或多或少)一个值。
  • 您的 SQL Server 版本是多少?
  • 感谢您的回复。 Knut:我不确定在 cte 中提取 json 名称的想法(将 json 列放在 viariable 中并为每一行使用 OpenJson?) Arun:SQL server Developer 2016 更新了 SP2
  • @MoonLightFlower 是的。第一部分是使用 OUTER APPLY 和 WITH structuring 提取 JSON(这里有很多示例)。从半结构化转换为关系。第二部分是使用递归技术将关系部分转换回所需的连接。第三是期望输出的输出。

标签: json sql-server concatenation


【解决方案1】:

您需要使用OPENSJON() 解析JSON 内容以提取"Name" 键,然后聚合值。对于 SQL Server 2016,您需要使用 FOR XML PATH 进行聚合。

表:

SELECT Id, JsonColumn
INTO Data
FROM (VALUES
   (1, '[{"Name": "Matthew", "Type":"Public"}, {"Name": "Rachel", "Type":"Private"}]'),
   (2, '[{"Name": "Sample", "Type":"Private"}]')
) v (Id, JsonColumn)

声明:

SELECT 
   Id, 
   Name = STUFF(
      (
      SELECT ',' + Name 
      FROM OPENJSON(JsonColumn) WITH (Name varchar(100) '$.Name')
      FOR XML PATH('')
      ), 1, 1, ''
   )
FROM Data

结果:

Id  JsonColumn
------------------
1   Matthew,Rachel
2   Sample

【讨论】:

  • 您介意我将您的 STUFF 示例添加到我的演示代码中并回答吗?
  • @KnutBoehnert,请随意使用这部分答案。
【解决方案2】:

粗略的方法(目前我没有足够的时间为代码提供有效的 SQL)。 要一步完成(最好的视图),请使用 CTE 方法来错开步骤。这会生成更多代码,并且随着时间的推移可以更轻松地进行修改。这是一种权衡。

  1. 递归方法

第一步:

提取具有 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
  1. 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
;
  1. 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
;

【讨论】:

  • 谢谢你的提示 :)
猜你喜欢
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-04
相关资源
最近更新 更多