Sửa đổi XML: thuộc tính thành các phần tử


11

Tôi có một XMLcột chứa dữ liệu có cấu trúc tương tự:

<Root>
    <Elements>
        <Element Code="1" Value="aaa"></Element>
        <Element Code="2" Value="bbb"></Element>
        <Element Code="3" Value="ccc"></Element>
    </Elements>
</Root>

Làm cách nào tôi có thể sửa đổi dữ liệu bằng SQL Server để thay đổi từng Valuethuộc tính thành một thành phần?

<Root>
    <Elements>
        <Element Code="1">
            <Value>aaa</Value>
        </Element>
        <Element Code="2">
            <Value>bbb</Value>
        </Element>
        <Element Code="3">
            <Value>ccc</Value>
        </Element>
    </Elements>
</Root>

Cập nhật:

XML của tôi trông giống như thế này:

<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="1" Value="aaa" ExtraData="extra" />
        <Element Code="2" Value="bbb" ExtraData="extra" />
        <Element Code="3" Value="ccc" ExtraData="extra" />
        <Element Code="4" Value="" ExtraData="extra" />
        <Element Code="5" ExtraData="extra" />
    </Elements>
    <ExtraData>
       <!-- Some XML is here -->
    </ExtraData>
</Root>

Tôi chỉ muốn di chuyển Valuethuộc tính và bảo tồn tất cả các thuộc tính và yếu tố khác.


Tại sao bạn muốn làm điều này ở nơi đầu tiên? Tôi không thể nghĩ ra bất kỳ lợi ích nào cho việc này trừ khi bạn có kế hoạch có nhiều <Value>yếu tố cho mỗi yếu tố <Element>. Nếu không, thì việc di chuyển thuộc tính sang một phần tử chỉ làm cho XML trở nên cồng kềnh hơn và có thể kém hiệu quả hơn.
Solomon Rutzky 4/11/2015

@srutzky, đó là một phần của tái cấu trúc. Bước thứ hai là lưu trữ dữ liệu phức tạp bên trong <Value>phần tử hoặc thay vì nó.
Wojteq

Câu trả lời:


13

Bạn có thể hủy bỏ XML và xây dựng lại nó bằng XQuery.

declare @X xml = '
<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="1" Value="aaa" ExtraData="extra" />
        <Element Code="2" Value="" ExtraData="extra" />
        <Element Code="3" ExtraData="extra" />
    </Elements>
    <ExtraData>
       <!-- Some XML is here -->
    </ExtraData>
</Root>';

select @X.query('
  (: Create element Root :)
  element Root 
    {
      (: Add all attributes from Root to Root :)
      /Root/@*, 
      (: create element Elements under Root :)
      element Elements 
        {
          (: For each Element element in /Root/Elements :)
          for $e in /Root/Elements/Element
          return 
            (: Add element Element :)
            element Element 
              {
                (: Add all attributes except Value to Element :)
                $e/@*[local-name() != "Value"], 

                (: Check if Attribute Value exist :)
                if (data($e/@Value) != "")
                then
                  (: Create a Value element under Element :)
                  element Value 
                  {
                    (: Add attribute Value as data to the element Element :)
                    data($e/@Value)
                  }
                else () (: Empty element :)
              } 
          },
      (: Add all childelements to Root except the Elements element :)
      /Root/*[local-name() != "Elements"]
    }');

Kết quả:

<Root attr1="val1" attr2="val2">
  <Elements>
    <Element Code="1" ExtraData="extra">
      <Value>aaa</Value>
    </Element>
    <Element Code="2" ExtraData="extra" />
    <Element Code="3" ExtraData="extra" />
  </Elements>
  <ExtraData>
    <!-- Some XML is here -->
  </ExtraData>
</Root>

Nếu Elementskhông phải là phần tử đầu tiên trong Roottruy vấn cần phải được sửa đổi để thêm tất cả các phần tử trước trước Elementsvà tất cả các phần tử Elementssau.


Cảm ơn sự giúp đỡ của bạn tuy nhiên tôi đã cập nhật câu hỏi của mình - trường hợp của tôi rất phức tạp.
Wojteq

2
@Wojteq đã thêm một câu trả lời phức tạp hơn.
Mikael Eriksson

Nó trông rất đẹp và hoạt động! Bạn có thể vui lòng thay đổi truy vấn để không tạo thành Valuephần nếu @Valuetrống hoặc không tồn tại? Tôi đã cố gắng nhưng tôi đã thất bại.
Wojteq

1
@srutzky không biết liệu flwor trong sửa đổi có hoạt động hay không nhưng giới hạn chỉ thêm hoặc sửa đổi một yếu tố tại một thời điểm là điều ngăn chặn việc sử dụng sửa đổi ở đây. Trừ khi bạn làm điều đó trong một vòng lặp một yếu tố tại một thời điểm. Bạn có thể xóa nhiều hơn một yếu tố cùng một lúc nhưng đó chỉ là một nửa ở đây.
Mikael Eriksson

1
@srutzky BTW, tôi tin rằng (không có testin) rằng câu trả lời của bạn là câu trả lời nhanh nhất. Vì vậy, nếu hiệu suất là một vấn đề và điều đó phụ thuộc hoàn toàn vào kích thước của XML, thì chắc chắn hãy thử thay thế regrec.
Mikael Eriksson

5

Bạn cũng có thể sử dụng các phương thức của kiểu dữ liệu XML (ví dụ: sửa đổi ) và một số XQuery để sửa đổi xml, ví dụ:

DECLARE @x XML = '<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="1" Value="aaa" ExtraData="extra" />
        <Element Code="2" Value="bbb" ExtraData="extra" />
        <Element Code="3" Value="ccc" ExtraData="extra" />
    </Elements>
    <ExtraData>
       <!-- Some XML is here -->
    </ExtraData>
</Root>'


SELECT 'before' s, DATALENGTH(@x) dl, @x x

-- Add 'Value' element to each Element which doesn't already have one
DECLARE @i INT = 0

WHILE @x.exist('Root/Elements/Element[not(Value)]') = 1
BEGIN

    SET @x.modify( 'insert element Value {data(Root/Elements/Element[not(Value)]/@Value)[1]} into (Root/Elements/Element[not(Value)])[1]' )

    SET @i += 1

    IF @i > 99 BEGIN RAISERROR( 'Too many loops...', 16, 1 ) BREAK END

END

-- Now delete all Value attributes
SET @x.modify('delete Root/Elements/Element/@Value' )

SELECT 'after' s, DATALENGTH(@x) dl, @x x

Phương pháp này không có xu hướng mở rộng tốt trên các phần lớn XML nhưng có thể phù hợp với bạn hơn là thay thế bán buôn XML.

Bạn cũng có thể dễ dàng điều chỉnh phương thức này nếu XML của bạn được lưu trữ trong một bảng. Một lần nữa từ kinh nghiệm, tôi không khuyên bạn nên chạy một bản cập nhật duy nhất so với bảng hàng triệu. Nếu bảng của bạn lớn, hãy xem xét việc chạy một con trỏ qua nó hoặc nếu không thì tạo các bản cập nhật. Đây là kỹ thuật:

DECLARE @t TABLE ( rowId INT IDENTITY PRIMARY KEY, yourXML XML )

INSERT INTO @t ( yourXML )
SELECT '<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="1" Value="aaa" ExtraData="extra" />
        <Element Code="2" Value="bbb" ExtraData="extra" />
        <Element Code="3" Value="ccc" ExtraData="extra" />
    </Elements>
    <ExtraData>
       <!-- Some XML is here -->
    </ExtraData>
</Root>'

INSERT INTO @t ( yourXML )
SELECT '<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="21" Value="uuu" ExtraData="extra" />
        <Element Code="22" Value="vvv" ExtraData="extra" />
        <Element Code="23" Value="www" ExtraData="extra" />
        <Element Code="24" Value="xxx" ExtraData="extra" />
        <Element Code="25" Value="yyy" ExtraData="extra" />
        <Element Code="26" Value="zzz" ExtraData="extra" />
    </Elements>
    <ExtraData>
       <!-- Some XML is here -->
    </ExtraData>
</Root>'


SELECT 'before' s, DATALENGTH(yourXML) dl, yourXML
FROM @t 

-- Add 'Value' element to each Element which doesn't already have one
DECLARE @i INT = 0

WHILE EXISTS ( SELECT * FROM @t WHERE yourXML.exist('Root/Elements/Element[not(Value)]') = 1 )
BEGIN

    UPDATE @t
    SET yourXML.modify( 'insert element Value {data(Root/Elements/Element[not(Value)]/@Value)[1]} into (Root/Elements/Element[not(Value)])[1]' )

    SET @i += 1

    IF @i > 99 BEGIN RAISERROR( 'Too many loops...', 16, 1 ) BREAK END

END

-- Now delete all Value attributes
UPDATE @t
SET yourXML.modify('delete Root/Elements/Element/@Value' )

SELECT 'after' s, DATALENGTH(yourXML) dl, yourXML
FROM @t 

4

CẬP NHẬT:

Tôi đã cập nhật mã, cũng như XML đầu vào và đầu ra trong truy vấn mẫu bên dưới để phản ánh yêu cầu mới nhất, được nêu trong một nhận xét về câu trả lời tốt của @ Mikael , đó là:

không tạo phần tử Giá trị nếu @Value trống hoặc không tồn tại

Mặc dù một biểu thức có thể khớp chính xác với biến thể mới này, nhưng dường như không có cách nào để bỏ qua <Value/>phần tử trống trong một lần truyền vì logic điều kiện không được phép trong chuỗi thay thế. Vì vậy, tôi đã điều chỉnh điều này thành một sửa đổi 2 phần: một vượt qua để có được các @Valuethuộc tính không trống và một vượt qua để có được các @Valuethuộc tính trống . Không cần xử lý <Element>s thiếu @Valuethuộc tính vì mong muốn là không có <Value>phần tử nào.


Một tùy chọn là coi XML như một chuỗi thông thường và biến đổi nó dựa trên một mẫu. Điều này có thể dễ dàng thực hiện bằng cách sử dụng Biểu thức chính quy (cụ thể là chức năng "Thay thế") có thể được cung cấp qua mã SQLCLR.

Ví dụ dưới đây sử dụng UDF vô hướng RegEx_Replace từ thư viện SQL # (mà tôi là tác giả, nhưng hàm RegEx này có sẵn trong phiên bản Miễn phí, cùng với nhiều phiên bản khác):

DECLARE @SomeXml XML;
SET @SomeXml = N'<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="1" Value="aaa" ExtraData="extra1" />
        <Element Code="22" Value="bbb" ExtraData="extra2" />
        <Element Code="333" Value="ccc" ExtraData="extra3" />
        <Element Code="4444" Value="" ExtraData="extra4" />
        <Element Code="55555" ExtraData="extra5" />
    </Elements>
    <ExtraData>
       <Something Val="1">qwerty A</Something>
       <Something Val="2">qwerty B</Something>
    </ExtraData>
</Root>';

DECLARE @TempStringOfXml NVARCHAR(MAX),
        @Expression NVARCHAR(4000),
        @Replacement NVARCHAR(4000);


SET @TempStringOfXml = CONVERT(NVARCHAR(MAX), @SomeXml);
PRINT N'Original: ' + @TempStringOfXml;

---

SET @Expression =
              N'(<Element Code="[^"]+")\s+Value="([^"]+)"\s+(ExtraData="[^"]+")\s*/>';
SET @Replacement = N'$1 $3><Value>$2</Value></Element>';

SELECT @TempStringOfXml = SQL#.RegEx_Replace(@TempStringOfXml, @Expression,
                                             @Replacement, -1, 1, '');

PRINT '-------------------------------------';
PRINT N'Phase 1:  ' + @TempStringOfXml; -- transform Elements with a non-empty @Value

---

SET @Expression = N'(<Element Code="[^"]+")\s+Value=""\s+(ExtraData="[^"]+")\s*/>';
SET @Replacement = N'$1 $2 />';

SELECT @TempStringOfXml = SQL#.RegEx_Replace(@TempStringOfXml, @Expression,
                                             @Replacement, -1, 1, '');

PRINT '-------------------------------------';
PRINT N'Phase 2:  ' + @TempStringOfXml; -- transform Elements with an empty @Value

SELECT CONVERT(XML, @TempStringOfXml); -- prove that this is valid XML

Các PRINTbáo cáo nằm trong đó chỉ để làm cho việc so sánh song song dễ dàng hơn trong tab "Tin nhắn". Kết quả đầu ra là (Tôi đã sửa đổi XML gốc một chút để làm rõ rằng chỉ có các phần mong muốn được chạm vào và không có gì khác):

Original: <Root attr1="val1" attr2="val2"><Elements><Element Code="1" Value="aaa" ExtraData="extra1"/><Element Code="22" Value="bbb" ExtraData="extra2"/><Element Code="333" Value="ccc" ExtraData="extra3"/><Element Code="4444" Value="" ExtraData="extra4"/><Element Code="55555" ExtraData="extra5"/></Elements><ExtraData><Something Val="1">qwerty A</Something><Something Val="2">qwerty B</Something></ExtraData></Root>
-------------------------------------
Phase 1:  <Root attr1="val1" attr2="val2"><Elements><Element Code="1" ExtraData="extra1"><Value>aaa</Value></Element><Element Code="22" ExtraData="extra2"><Value>bbb</Value></Element><Element Code="333" ExtraData="extra3"><Value>ccc</Value></Element><Element Code="4444" Value="" ExtraData="extra4"/><Element Code="55555" ExtraData="extra5"/></Elements><ExtraData><Something Val="1">qwerty A</Something><Something Val="2">qwerty B</Something></ExtraData></Root>
-------------------------------------
Phase 2:  <Root attr1="val1" attr2="val2"><Elements><Element Code="1" ExtraData="extra1"><Value>aaa</Value></Element><Element Code="22" ExtraData="extra2"><Value>bbb</Value></Element><Element Code="333" ExtraData="extra3"><Value>ccc</Value></Element><Element Code="4444" ExtraData="extra4" /><Element Code="55555" ExtraData="extra5"/></Elements><ExtraData><Something Val="1">qwerty A</Something><Something Val="2">qwerty B</Something></ExtraData></Root>

Nếu bạn muốn cập nhật một trường trong bảng, bạn có thể điều chỉnh các mục trên thành như sau:

DECLARE @NonEmptyValueExpression NVARCHAR(4000),
        @NonEmptyValueReplacement NVARCHAR(4000),
        @EmptyValueExpression NVARCHAR(4000),
        @EmptyValueReplacement NVARCHAR(4000);

SET @NonEmptyValueExpression =
                   N'(<Element Code="[^"]+")\s+Value="([^"]+)"\s+(ExtraData="[^"]+")\s*/>';
SET @NonEmptyValueReplacement = N'$1 $3><Value>$2</Value></Element>';

SET @EmptyValueExpression =
                   N'(<Element Code="[^"]+")\s+Value=""\s+(ExtraData="[^"]+")\s*/>';
SET @EmptyValueReplacement = N'$1 $2 />';

UPDATE tbl
SET    XmlField = SQL#.RegEx_Replace4k(
                                     SQL#.RegEx_Replace4k(
                                                     CONVERT(NVARCHAR(4000), tbl.XmlField),
                                                        @NonEmptyValueExpression,
                                                        @NonEmptyValueReplacement,
                                                        -1, 1, ''),
                                     @EmptyValueExpression,
                                     @EmptyValueReplacement,
                                     -1, 1, '')
FROM   SchemaName.TableName tbl
WHERE  tbl.XmlField.exist('Root/Elements/Element/@Value') = 1;

giải pháp của bạn có vẻ tốt và nó rất hữu ích nhưng tôi có thể sử dụng CLR.
Wojteq

@Wojteq Cảm ơn. Thật tốt khi có các lựa chọn, phải không? Vì tò mò, tại sao bạn không thể sử dụng SQLCLR?
Solomon Rutzky

Đó là vì kiến ​​trúc của chúng tôi. Chúng tôi đã có ứng dụng web nhiều người thuê. Mỗi người thuê nhà có cơ sở dữ liệu riêng của mình. Chúng tôi không muốn thêm bất kỳ 'phần chuyển động' nào khác có thể thất bại trong quá trình triển khai. Sử dụng phương pháp tiếp cận chỉ mã / webapp là duy trì nhiều hơn cho chúng tôi.
Wojteq

1

Có lẽ có nhiều cách tốt hơn để làm điều đó bên ngoài SQL Server. Tuy nhiên, đây là một cách để làm điều đó.

Dữ liệu của bạn:

declare @xml xml = N'<Root>
    <Elements>
        <Element Code="1" Value="aaa"></Element>
        <Element Code="2" Value="bbb"></Element>
        <Element Code="3" Value="ccc"></Element>
    </Elements>
</Root>';

Truy vấn:

With xml as (
    Select 
        Code = x.e.value('(@Code)', 'varchar(10)')
        , Value = x.e.value('(@Value)', 'varchar(10)')
    From @xml.nodes('/Root//Elements/Element') as x(e)
)
Select * From (
    Select code
        , (
        Select value
        From xml x1 where x1.Code = Element.Code
        For xml path(''), elements, type
    )
    From xml Element
    For xml auto, type
) as Root(Elements)
for xml auto, elements;

CTE xml biến đổi biến xml của bạn thành một bảng.

Lựa chọn chính sau đó biến đổi CTE trở lại thành xml.

Đầu ra:

<Root>
  <Elements>
    <Element code="1">
      <value>aaa</value>
    </Element>
    <Element code="2">
      <value>bbb</value>
    </Element>
    <Element code="3">
      <value>ccc</value>
    </Element>
  </Elements>
</Root>

Nó cũng có thể được thực hiện bằng cách sử dụng For XML Explicit.


Cảm ơn sự giúp đỡ của bạn tuy nhiên tôi đã cập nhật câu hỏi của mình - trường hợp của tôi rất phức tạp. Tôi muốn cập nhật XML của mình bằng SQL Server vì hiệu suất. Tôi đã có các bảng chứa hàng trăm ngàn hồ sơ. Một cách khác là tải nó, giải tuần tự hóa và tuần tự hóa nó trong ứng dụng ASP MVC.
Wojteq
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.