操作包含键/值对的列

And*_*ndy 7 join sql-server view sql-server-2016 enterprise-edition

我正在通过复制的 SQL Server 数据库访问和创建来自供应商的报告。他们做了一些我一直试图解决的绝对疯狂的事情,但这个问题占据了上风。

他们有一个包含许多标准列的表。但是这个表还有一个叫做“数据”的列。该列是传统的“文本”数据类型,它包含一个巨大的(数百个)键/值对列表。每对由 CRLF 分隔,键和值由等号分隔。例子:

select myTable.[data] from myTable where tblKey = 123
Run Code Online (Sandbox Code Playgroud)

结果:

Key 1=Value 1
Key 2=Value 2
Key 3=Value 3
...
Key 500=Value 500
Run Code Online (Sandbox Code Playgroud)

我正在尝试确定将该列分解为可用数据表的最有效方法。最终目标是能够以将表键以及指定键/值作为列/字段返回的方式查询表:

tblKey | [Key 1] | [Key 3] | [Key 243]
-------|---------|---------|-----------
 123     Value 1   Value 3   Value 243
 124     Value 1   Value 3   Value 243
 125     Value 1   Value 3   Value 243
Run Code Online (Sandbox Code Playgroud)

有没有办法将该列塑造成一个视图?我无法想象一个函数会特别有效,但我确信我可以使用 string_split 或类似的东西来解析事情。有没有人以前遇到过这种暴行并找到了一种将其操纵成可用数据的好方法?

编辑以添加dbfiddle示例数据。

数据是从供应商的来源复制的,因此我无法创建新表。我可以创建视图、过程和函数。这就是我正在寻找一种体面的方式来完成的建议。

McN*_*ets 5

更新

如果正如您在自己的答案中发布的那样,您可以使用 UDF 来获取特定的键值,那么我建议您这样做:(您不需要拆分所有键/值,也不需要再次阅读该表,您可以使用文本函数来获取它。)

CREATE FUNCTION fnGetKey(@Data text, @Key varchar(20))
RETURNS varchar(100)
AS
BEGIN

  RETURN
  (
  SELECT 
      SUBSTRING (
                  @Data,
                  /* Position of first '=' after key + 1 */
                  CHARINDEX('=', @Data, PATINDEX('%' + @key + '%', @Data)) + 1,
                  /* Lenght, Position of first chr(13) after key less previuos value - 1 */
                  (CHARINDEX(CHAR(13), @Data, PATINDEX('%' + @key + '%', @Data)) 
                  - 
                  CHARINDEX('=', @Data, PATINDEX('%' + @key + '%', @Data))) - 1
                )
  )

END

SELECT
    FruitID, Name, Description,
    dbo.fnGetKey([Data], 'key 2') as [key 2],
    dbo.fnGetKey([Data], 'key 4') as [key 4]
FROM
    [Fruit];
Run Code Online (Sandbox Code Playgroud)
水果ID | 姓名 | 说明 | 关键 2 | 键 4  
------: | :----- | :---------- | :------ | :------
      1 | 香蕉 | 美味 | 值 2 | 值 4
      2 | 梨 | 罗顿 | 值 2 | 值 4
      3 | 猕猴桃 | 好的| 值 2 | 值 4

db<>在这里摆弄

原答案

我能想到的唯一解决方案是拆分键/值,然后将其旋转以获得所需的结果。

不幸的是,有一些不便之处:

  • STRING_SPLIT 不适用于text列。因此,您必须先将其转换为 to,varchar然后才能操作它。
  • STRING_SPLIT 需要一个nchar(1)or nvarchar(1),因此您应该用CHAR(3)+CHAR(10)单个字符替换。
  • PIVOT 上的聚合函数对数值效果更好,那么您应该转换Value为某种数值数据类型。
  • PIVOT 需要已知数量的列,在我的示例中,我使用了其中的一些列,但除非您更愿意处理动态查询,否则您应该编写整个序列。

这是我使用您的示例数据所得到的:

WITH KP AS
(
    SELECT FruitID, Name, Description, value as KPair
    FROM   Fruit
    CROSS APPLY STRING_SPLIT(REPLACE(CAST(Data AS varchar(max)), CHAR(13)+CHAR(10), ','), ',') /* STRING_SPLIT only allows nchar(1),  varchar(1) */
)
, KP1 AS
(
  SELECT
      FruitID,  
      SUBSTRING(KPair, 5, CHARINDEX('=', KPair) - 5) AS [Key],
      SUBSTRING(KPair, CHARINDEX('=', KPair) + 7, LEN(KPair) - CHARINDEX('=', KPair) - 6) AS [Value]
  FROM
      KP
)
SELECT [FruitID], [1],[2],[3],[4],[5]
FROM   KP1
PIVOT (MAX([Value]) FOR [Key] IN ([1],[2],[3],[4],[5])) AS PVT;
Run Code Online (Sandbox Code Playgroud)

第一个 CTE 拆分每个Key X=Value Y. 第二个切掉这个值,得到每个[Key]和[Value]。最终的 PIVOT 在列中组成最终结果。

水果ID | 1 | 2 | 3 | 4 | 5
------: | :- | :- | :- | :- | :-
      1 | 1 | 2 | 3 | 4 | 5
      2 | 1 | 2 | 3 | 4 | 5
      3 | 1 | 2 | 3 | 4 | 5

db<>在这里摆弄

注意:我不确定是否应该保留 [Key 1] & [Value 1] 或者它应该转换为名为 [Key] & [Value] 的列。

不同的方法

当我使用 3rd 方数据库时,我通常会在同一个服务器/实例上添加一个新数据库,如果可能的话,然后我将它用于我自己的目的,只是为了避免与数据库所有者发生冲突。

在这种情况下,您可以添加一个新表并定期抛出一个进程以使用新值更新它。

您可以使用包含所有列的表:

CREATE TABLE [FruitKeys]
(
    [FruitID] int NOT NULL PRIMARY KEY,
    [V1]      int NULL,
    [V2]      int NULL,
    [V3]      int NULL,
    [V4]      int NULL,
    [V5]      int NULL
);
Run Code Online (Sandbox Code Playgroud)

或具有键/值对的表并使用数据透视来获得最终结果:

CREATE TABLE [FruitKeys]
(
    [FruitID] int NOT NULL,
    [Key]     int NOT NULL,
    [Value]   int NOT NULL,
    CONSTRAINT [PK_FruitKeys] PRIMARY KEY ([FruitID], [Key])
);
Run Code Online (Sandbox Code Playgroud)


Pau*_*ite 5

在我看来,源数据与JSON格式相差不远。

您可以直接转换它,然后用于OPENJSON生成关系输出:

SELECT
    F.FruitID,
    F.[Name],
    OJ.[key 1],
    OJ.[key 2],
    OJ.[key 3],
    OJ.[key 4],
    OJ.[key 5],
    F.[Description]
FROM dbo.Fruit AS F
CROSS APPLY OPENJSON
(
    -- Convert source data to JSON format
    '{' + 
        CHAR(34) + 
        REPLACE
        (
            REPLACE
            (
                CONVERT(varchar(max), F.Data), 
                '=', CHAR(34) + ':' + CHAR(34)
            ), 
            CHAR(13) + CHAR(10), 
            CHAR(34) + ',' + CHAR(34)
        ) + 
        CHAR(34) + 
    '}'
) 
WITH
(
    [key 1] varchar(100),
    [key 2] varchar(100),
    [key 3] varchar(100),
    [key 4] varchar(100),
    [key 5] varchar(100)
) AS OJ;
Run Code Online (Sandbox Code Playgroud)

输出:

水果ID | 姓名 | 关键 1 | 关键 2 | 关键 3 | 关键 4 | 关键 5 | 描述
------: | :----- | :------ | :------ | :------ | :------ | :------ | :----------
      1 | 香蕉 | 值 1 | 值 2 | 值 3 | 值 4 | 值 5 | 美味的  
      2 | 梨 | 值 1 | 值 2 | 值 3 | 值 4 | 值 5 | 罗顿     
      3 | 猕猴桃 | 值 1 | 值 2 | 值 3 | 值 4 | 值 5 | 好的       

db<>小提琴演示