SQL Server 2008: переименовать элемент с использованием XML DML? - PullRequest
3 голосов
/ 24 августа 2010

Можно ли использовать оператор XML DML для переименования элемента в нетипизированном столбце XML?

Я нахожусь в процессе обновления коллекции схем XML для столбца XML, и мне нужно исправить существующий XMLэкземпляры, переименовав один элемент, прежде чем я смогу применить последнюю схему.

Насколько я могу судить по документам, вы можете только вставлять / удалять узлы или заменять их значения.

Ответы [ 3 ]

2 голосов
/ 20 декабря 2010

Как говорится, "Там, где есть воля, есть путь"

Вот два метода: во-первых, просто заменить предыдущий xml новым xml, созданным из оригинала, с новым именем элемента. В моем примере я изменил Legs / Leg на Limbs / Limb, это может быть очень сложно для всего, кроме самой простой схемы

А во-вторых, более подходящий подход сочетания вставки и удаления.

Я объединил их в один простой пример:

declare @xml as xml = '<animal species="Mouse">
  <legs>
    <leg>Front Right</leg>
    <leg>Front Left</leg>
    <leg>Back Right</leg>
    <leg>Back Left</leg>
  </legs>
</animal>'

set @xml = (select 
     t.c.value('@species', 'varchar(max)') as '@species'
    ,(select
     ti.C.value('.', 'varchar(max)') 
from @Xml.nodes('//animal/legs/leg') ti(c) for xml path('limb'), /* root('limb'), */type) as    limbs   
from @xml.nodes('//*:animal') t(c) for xml path('animal'), type)

select @xml;

while (@xml.exist('/animal/limbs/limb') = 1) begin
    /*insert..*/
    set @xml.modify('
            insert <leg>{/animal/limbs/limb[1]/text()}</leg>
            before (/animal/limbs/limb)[1]
        ');
    /*delete..*/
    set @xml.modify('delete (/animal/limbs/limb)[1]');
end

set @xml.modify('
        insert <legs>{/animal/limbs/leg}</legs>
        before (/animal/limbs)[1]
    ');
set @xml.modify('delete (/animal/limbs)[1]');

select @xml;
0 голосов
/ 28 января 2016

Да, вы можете использовать DML для переименования элемента, перерезав его в узле, который вы хотите переименовать, вставив новый узел в этот элемент, а затем вставив перерезанные элементы обратно в xml на этом узле. Я сделал SQL скрипку для демонстрации. http://sqlfiddle.com/#!3/dc64d/1 Это изменится

<animal species="Mouse">
<legs>
<leg>Front Right</leg>
<leg>Front Left</leg>
<leg>Back Right</leg>
<leg>Back Left</leg>
</legs>
</animal>

в

<animal species="Mouse">
<armsandlegs>
<leg>Front Right</leg>
<leg>Front Left</leg>
<leg>Back Right</leg>
<leg>Back Left</leg>
</armsandlegs>
</animal>

SqlFiddle, похоже, давно сломал мое решение. Из памяти я вставил основу моего решения ниже ...

DECLARE @XML2 xml
DECLARE @XML3 xml = '<limbs></limbs>'
DECLARE @XML xml = 
'<animal species="Mouse">
<legs>
<leg>Front Right</leg>
<leg>Front Left</leg>
<leg>Back Right</leg>
<leg>Back Left</leg>
</legs>
</animal>'

SET @XML2 = @XML.query('animal/legs/*')

SET @XML.modify('
insert      
    (sql:variable("@XML3"))
after
    (/animal/legs)[1]
')
SET @XML.modify('
delete (/animal/legs[1])
')
SET @XML.modify('
insert      
    (sql:variable("@XML2"))
as last into
    (/animal/limbs)[1]
')
select @XML
0 голосов
/ 21 декабря 2011

Во время разработки модульного теста SQL Server (ssut - см. Связанный пост в блоге) я хотел стандартизировать набор xml, полученный из тестируемого объекта. Поскольку я буду вызывать тестируемый объект несколько раз, каждый раз имена наборов и записей будут одинаковыми. Для удобства чтения я хочу, чтобы набор записей из исходных записей был назван аналогично <original_record_set><original_record /></original_record_set>, а набор записей для записи испытаний должны быть названы аналогично <test_record_set><test_record /></ test_record_set >.

Очевидно, что это тривиально, если вы можете сначала изменить вызов в тестируемом объекте:

SET @output = (SELECT col1, col2
    FROM   @test_object_result
    FOR xml path ( test_record  '), root( test_record_set '));

и затем:

SET @output = (SELECT col1, col2
    FROM   @test_object_result
    FOR xml path (  original_record'), root(  original_record_set '));

Однако, поскольку я вызываю один и тот же объект несколько раз, а «для пути xml» НЕ допускает использование переменных в методах path('...') и root('...'), мне пришлось придумать другой метод.

Эта функция принимает дерево XML и создает новое дерево, заменяя корневой узел значением @relation_name и именем каждой записи @tuple_name. Новое дерево строится со всеми атрибутами оригинала, даже если в записи разные номера.

ИСКЛЮЧЕНИЯ
Очевидно, что это НЕ работает с несколькими уровнями элементов! Я построил его специально для обработки одноуровневого дерева на основе атрибутов, как показано в примере ниже. Я могу построить его для многоуровневого смешанного дерева атрибутов / элементов в будущем, но я думаю, что способ сделать это становится очевидным теперь, когда я решил основную проблему, как показано ниже, и оставлю это упражнение читателю в ожидании этого времени.

USE [unit_test];
GO
IF EXISTS  (SELECT * FROM   sys.objects  WHERE  object_id = OBJECT_ID(N'[dbo].[standardize_record_set]')   AND type IN ( N'FN', N'IF', N'TF', N'FS', N'FT' ))
  DROP FUNCTION [dbo].[standardize_record_set];
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
SET nocount ON;
GO
/*
DECLARE
  @relation_name nvarchar(150)= N'standardized_record_set',
  @tuple_name    nvarchar(150)= N'standardized_record',
  @xml           xml,
  @standardized_result xml;

SET @xml='<Root>
    <row id="12" two="now1" three="thr1" four="four1" />
    <row id="232" two="now22" three="thr22" />
    <row id="233" two="now23" three="thr23" threeextra="extraattrinthree" />
    <row id="234" two="now24" three="thr24" fourextra="mealsoin four rwo big mone" />
    <row id="235" two="now25" three="thr25" />
</Root>';

execute @standardized_result =  [dbo].[standardize_record_set] @relation_name=@relation_name, @tuple_name=@tuple_name, @xml=@xml;
select @standardized_result;

*/
CREATE FUNCTION [dbo].[standardize_record_set] (@relation_name nvarchar(150)= N'record_set',
                                                @tuple_name    nvarchar(150)= N'record', @xml  xml )
returns XML
AS
  BEGIN
      DECLARE
        @attribute_index int = 1,
        @attribute_count int = 0,
        @record_set      xml = N'<' + @relation_name + ' />',
        @record_name     nvarchar(50) = @tuple_name,
        @builder         nvarchar(max),
        @record          xml,
        @next_record     xml;
      DECLARE @record_table TABLE (
        record xml );

      INSERT INTO @record_table
      SELECT t.c.query('.') AS record
      FROM   @xml.nodes('/*/*') T(c);

      DECLARE record_table_cursor CURSOR FOR
        SELECT cast([record] AS xml)
        FROM   @record_table

      OPEN record_table_cursor

      FETCH NEXT FROM record_table_cursor INTO @next_record

      WHILE @@FETCH_STATUS = 0
        BEGIN
            SET @attribute_index=1;
            SET @attribute_count = @next_record.query('count(/*[1]/@*)').value('.', 'int');
            SET @builder = N'<' + @record_name + N' ';

            -- build up attribute string
            WHILE @attribute_index <= @attribute_count
              BEGIN
                  SET @builder = @builder + @next_record.value('local-name((/*/@*[sql:variable("@attribute_index")])[1])',
                                                               'varchar(max)') + '="' + @next_record.value('((/*/@*[sql:variable("@attribute_index")])[1])',
                                                                                                           'varchar(max)') + '" ';
                  SET @attribute_index = @attribute_index + 1
              END

            -- build record and add to record_set
            SET @record = @builder + ' />';
            SET @record_set.modify('insert sql:variable("@record") into (/*)[1]');

            FETCH NEXT FROM record_table_cursor INTO @next_record
        END

      CLOSE record_table_cursor;

      DEALLOCATE record_table_cursor;

      RETURN @record_set;
  END;

GO 
...