Почему при преобразовании динамического SQL (сводного запроса) в вывод XML, первая цифра даты преобразуется в Юникод?

11

Я использую этот замечательный пример /dba//a/25818/113298 от Bluefeet, чтобы создать сводную диаграмму и преобразовать ее в данные XML.

Объявление параметра

DECLARE @cols AS NVARCHAR(MAX),  @query  AS NVARCHAR(MAX);

Далее идет CTE с большим количеством кода, конечный результат CTE помещается во временную DB (так же, как в примере)

SELECT 
B.[StayDate] -- this is a date dd-mm-yyyy
, B.[Guid]
INTO #tempDates
FROM BaseSelection B

Генерация столбцов (так же, как в примере)

SELECT @cols = STUFF((SELECT distinct ',' +QUOTENAME(convert(char(10), [StayDate] , 120)) 
FROM #tempDates
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') 
,1,1,'');

Набор результатов - это то, что я должен был ожидать

set @query = 
   'SELECT [Guid],' + @cols +'
    FROM
    (
        SELECT 
            [StayDate] 
           ,[Guid]
        FROM #tempDates
    ) A
    pivot
    (
        count([StayDate])
        for [StayDate] in (' + @cols +')                    
    ) p
    '
EXEC sp_executesql  @query ;

введите описание изображения здесь

Когда я пытаюсь преобразовать его в XML, мои атрибуты конвертируются только частично

set @query = 
   'SELECT [Guid],' + @cols +'
    FROM
    (
        SELECT 
            [StayDate] 
           ,[Guid]
        FROM #tempDates
    ) A
    pivot
    (
        count([StayDate])
        for [StayDate] in (' + @cols +')                    
    ) p
    for xml auto
    -- when using for XML path i will get a error
    -- FOR XML PATH(''''), ROOT(''root'') 
    -- Msg 6850, Level 16, State 1, Line 3
    -- Column name '2016-12-17' contains an invalid XML identifier 
    -- as required by FOR XML; '2'(0x0032) is the first character at fault.
    '
EXEC sp_executesql  @query ;

ResultSet

<p Guid="3C3359E3-CFE5-E511-80CA-005056A90901"
  _x0032_016-12-17="2" --> should be 2016-12-17="2" 
  _x0032_016-12-18="2" --> should be 2016-12-18="2" 
  _x0032_016-12-19="2" --> should be 2016-12-19="2" 
/>

Я что-то пропустил, почему только часть даты конвертируется в юникод?

Как я могу это исправить?

Bunkerbuster
источник
Для какой версии SQL Server это нужно?
ypercubeᵀᴹ
Sql Server 2012, но это не главное, в этом случае
важны
Это похоже на проблему XY. Использование даты в качестве имени атрибута в XML кажется нецелесообразным, даже если это работает как задумано. Я был бы более склонен хранить дату как значение атрибута или, возможно, как текст элемента, в зависимости от того, что я планировал делать с ним. При необходимости я бы сделал несколько элементов с парами атрибутов.
jpmc26

Ответы:

14

Имена атрибутов в XML не могут начинаться с цифры, см. NameStartChar .

Вы должны придумать альтернативные имена для ваших атрибутов и закодировать их в отдельной @colsпеременной, указав псевдонимы столбцов для вашего динамического сводного запроса.

SELECT @cols2 = STUFF((SELECT distinct ',' +
                       quotename(convert(char(10), [StayDate] , 120)) + 
                       ' as '+ QUOTENAME('z'+convert(char(10), [StayDate] , 120)) 
FROM #tempDates
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') 
,1,1,'');

Результат;

[2016-12-20] as [z2016-12-20],[2016-12-21] as [z2016-12-21]
<p Guid="6365FC57-F476-4703-B9D4-1EB81288FF30" z2016-12-20="0" z2016-12-21="1" />
<p Guid="B38FA9DB-B4E1-4725-8F3B-3AF6E009C10A" z2016-12-20="1" z2016-12-21="0" />

Когда вы используете for xml autoSQL Server, это делает для вас.

Микаэль Эрикссон
источник
Это была недостающая ссылка, также для пути XML (''), root ('root') теперь работает.
Bunkerbuster
6

Первый персонаж не является Unicode, как таковой. Я имею в виду, что технически все символы в XML в SQL Server кодируются как UTF-16 Little Endian, так что в этом смысле все они являются Unicode. Но то, что вы видите, это просто экранированная нотация для символа, в данном случае «2», которая имеет шестнадцатеричное / двоичное значение «32».

Проблема в том, что имена XML не могут начинаться с цифры. Следующие тесты показывают, что имя атрибута или имя элемента, начинающееся с числа, получает ошибку, но начинать с подчеркивания ( _) или буквы просто отлично.

SELECT CONVERT(XML, N'<test><row 2016-12-17="2" /></test>');
/*
Msg 9455, Level 16, State 1, Line 10
XML parsing: line 1, character 12, illegal qualified name character
*/


SELECT CONVERT(XML, N'<test><2016>a</2016></test>');
/*
Msg 9455, Level 16, State 1, Line 10
XML parsing: line 1, character 8, illegal qualified name character
*/


SELECT CONVERT(XML, N'<test><row _2016-12-17="2" /></test>');
/*
<test>
  <row _2016-12-17="2" />
</test>
*/


SELECT CONVERT(XML, N'<test><row x2016-12-17="2" /></test>');
/*
<test>
  <row x2016-12-17="2" />
</test>
*/

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


Кроме того, вы уверены, что он "работает" с FOR XML AUTO? Из того, что я вижу, это просто автоматическое преобразование «недопустимого» символа в _x0032_:

SELECT tmp.* FROM (SELECT 2) tmp([2016]) FOR XML AUTO;

Возвращает:

<tmp _x0032_016="2" />
Соломон Руцкий
источник