【问题标题】:Dynamic Pivot Column by Substring按子字符串动态透视列
【发布时间】:2026-01-03 17:50:01
【问题描述】:

我有这张桌子:

itemid                  attributename   value
6829556334057807840       Part Notes    Drilling Required: No,Install Time: 30 Minutes,Fascia Trimming: Yes (minor),Visibility: Exposed

我希望能够将 Value 列中的数据转换为 KeyValue 对

Example:

    ItemId                  Key                  Value
6829556334057807840       Drilling Required       No
6829556334057807840       Install Time            30
6829556334057807840       Fascia Trimming         Yes (minor)

但是,数据透视必须是动态的,因为值列可以有多个不同的键值,具体取决于列中的内容。

【问题讨论】:

  • 这实际上不需要是动态枢轴。您只是想将分隔的字符串拆分为行,然后再次拆分为单独的列。
  • 这里的最佳解决方案是首先不要像这样存储数据。如果可以,请修复您的数据库,而不是试图让 SQL Server 进行笨拙的转换。

标签: sql sql-server dynamic-pivot


【解决方案1】:

考虑数据库的规范化。如果您有未知数据,请使用 xml(自 SQL Server 2005 起受支持)或 JSON(自 SQL Server 2016 起受支持)。使用此查询作为起点:

WITH Src AS
(
    SELECT * FROM (VALUES
        ('6829556334057807840', 'Part Notes', 'Drilling Required: No,Install Time: 30 Minutes,Fascia Trimming: Yes (minor),Visibility: Exposed')
    ) T(itemid, attributename, value)
), Recurse AS
(
    SELECT
        itemid,
        SUBSTRING(value, 1, CASE WHEN CHARINDEX(',', value, 1)=0 THEN LEN(value) ELSE CHARINDEX(',', value, 1)-1 END) Item,
        SUBSTRING(value, CASE WHEN CHARINDEX(',', value, 1)=0 THEN LEN(value) ELSE CHARINDEX(',', value, 1)+1 END, LEN(value)) Rest
    FROM Src
    UNION ALL
    SELECT
        itemid,
        CASE WHEN CHARINDEX(',', Rest, 1)=0 THEN Rest ELSE SUBSTRING(Rest, 1, CHARINDEX(',', Rest)-1) END Item,
        CASE WHEN CHARINDEX(',', Rest, 1)=0 THEN NULL ELSE SUBSTRING(Rest, CHARINDEX(',', Rest)+1, LEN(Rest)) END Rest
    FROM Recurse
    WHERE Rest IS NOT NULL
)
SELECT
    itemid,
    SUBSTRING(Item, 1, CHARINDEX(':', Item)-1) [Key],
    SUBSTRING(Item, CHARINDEX(':', Item)+1, LEN(Item)) [Value]
FROM Recurse

返回:

itemid               Key                   Value
-------------------  --------------------  -------------
6829556334057807840  Drilling Required     No
6829556334057807840  Install Time          30 Minutes
6829556334057807840  Fascia Trimming       Yes (minor)
6829556334057807840  Visibility            Exposed

【讨论】:

    【解决方案2】:

    XML 的另一种方式:

    DECLARE @xml xml
    
    SELECT @xml = (
        SELECT CAST('<item id="' + CAST(itemid as nvarchar(100)) +'"><row><key>' +
                REPLACE(REPLACE([value],',','</value></row><row><key>'),': ','</key><value>')+'</value></row></item>' as xml)
        FROM YourTable
        FOR XML PATH('')
    )
    
    SELECT  t.v.value('../@id','bigint') ItemId,                                    
            t.v.value('key[1]','nvarchar(max)') [Key],
            t.v.value('value[1]','nvarchar(max)') [Value]
    FROM @xml.nodes('/item/row') as t(v)
    

    对于您提供的表格样本,输出将是:

    ItemId              Key                 Value
    6829556334057807840 Drilling Required   No
    6829556334057807840 Install Time        30 Minutes
    6829556334057807840 Fascia Trimming     Yes (minor)
    6829556334057807840 Visibility          Exposed
    

    这是您在使用XML PATH 和替换转换表后获得的 XML 数据:

    <item id="6829556334057807840">
      <row>
        <key>Drilling Required</key>
        <value>No</value>
      </row>
      <row>
        <key>Install Time</key>
        <value>30 Minutes</value>
      </row>
      <row>
        <key>Fascia Trimming</key>
        <value>Yes (minor)</value>
      </row>
      <row>
        <key>Visibility</key>
        <value>Exposed</value>
      </row>
    </item>
    

    【讨论】: