TSQL "Недопустимый символ XML" при преобразовании Varbinary в XML - PullRequest
0 голосов
/ 02 ноября 2018

Я пытаюсь создать хранимую процедуру в SQL Server 2016, которая преобразует XML, который ранее был преобразован в Varbinary, обратно в XML, но при преобразовании появляется ошибка «Недопустимый символ XML». Я нашел обходной путь, который, кажется, работает, но я не могу понять, , почему это работает, что делает меня неудобным.

Хранимая процедура принимает данные, которые были преобразованы в двоичный файл в службах SSIS и вставлены в столбец varbinary(MAX) в таблице, и выполняет простое

CAST(Column AS XML)

Долгое время он работал нормально, и я только начал видеть проблему, когда начальный XML начал содержать символ ® (зарегистрированный товарный знак).

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

Сообщение 9420, Уровень 16, Состояние 1, Строка 23
Синтаксический анализ XML: строка 1, символ 7, недопустимый символ xml

Однако , если я сначала преобразовал двоичный файл в varchar(MAX), а затем преобразовал его в XML, похоже, он работает нормально. Я не понимаю, что происходит, когда я выполняю тот промежуточный CAST, который отличается от преобразования непосредственно в XML. Моя главная проблема заключается в том, что я не хочу добавлять его, чтобы учесть этот сценарий, и в результате я получаю непредвиденные последствия.

Тестовый код:

DECLARE @foo VARBINARY(MAX)
DECLARE @bar VARCHAR(MAX)
DECLARE @Nbar NVARCHAR(MAX) 

--SELECT Varbinary
SET @foo = CAST( '<Test>®</Test>' AS VARBINARY(MAX)) 
SELECT @foo AsBinary


--select as binary as varchar
SET @bar = CAST(@foo AS VARCHAR(MAX))

SELECT @bar BinaryAsVarchar                             -- Correct string output

--select binary as nvarchar
SET @nbar = CAST(@foo AS NVARCHAR(MAX))
SELECT @nbar BinaryAsNvarchar                           -- Chinese characters 

--select binary as XML
SELECT TRY_CAST(@foo AS XML) BinaryAsXML                -- ILLEGAL XML character
-- SELECT CONVERT(xml, @obfoo) BinaryAsXML                    --ILLEGAL XML Character

--select BinaryAsVarcharAsXML
SELECT TRY_CAST(@bar AS XML) BinaryAsVarcharAsXML       -- Correct Output

--select BinaryAsNVarcharAsXML
SELECT TRY_CAST(@nbar AS XML) BinaryAsNvarcharAsXML     -- Chinese Characters

1 Ответ

0 голосов
/ 03 ноября 2018

Есть несколько вещей, которые нужно знать:

  • SQL-сервер довольно ограничен кодировками символов. Существует VARCHAR, то есть 1-байтовый расширенный ASCII и NVARCHAR, что составляет UCS-2 (почти то же самое, что utf-16).
  • VARCHAR использует обычный латинский для первого набора символов и отображение кодовой страницы, предоставляемое сопоставлением, используемым для второго набора.
  • VARCHAR не является utf-8 . utf-8 работает с VARCHAR, если все символы 1-байтовые. Но utf-8 знает много 2-байтовых (до 4-байтовых) символов, которые могут нарушить внутреннюю память строки VARCHAR.
  • NVARCHAR будет работать практически с любым 2-байтовым закодированным символом (то есть практически с любым существующим символом). Но это не совсем utf-16 (есть 3-байтовые закодированные символы, которые сломают внутреннюю память SQL-серверов).
  • XML хранится не в виде XML-строки, которую вы видите, а в виде иерархически организованной физической таблицы, основанной на значениях NVARCHAR.
  • Собственно хранимый XML действительно быстрый, в то время как для любого текстового хранилища заранее потребуется очень дорогая операция разбора (снова и снова ...).
  • Хранить XML как строку плохо, хранить XML как VARCHAR строка еще хуже.
  • Хранение VARCHAR -string-XML как VARBINARY - это совокупность вещей, которые вы не должны делать.

Попробуйте это:

DECLARE @text1Byte VARCHAR(100)='<test>blah</test>';
DECLARE @text2Byte NVARCHAR(100)=N'<test>blah</test>';

SELECT CAST(@text1Byte AS VARBINARY(MAX)) AS text1Byte_Binary
      ,CAST(@text2Byte AS VARBINARY(MAX)) AS text2Byte_Binary
      ,CAST(@text1Byte AS XML) AS text1Byte_XML
      ,CAST(@text2Byte AS XML) AS text2Byte_XML
      ,CAST(CAST(@text1Byte AS VARBINARY(MAX)) AS XML) AS text1Byte_XML_via_Binary
      ,CAST(CAST(@text2Byte AS VARBINARY(MAX)) AS XML) AS text2Byte_XML_via_Binary

Единственное отличие, которое вы увидите, это множество нулей в 0x3C0074006500730074003E0062006C00610068003C002F0074006500730074003E00. Это связано с 2-байтовым кодированием из nvarchar, каждый второй байт в этом примере не требуется. Но если вам понадобятся символы дальнего востока, картина будет совсем другой.

Причина, по которой это работает: SQL-сервер очень умный. Преобразование из переменной в XML довольно просто, поскольку движок знает, что лежащая в основе переменная равна varchar или nvarchar. Но последние два броска разные. Движок должен проверить двоичный файл, является ли он действительным nvarchar, и даст ему вторую попытку с varchar в случае сбоя.

Теперь попробуйте добавить ваш зарегистрированный товарный знак в данный пример. Сначала добавьте его ко второй переменной DECLARE @text2Byte NVARCHAR(100)=N'<test>blah®</test>'; и попробуйте запустить это. Затем добавьте его к первой переменной и попробуйте снова.

Что вы можете попробовать:

Приведите ваш двоичный файл к varchar(max), затем к nvarchar(max) и, наконец, к xml.

,CAST(CAST(CAST(CAST(@text1Byte AS VARBINARY(MAX)) AS VARCHAR(MAX)) AS NVARCHAR(MAX)) AS XML) AS text1Byte_XML_via_Binary

Это будет работать, но не будет быстро ...

...