TSQL «Недопустимый XML-символ» при преобразовании Varbinary в XML

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

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

CAST(Column AS XML)

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

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

Msg 9420, Level 16, State 1, Line 23
XML parsing: line 1, character 7, illegal xml character

тем не мение, если я сначала конвертирую двоичный файл в 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
varchar означает ASCII или, по крайней мере, однобайтовый текст. ® находится за пределами диапазона 0–127, на который не влияют кодовые страницы. Попробуйте с nvarchar и CAST( N'<Test>®</Test>' as varbinary(max)). nvarchar означает UTF16, т.е. два байта, поэтому преобразование из varchar в varbinary в nvarchar не удалось.
Panagiotis Kanavos 02.11.2018 18:15

Почему вы вообще делаете любой этих преобразований? Какую бы проблему вы ни хотели решить, смешение типов не поможет. Если у вас есть ошибки кодирования, убедитесь, что вы всегда используете поля nvarchar, строковые литералы параметров а также.

Panagiotis Kanavos 02.11.2018 18:16

Кстати, это означает, что преобразования, которые сработали, на самом деле неверны - они зависят от использования одной и той же кодировки как при преобразовании в varbinary, так и обратно в текст

Panagiotis Kanavos 02.11.2018 18:18

Наконец, почему пакет SSIS сохранил текст в столбце варбинарный? Это просто просит для проблем с конвертацией. Я подозреваю, что первоначальный автор использовал varchar, столкнулся с проблемами преобразования и вместо того, чтобы использовать правильные сопоставления столбцов или переключиться на nvarchar, просто прикрыл проблему с помощью varbinary. Это ничего не исправило, просто перенесло проблемы с кодировкой в ​​ридер. Это работало до тех пор, пока не было нелатинских символов (что также не вызвало бы проблем с varchar). Когда был добавлен первый нелатинский символ, бум, считыватель не смог

Panagiotis Kanavos 02.11.2018 18:19

Встроенные преобразования из вопроса: CAST(CAST( '<Test>®</Test>' AS VARBINARY(MAX)) AS NVARCHAR(MAX)) Э, не делайте этого. Основное правило кодировки символов - читать с той кодировкой, которая использовалась для записи.

Tom Blodget 03.11.2018 20:04

@TomBlodget Хотя ваше утверждение совершенно верно, я предполагаю, что OP должен иметь дело с мусором, созданным кем-то другим. SQL-Server не очень силен в работе с кодировками. Может быть, в этом месте невозможно читать с кодировкой, которая использовалась для записи ...

Shnugo 04.11.2018 11:14

@Shnugo Как только он выяснил, как данные были искажены, возможно, его можно будет отменить, используя SQL CLR, если это необходимо.

Tom Blodget 04.11.2018 12:50

@TomBlodget Вот что я имею в виду ... Если бы мои проекты не могли использовать функции CLR ... Иногда нам приходится иметь дело с мусором. Я думаю, что многократное приведение от varbin к varchar, затем к nvarchar и, наконец, к xml может решить эту проблему, как показано в моем ответе ...

Shnugo 04.11.2018 13:26

Я думаю, что первоначальный автор использовал varbinary как способ обойти ограничение строковых символов в SSIS. Прочитав все комментарии, я попытался быстро переработать таблицу package + SQL, чтобы использовать строку Unicode + nvarchar, и столкнулся с этой ошибкой Description: "Unicode data is odd byte size for column 11. Should be even byte size.". Из того, что я прочитал, вы можете решить эту проблему, явно указав четную длину строки, но тогда SSIS ограничивает вас до 4000 символов.

Evan Prickett 13.11.2018 17:39

Я думаю, что моим решением будет загрузка исходного XML непосредственно в столбец SQL XML с использованием OPENROWSET и массового импорта SQL в задаче сценария.

Evan Prickett 13.11.2018 18:31
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
1 965
1

Ответы 1

Необходимо знать несколько вещей:

  • SQL-Server довольно ограничен кодировками символов. Есть 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

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

Другие вопросы по теме