XML: öznitelikleri öğelere değiştirme


11

XMLBenzer yapıya sahip veriler içeren bir sütun var:

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

Her Valueözniteliği bir öğeye değiştirmek için SQL Server'ı kullanarak verileri nasıl değiştirebilirim ?

<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>

Güncelleme:

XML'im daha çok şöyle görünüyor:

<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>

Yalnızca niteliği taşımak Valueve diğer tüm nitelikleri ve öğeleri korumak istiyorum .


Neden ilk etapta bunu yapmak istiyorsun? <Value>Her biri için birden fazla öğeye sahip olmayı planlamadığınız sürece bunun herhangi bir faydasını düşünemiyorum <Element>. Değilse, özniteliği bir öğeye taşımak XML'i daha şişkin ve muhtemelen daha az verimli hale getirir.
Solomon Rutzky

@srutzky, bu yeniden düzenlemenin bir parçası. İkinci adım, karmaşık verilerin <Value>elemanın içinde veya yerine depolanmasıdır .
Wojteq

Yanıtlar:


13

XML'i parçalayabilir ve XQuery kullanarak yeniden oluşturabilirsiniz.

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"]
    }');

Sonuç:

<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>

Değilse Elements, Rootsorgunun altındaki ilk öğenin tüm öğeleri önce Elementsve sonra tüm öğeleri eklemek için değiştirilmesi gerekir Elements.


5

XML veri türünün (örn. Değiştir ) ve bazı XQuery yöntemlerini xml'yi değiştirmek için de kullanabilirsiniz;

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

Bu yöntem, büyük XML parçaları üzerinde iyi ölçeklendirme eğilimi göstermez, ancak XML'in toptan değiştirilmesinden size daha uygun olabilir.

XML'iniz bir tabloda depolanmışsa bu yöntemi kolayca uyarlayabilirsiniz. Yine deneyimden, bir milyon satırlık tabloya karşı tek bir güncelleme çalıştırmanızı önermem. Tablonuz büyükse, içinden bir imleç çalıştırmayı ya da güncellemeleri toplu olarak kullanmayı düşünün. İşte teknik:

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

GÜNCELLEME:

@ Mikael iyi cevap , bir yorumda belirtilen en son gereksinimi yansıtmak için aşağıdaki örnek sorgu giriş ve çıkış XML yanı sıra kodu güncelledim :

Değer boşsa veya yoksa Değer öğesi oluşturmama

Tek bir ifade bu yeni varyasyonla doğru bir şekilde eşleşebilse <Value/>de, değiştirme dizesinde koşullu mantığa izin verilmediğinden boş öğeyi tek bir geçişte atlamanın bir yolu yoktur. Yani, bunu 2 bölümlü bir değişiklik olarak uyarladım: boş olmayan @Valuenitelikleri almak için bir geçiş ve boş @Valuenitelikleri almak için bir geçiş . Bu <Element>öznenin eksik olmasıyla başa çıkmaya gerek yoktu , @Valueçünkü arzu <Value>zaten öğeye sahip olmamaktır .


Bir seçenek, XML'i normal bir dize olarak ele almak ve bir desene göre dönüştürmektir. Bu, SQLCLR kodu ile kullanılabilen Normal İfadeler (özellikle "Değiştir" işlevi) kullanılarak kolayca gerçekleştirilir.

Aşağıdaki örnek , SQL # kitaplığından RegEx_Replace skaler UDF'yi kullanır (ki ben yazarım, ancak bu RegEx işlevi, Diğerleri ile birlikte Ücretsiz sürümde kullanılabilir):

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

PRINTİfadeleri sadece "Mesajlar" sekmesinde daha kolay yan-yana karşılaştırma için orada yapmak içindedir. Ortaya çıkan çıktı (sadece istenen parçalara dokunulduğunu ve başka bir şey olmadığını çok netleştirmek için orijinal XML'i biraz değiştirdim):

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>

Bir tablodaki bir alanı güncellemek istiyorsanız, yukarıdakileri aşağıdaki şekilde uyarlayabilirsiniz:

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;

çözümünüz iyi görünüyor ve yardımcı oldu ama CLR'yi kullanabilirim.
Wojteq

@Wojteq Teşekkürler. Seçeneklere sahip olmak güzel, değil mi? Sadece meraktan, neden SQLCLR'yi kullanamıyorsunuz?
Solomon Rutzky

Bizim mimarlığımız yüzünden. Çok kiracılı web uygulamamız var. Her kiracının kendi veritabanı vardır. Dağıtım işlemi sırasında başarısız olabilecek başka bir 'hareketli parça' eklemek istemiyoruz. Yalnızca kod / salt webapp yaklaşımını kullanmak bizim için çok daha sürdürülebilir.
Wojteq

1

SQL Server dışında yapmanın muhtemelen daha iyi yolları vardır. Ancak, bunu yapmanın bir yolu var.

Verileriniz:

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>';

Sorgu:

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;

Xml CTE, xml değişkeninizi bir tabloya dönüştürür.

Ana seçim daha sonra CTE'yi tekrar xml'ye dönüştürür.

Çıktı:

<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>

Ayrıca kullanılarak da yapılabilir For XML Explicit.


Yardımınız için teşekkür ederim, ancak sorumu güncelledim - benim durumum karmaşık hareket. XML'i performans nedeniyle SQL Server kullanarak güncellemek istiyorum. Yüzbinlerce kayıt içeren tablolarım var. Diğer alternatif, ASP MVC uygulamasının içine yüklemek, serisini kaldırmak ve serileştirmektir.
Wojteq
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.