将XML数据转换为行和列

时间:2016-06-28 15:00:40

标签: sql-server sql-server-2008

我有一张表来跟踪变化。

CREATE TABLE ChangeTracker 
(
    ChangeId BIGINT NOT NULL identity(1, 1) PRIMARY KEY,
    ChangeDate DATETIME NOT NULL DEFAULT getdate(),
    Changes VARCHAR(max) NOT NULL DEFAULT ''
)

Changes中的数据采用以下格式:

<span class="fieldname">AssignedTo</span>
<span class="oldvalue">user1</span>
<span class="newvalue">user2</span>
<br />
<span class="fieldname">Attachments</span>
<br />
<span class="fieldname">Status</span>
<span class="oldvalue">new</span>
<span class="newvalue">open</span>
<br />
<span class="fieldname">Priority</span>
<span class="oldvalue">low</span>
<span class="newvalue">high</span>
<br />
...

请注意,某些更改包含fieldname,oldvalue,newvalue对,而有些更改只有fieldname。此外,所有更改都以<br />标记分隔。

因此,当我想要对特定的FieldName(比如Status)进行更改时,我可以使用以下查询来执行此操作:

SELECT * FROM
(
    SELECT ChangeId,
        ChangeDate,
        TransDesc.value('(/root/span[@class="fieldname"]/text())[1]', 'varchar(255)') as FieldName,
        TransDesc.value('(/root/span[@class="oldvalue"]/text())[1]', 'varchar(255)') AS OldValue,
        TransDesc.value('(/root/span[@class="newvalue"]/text())[1]', 'varchar(255)') AS NewValue
    FROM (
        SELECT *, TransDesc = CAST('<root>' + SUBSTRING(ChangeA, 0, CHARINDEX('<br />', ChangeA)) + '</root>' AS XML)
        FROM
        (
            SELECT *, ChangeA = SUBSTRING(Changes, CHARINDEX('<span class="fieldname">Status</span>', Changes), 4000) 
            FROM ChangeTracker
            WHERE CHARINDEX('<span class="fieldname">Status</span>', Changes) > 0
            ) TTX
        ) TT
) x

这给了我以下结果:

 ChangeId | ChangeDate              | FieldName | OldValue | NewValue
 ————————————————————————————————————————————————————————————————————
 1        | 2016-06-28 18:37:24.403 | Status    | new      | open

现在我想要一个获得所有更改的查询(创建一个视图)。所以输出看起来像这样:

 ChangeId | ChangeDate              | FieldName   | OldValue | NewValue
 ————————————————————————————————————————————————————————————————————
 1        | 2016-06-28 18:37:24.403 | AssignedTo  | user1    | user2
 1        | 2016-06-28 18:37:24.403 | Attachments | NULL     | NULL
 1        | 2016-06-28 18:37:24.403 | Status      | new      | open
 1        | 2016-06-28 18:37:24.403 | Priority    | low      | high

2 个答案:

答案 0 :(得分:2)

<br />作为分隔符非常好。这样你就可以得到你想要的东西。

;with tbl as (
select ChangeId,ChangeDate,
--build xml
cast('<root><rec>'+replace(Changes,'<br />','</rec><rec>')+'</rec></root>' as xml) x
from ChangeTracker
)
select ChangeId, ChangeDate,
t.v.value('span[@class="fieldname"][1]','varchar(50)') fieldname,
t.v.value('span[@class="oldvalue"][1]','varchar(50)') oldvalue,
t.v.value('span[@class="newvalue"][1]','varchar(50)') newvalue

from tbl cross apply tbl.x.nodes('root/rec') t(v) --convert to tabular form
where t.v.value('span[@class="fieldname"][1]','varchar(50)') is not null

答案 1 :(得分:2)

我们可以通过逗号分割方法来做。在这里,我们只需要将字符串拆分为“&lt; br /&gt;”。

例如: -

SELECT CAST(item AS XML)
FROM dbo.SplitString(@str, '<br />') AS [Changes]
WHERE LEN(item) > 0

<强>结果: - Result

因此,最终的查询将是: -

INSERT INTO ChangeTracker
SELECT GETDATE()
,tb.xmldata.value('(/span[@class="fieldname"]/text())[1]', 'varchar(255)') AS fieldName
,tb.xmldata.value('(/span[@class="oldvalue"]/text())[1]', 'varchar(255)') AS oldValue
,tb.xmldata.value('(/span[@class="newvalue"]/text())[1]', 'varchar(255)') AS newValue
FROM(
    SELECT CAST(Item AS XML) AS xmldata
    FROM dbo.SplitString(@str, '<br />') AS [Changes]
    WHERE LEN(item) > 0
) AS tb

最终结果: - Final Result

注意: - 您需要创建分割功能,请在下面找到.BEGIN

    CREATE FUNCTION SplitString
    (    
          @Input NVARCHAR(MAX),
          @Character NVARCHAR(10)
    )
    RETURNS @Output TABLE (
          Item NVARCHAR(1000)
    )
    AS
    BEGIN
          DECLARE @StartIndex INT, @EndIndex INT

          SET @StartIndex = 1
          IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
          BEGIN
                SET @Input = @Input + @Character
          END

          WHILE CHARINDEX(@Character, @Input) > 0
          BEGIN
                SET @EndIndex = CHARINDEX(@Character, @Input)

                INSERT INTO @Output(Item)
                SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)

                SET @Input = SUBSTRING(@Input, @EndIndex + LEN(@Character), LEN(@Input))
          END

          RETURN
    END