【问题标题】:SQL Server: How to flatten nested arrays by merging values usingSQL Server:如何通过使用合并值来展平嵌套数组
【发布时间】:2021-08-19 11:47:01
【问题描述】:

我有 10000 个具有不同 ID 的 json,每个都有 10000 个名称。如何通过使用 SQL Server 合并值来展平嵌套数组? Jsons 可以用任何语言阅读,我正在寻找任何可以在使用 spark 连接器时转换数据的 SQL 方言。我使用许多 SQL 方言,包括不限制 Spark SQL、Postgresql、MySql、SQLite 和 SQL Server...

注意:Martijn Pieters 要求我为每种 SQL 方言创建特定的副本,因此这是针对 SQL Server 的。

注意事项:

  • 输入数据帧有超过 10000 个列 name_1_a、name_1000_xx 所以列(数组)名称不能硬编码,因为它需要写入 10000 个名称
  • iddateval 在所有列和所有 json 中始终具有相同的命名约定
  • 数组大小可以变化,但dateval 始终存在,因此可以硬编码
  • date 在每个数组中可以不同,例如 name_1_a 以 2001 开头,但 id == 1 的 name_10000_xvz 以 2000 开头,finnish 以 2004 开头,但是 id == 2 以 1990 开头并以 2004 结束

输入df:

root
 |-- id: long (nullable = true)
 |-- name_10000_xvz: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- date: long (nullable = true)
 |    |    |-- val: long (nullable = true)
 |-- name_1_a: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- date: long (nullable = true)
 |    |    |-- val: long (nullable = true)
 |-- name_1_b: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- date: long (nullable = true)
 |    |    |-- val: long (nullable = true)
 |-- name_2_a: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- date: long (nullable = true)
 |    |    |-- val: long (nullable = true)

+---+------------------------------------------------------------------------+---------------------------------+---------------------------------+------------------------------------+
|id |name_10000_xvz                                                          |name_1_a                         |name_1_b                         |name_2_a                            |
+---+------------------------------------------------------------------------+---------------------------------+---------------------------------+------------------------------------+
|2  |[{1990, 39}, {2000, 30}, {2001, 31}, {2002, 32}, {2003, 33}, {2004, 34}]|[{2001, 1}, {2002, 2}, {2003, 3}]|[{2001, 4}, {2002, 5}, {2003, 6}]|[{2001, 21}, {2002, 22}, {2003, 23}]|
|1  |[{2000, 30}, {2001, 31}, {2002, 32}, {2003, 33}]                        |[{2001, 1}, {2002, 2}, {2003, 3}]|[{2001, 4}, {2002, 5}, {2003, 6}]|[{2001, 21}, {2002, 22}, {2003, 23}]|
+---+------------------------------------------------------------------------+---------------------------------+---------------------------------+------------------------------------+

所需的输出df:

+---+---------+----------+-----------+---------+----------------+
|id |   date  | name_1_a | name_1_b  |name_2_a | name_10000_xvz |
+---+---------+----------+-----------+---------+----------------+
|1  |   2000  |     0    |    0      |   0     |        30      |
|1  |   2001  |     1    |    4      |   21    |        31      |
|1  |   2002  |     2    |    5      |   22    |        32      |
|1  |   2003  |     3    |    6      |   23    |        33      |
|2  |   1990  |     0    |    0      |   0     |        39      |
|2  |   2000  |     0    |    0      |   0     |        30      |
|2  |   2001  |     1    |    4      |   21    |        31      |
|2  |   2002  |     2    |    5      |   22    |        32      |
|2  |   2003  |     3    |    6      |   23    |        33      |
|2  |   2004  |     0    |    0      |   0     |        34      |
+---+---------+----------+-----------+---------+----------------+

以下是输入df的json:

1.json

{  "id": 1,  "name_1_a": [    {      "date": 2001,      "val": 1    },    {      "date": 2002,      "val": 2    },    {      "date": 2003,      "val": 3    }  ],  "name_1_b": [    {      "date": 2001,      "val": 4    },    {      "date": 2002,      "val": 5    },    {      "date": 2003,      "val": 6    }  ],  "name_2_a": [    {      "date": 2001,      "val": 21    },    {      "date": 2002,      "val": 22    },    {      "date": 2003,      "val": 23    }  ],   "name_10000_xvz": [    {        "date": 2000,        "val": 30    },    {      "date": 2001,      "val": 31    },    {      "date": 2002,      "val": 32    },    {      "date": 2003,      "val": 33    }  ]}

2.json

{  "id": 2,  "name_1_a": [    {      "date": 2001,      "val": 1    },    {      "date": 2002,      "val": 2    },    {      "date": 2003,      "val": 3    }  ],  "name_1_b": [    {      "date": 2001,      "val": 4    },    {      "date": 2002,      "val": 5    },    {      "date": 2003,      "val": 6    }  ],  "name_2_a": [    {      "date": 2001,      "val": 21    },    {      "date": 2002,      "val": 22    },    {      "date": 2003,      "val": 23    }  ],  "name_10000_xvz": [    {        "date": 1990,        "val": 39      },    {      "date": 2000,      "val": 30    },    {      "date": 2001,      "val": 31    },    {      "date": 2002,      "val": 32    },    {      "date": 2003,      "val": 33    },    {      "date": 2004,      "val": 34    }  ]}}

【问题讨论】:

  • posted this earlier 怎么又发帖了?有一个edit 功能。
  • @Larnu,您引用的帖子已被删除,因为某些原因无法编辑,这是专门为您和 SQL Server 准备的。提前感谢您的帮助!!!
  • 那么,如果我正确读取了您的示例数据,您需要动态列名吗?如果是这样,您将需要为此使用动态 SQL。还是您的列将始终为name_1_aname_1_bname_2_aname_10000_xvz
  • 它们大约有 10000 个名称,但总是相同,因此我可以提前获取名称列表,并且可以通过循环这些名称来创建 SQL 字符串
  • 那么动态SQL...这将是一团糟。

标签: sql sql-server


【解决方案1】:

好的,所以我们需要在这里解决 2 个“问题”。首先,您需要动态数量的列,因为您不知道您的数据名称是什么。这意味着您需要动态 SQL。

接下来的问题是,不是每个名字都有一个每年的值,所以我们还需要有一个“年份”表,我们可以从中 LEFT JOIN 以便我们有一行代表每个名字。

因此,这将是真的混乱,但它可以做到。我已经把 cmets 留在了我能做的地方,但我真正建议的最好的事情是花时间阅读 SQL,PRINTing/SELECTing 动态语句,并了解它的作用。

首先让我们构建一个静态版本,这样您就可以看到它的样子。所以在这里我使用 CTE 来获取所有年份,然后使用另一个从 JSON 中获取标准化格式的数据。最后使用条件聚合取消透视数据:

--Sample JSON
DECLARE @JSON nvarchar(MAX) = N'{  "id": 1,  "name_1_a": [    {      "date": 2001,      "val": 1    },    {      "date": 2002,      "val": 2    },    {      "date": 2003,      "val": 3    }  ],  "name_1_b": [    {      "date": 2001,      "val": 4    },    {      "date": 2002,      "val": 5    },    {      "date": 2003,      "val": 6    }  ],  "name_2_a": [    {      "date": 2001,      "val": 21    },    {      "date": 2002,      "val": 22    },    {      "date": 2003,      "val": 23    }  ],   "name_10000_xvz": [    {        "date": 2000,        "val": 30    },    {      "date": 2001,      "val": 31    },    {      "date": 2002,      "val": 32    },    {      "date": 2003,      "val": 33    }  ]}';

--Get distinct Years
WITH Years AS(
    SELECT DISTINCT V.date
    FROM OPENJSON(@JSON) J
         CROSS APPLY (SELECT *
                      FROM OPENJSON(J.[value]) 
                           WITH(date int) 
                      WHERE ISJSON(J.[value]) = 1) V),
--Get Data
Data AS(
    SELECT J.[key] AS [name],
           V.date,
           V.val 
    FROM OPENJSON(@JSON) J
         CROSS APPLY (SELECT *
                      FROM OPENJSON(J.[value]) 
                           WITH(date int,
                                val int) 
                      WHERE ISJSON(J.[value]) = 1) V)
--Final Select and Unpivot
SELECT JSON_VALUE(@JSON, '$.id') AS ID,
       Y.Date,
       ISNULL(MAX(CASE D.[name] WHEN 'name_1_a' THEN D.val END),0) AS name_1_a,
       ISNULL(MAX(CASE D.[name] WHEN 'name_1_b' THEN D.val END),0) AS name_1_b,
       ISNULL(MAX(CASE D.[name] WHEN 'name_2_a' THEN D.val END),0) AS name_2_a,
       ISNULL(MAX(CASE D.[name] WHEN 'name_10000_xvz' THEN D.val END),0) AS name_10000_xvz
FROM Years Y
     LEFT JOIN Data D ON Y.Date = D.Date
GROUP BY Y.Date;

但是,正如我所提到的,这不是动态的。因此,这是它变得更加混乱的地方。对于以下内容,我假设您使用的是最新版本的 SQL Server,因此可以访问STRING_AGG(如果没有,您需要使用旧的FOR XML PATHSTUFF 方法):

--Sample JSON
DECLARE @JSON nvarchar(MAX) = N'{  "id": 1,  "name_1_a": [    {      "date": 2001,      "val": 1    },    {      "date": 2002,      "val": 2    },    {      "date": 2003,      "val": 3    }  ],  "name_1_b": [    {      "date": 2001,      "val": 4    },    {      "date": 2002,      "val": 5    },    {      "date": 2003,      "val": 6    }  ],  "name_2_a": [    {      "date": 2001,      "val": 21    },    {      "date": 2002,      "val": 22    },    {      "date": 2003,      "val": 23    }  ],   "name_10000_xvz": [    {        "date": 2000,        "val": 30    },    {      "date": 2001,      "val": 31    },    {      "date": 2002,      "val": 32    },    {      "date": 2003,      "val": 33    }  ]}';

--Variables for dynamic SQL
DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13) + NCHAR(10);

DECLARE @Delimiter varchar(20) = N',' + @CRLF + N'       ';


--You'll note the start is all the same
SET @SQL = N'--Get disinct Years' + @CRLF + 
           N'WITH Years AS(' + @CRLF + 
           N'    SELECT DISTINCT V.date' + @CRLF + 
           N'    FROM OPENJSON(@JSON) J' + @CRLF + 
           N'         CROSS APPLY (SELECT *' + @CRLF + 
           N'                      FROM OPENJSON(J.[value]) ' + @CRLF + 
           N'                           WITH(date int) ' + @CRLF + 
           N'                      WHERE ISJSON(J.[value]) = 1) V),' + @CRLF + 
           N'--Get Data' + @CRLF + 
           N'Data AS(' + @CRLF + 
           N'    SELECT J.[key] AS [name],' + @CRLF + 
           N'           V.date,' + @CRLF + 
           N'           V.val ' + @CRLF + 
           N'    FROM OPENJSON(@JSON) J' + @CRLF + 
           N'         CROSS APPLY (SELECT *' + @CRLF + 
           N'                      FROM OPENJSON(J.[value]) ' + @CRLF + 
           N'                           WITH(date int,' + @CRLF + 
           N'                                val int) ' + @CRLF + 
           N'                      WHERE ISJSON(J.[value]) = 1) V)' + @CRLF + 
           N'--Final Select and Unpivot' + @CRLF + 
           N'SELECT JSON_VALUE(@JSON, ''$.id'') AS ID,' + @CRLF +
           N'       Y.Date,' + @CRLF +
           (SELECT STRING_AGG(N'ISNULL(MAX(CASE D.[name] WHEN ' + QUOTENAME(J.[key],'''') + N' THEN D.val END),0) AS ' + QUOTENAME(J.[key]),@Delimiter)
            FROM OPENJSON(@JSON) J) + @CRLF +
           N'FROM Years Y' + @CRLF +
           N'     LEFT JOIN Data D ON Y.Date = D.Date' + @CRLF +
           N'GROUP BY Y.Date;';

PRINT @SQL; --YOur best friend for debugging

EXEC sys.sp_executesql @SQL, N'@JSON nvarchar(MAX)', @JSON;

db<>fiddle

【讨论】:

    猜你喜欢
    • 2021-10-20
    • 2022-12-17
    • 1970-01-01
    • 2022-07-10
    • 2016-02-26
    • 2017-12-26
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多