NVARCHAR хранит символы, не поддерживаемые кодировкой UCS-2 на SQL Server

По документация (и устаревшая документация), поле nvarchar без сопоставления _SC должно использовать UCS-2 ENCODING.

Начиная с SQL Server 2012 (11.x), при использовании сортировки с поддержкой дополнительных символов (SC) эти типы данных хранят весь диапазон данных символов Unicode и используют кодировку символов UTF-16. Если указано сопоставление, отличное от SC, то эти типы данных хранят только подмножество символьных данных, поддерживаемых кодировкой символов UCS-2.

В нем также указано, что UCS-2 ENCODING хранит только символы подмножества, поддерживаемые UCS-2. Из википедии UCS-2 спецификация:

UCS-2 использует одно кодовое значение [...] от 0 до 65 535 для каждого символа и позволяет ровно два байта (одно 16-битное слово) представлять это значение. Таким образом, UCS-2 допускает двоичное представление каждой кодовой точки в BMP, которая представляет символ. UCS-2 не может представлять кодовые точки вне BMP.

Итак, согласно приведенным выше спецификациям, кажется, что я не смогу хранить смайлики, такие как: ????, которые имеют значение 0x1F60D (или 128525 в десятичном виде, намного выше предела 65535 UCS-2). Но в SQL Server 2008 R2 или SQL Server 2019 (оба с SQL_Latin1_General_CP1_CI_AS COLLATION по умолчанию) в поле nvarchar оно отлично сохраняется и возвращается (хотя не поддерживается при сравнении с LIKE или =):

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

SMSS неправильно отображает эмодзи, но вот значение, скопированное и вставленное из результата запроса: ????

Итак, мои вопросы:

  1. Действительно ли поле nvarchar использует USC-2 в SQL Server 2008 R2 (я также тестировал SQL Server 2019 с теми же сопоставлениями, отличными от _SC, и получил такие же результаты)?

  2. Документация Microsoft nchar/nvarchar вводит в заблуждение относительно того, что эти типы данных хранят только подмножество символьных данных, поддерживаемых кодировкой символов UCS-2?

  3. Поддерживает ли UCS-2 ENCODING кодовые точки после 65535?

  4. Как SQL Server мог правильно хранить и извлекать данные из этого поля, если они не поддерживаются UCS-2 ENCODING?

ПРИМЕЧАНИЕ. Сопоставление сервера — SQL_Latin1_General_CP1_CI_AS, а сопоставление поля — Latin1_General_CS_AS.
ПРИМЕЧАНИЕ 2. В исходном вопросе говорилось о тестах для SQL Server 2008. Я протестировал и получил те же результаты на SQL Server 2019. , с теми же соответствующими COLLATIONs.
ПРИМЕЧАНИЕ 3. Все остальные протестированные мной символы за пределами UCS-2 поддерживаемого диапазона ведут себя одинаково. Вот некоторые из них: ????, ????, ????, ????, ????


person Vitox    schedule 03.09.2020    source источник
comment
Комментарии не для расширенного обсуждения; этот разговор был перемещен в чат.   -  person Samuel Liew♦    schedule 03.09.2020


Ответы (1)


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

Сколько байтов на символ в SQL Server: полное руководство

Перво-наперво (это единственный способ, которым это может быть, верно?): я не оскорбляю людей, которые написали документацию по MS, поскольку SQL Server сам по себе является огромным продуктом, и есть много чего, что нужно сделать. обложка и т. д., но на данный момент (пока у меня не будет возможности обновить ее), пожалуйста, прочитайте официальную документацию с чувством осторожности. Есть несколько неверных утверждений относительно Collations/Unicode.

  1. UCS-2 — это кодировка, которая обрабатывает подмножество набора символов Unicode. Он работает в 2-байтовых блоках. С помощью 2 байтов вы можете кодировать значения от 0 до 65535. Этот диапазон кодовых точек известен как BMP (базовая многоязычная плоскость). BMP — это все символы, которые не являются дополнительными символами (поскольку они дополняют BMP), но содержат набор кодовых точек, которые используются исключительно для кодировать дополнительные символы в UTF-16 (т. е. 2048 суррогатных кодовых точек). Это полное подмножество UTF-16.

  2. UTF-16 — это кодировка, которая обрабатывает весь набор символов Unicode. Он также работает с 2-байтовыми блоками. На самом деле нет никакой разницы между UCS-2 и UTF-16 в отношении кодовых точек и символов BMP. Разница в том, что UTF-16 использует эти 2048 суррогатных кодовых точек в BMP для создания суррогатных пар, которые являются кодировками для всех дополнительных символов. Хотя дополнительные символы имеют размер 4 байта (в UTF-8, UTF-16 и UTF-32), на самом деле они представляют собой две 2-байтовые единицы кода при кодировании в UTF-16 (аналогично, они четыре 1-байтовых блока в UTF-8 и один 4-байтовый в UTF-32).

  3. Поскольку UTF-16 просто расширяет возможности UCS-2 (фактически определяя использование суррогатных кодовых точек), нет абсолютно никакой разницы в последовательностях байтов, которые можно хранить. в любом случае. Все 2048 суррогатных кодовых точек, используемых для создания дополнительных символов в UTF-16, являются допустимыми кодовыми точками в UCS-2, они просто не имеют определенного использования (т. е. интерпретации) в UCS-2.

  4. NVARCHAR, NCHAR и устаревшие типы данных "так-не-используйте-это-NTEXT" хранят символы Unicode, закодированные в UCS-2/UTF-16. С точки зрения хранения нет абсолютно никакой разницы. Таким образом, не имеет значения, если что-то (даже за пределами SQL Server) говорит, что может хранить UCS-2. Если он может это сделать, то он по своей сути может хранить UTF-16. На самом деле, хотя у меня не было возможности обновить сообщение, указанное выше, я смог сохранить и получить, как и ожидалось, смайлики (большинство из которых являются дополнительными символами) в SQL Server 2000, работающем в Windows XP. Я думаю, что до 2003 года не было определено никаких дополнительных символов, и уж точно не до 1999 года, когда разрабатывался SQL Server 2000. На самом деле (опять же) UCS-2 использовался только в Windows / SQL Server, потому что Microsoft продвинулась вперед в разработке до того, как UTF-16 была завершена и опубликована (и как только это было, UCS-2 устарел).

  5. Единственная разница между UCS-2 и UTF-16 заключается в том, что UTF-16 знает, как интерпретировать суррогатные пары (состоящие из пары суррогатных кодовых точек, поэтому, по крайней мере, они имеют соответствующие имена). Именно здесь вступают в действие параметры сортировки _SC (и, начиная с SQL Server 2017, также параметры сортировки версии _140_, которые включают поддержку дополнительных символов, поэтому ни один из них не имеет _SC в своем имени): они позволяют встроенным функциям SQL Server правильно интерпретировать дополнительные символы. Вот и все! Эти параметры сортировки не имеют ничего общего с хранением и извлечением дополнительных символов, и они даже не имеют ничего общего с их сортировкой или сравнением (хотя в документации по параметрам сортировки и поддержке Unicode говорится в частности, это то, что делают эти сопоставления — еще один пункт в моем списке дел, который нужно исправить). Для параметров сортировки, в имени которых нет ни _SC, ни _140_ (хотя новое в SQL Server 2019 Latin1_General_100_BIN2_UTF8 может быть серой областью, по крайней мере, я помню, что было некоторое несоответствие либо там, либо с сопоставления Japanese_*_140_BIN2), встроенные функции обрабатывают только кодовые точки BMP (т. е. UCS-2).

  6. Отсутствие обработки дополнительных символов означает, что допустимая последовательность из двух суррогатных кодовых точек не интерпретируется как единая дополнительная кодовая точка. Таким образом, для сопоставлений, отличных от SC, суррогатная кодовая точка BMP 1 (B1) и суррогатная кодовая точка BMP 2 (B2) — это просто те две кодовые точки, ни одна из которых не определена, поэтому они отображаются как два нуля (т. е. B1, за которым следует БИ 2). Вот почему можно разделить дополнительный символ на два, используя SUBSTRING / LEFT / RIGHT, потому что они не будут знать, как сохранить эти две кодовые точки BMP вместе. Но сопоставление SC прочитает эти кодовые точки B1 и B2 с диска или памяти и увидит одну дополнительную кодовую точку S. Теперь ее можно правильно обработать через SUBSTRING / CHARINDEX / и т. д.

  7. Функция NCHAR() (не тип данных; да, плохо названная функция;) также чувствительна к тому, поддерживает ли сопоставление по умолчанию текущей базы данных дополнительные символы. Если да, то передача значения от 65536 до 1114111 (диапазон дополнительных символов) вернет значение, отличное от NULL. Если нет, то передача любого значения выше 65535 вернет NULL. (Конечно, было бы намного лучше, если бы NCHAR() всегда работало, учитывая, что сохранение/извлечение всегда работает, поэтому, пожалуйста, проголосуйте за это предложение: Функция NCHAR() должна всегда возвращать дополнительный символ для значений 0x10000 - 0x10FFFF независимо от сортировки активной базы данных по умолчанию ) .

  8. К счастью, вам не нужна сортировка SC для вывода дополнительного символа. Вы можете либо вставить литеральный символ, либо преобразовать суррогатную пару в кодировке UTF-16 Little Endian, либо использовать функцию NCHAR() для вывода суррогатной пары. Следующее работает в SQL Server 2000 (с использованием SSMS 2005), работающем в Windows XP:

    SELECT N'????', -- ????
    CONVERT(VARBINARY(4), N'????'), -- 0x3DD8A9DC
    CONVERT(NVARCHAR(10), 0x3DD8A9DC), -- ???? (regardless of DB Collation)
    NCHAR(0xD83D) + NCHAR(0xDCA9) -- ???? (regardless of DB Collation)
    

    Дополнительные сведения о создании дополнительных символов при использовании сопоставлений, отличных от SC, см. в моем ответе на следующий вопрос DBA.SE: Как сделать Я установил строку Unicode/NVARCHAR для SQL Server в эмодзи или дополнительный символ?

  9. Ничто из этого не влияет на то, что вы видите. Если вы храните кодовую точку, то она есть. Его поведение — сортировка, сравнение и т. д. — контролируется параметрами сортировки. Но то, как это выглядит, контролируется шрифтами и ОС. Ни один шрифт не может содержать все символы, поэтому разные шрифты содержат разные наборы символов, причем наиболее часто используемые символы часто перекрываются. Однако, если шрифт имеет сопоставленную последовательность байтов, он может отображать этот символ. Вот почему единственная работа, необходимая для правильного отображения дополнительных символов в SQL Server 2000 (с использованием SSMS 2005), работающем в Windows XP, заключалась в добавлении шрифта, содержащего символы, и внесении одного или двух незначительных изменений в реестр (без изменений в SQL Server).

  10. Дополнительные символы в SQL_* сопоставлениях и сопоставлениях без номера версии в имени не имеют весов сортировки. Следовательно, все они приравниваются друг к другу, а также к любым другим кодовым точкам BMP, которые не имеют весов сортировки (включая пробел (U+0020) и ноль (U+0000)). Это начали исправлять в версии _90_ collations.

  11. SSMS не имеет ничего общего со всем этим, за исключением возможной необходимости замены шрифта, используемого для редактора запросов и/или результатов сетки, и/или сообщений об ошибках + на шрифт с нужными символами. (SSMS не отображает ничего за пределами, возможно, пространственных данных; символы визуализируются драйвером дисплея + определениями шрифтов +, возможно, чем-то еще).

Поэтому следующее утверждение в документации (из вопроса):

Если указано сопоставление, отличное от SC, то эти типы данных хранят только подмножество символьных данных, поддерживаемых кодировкой символов UCS-2.

и бессмысленно, и неправильно. Вероятно, они имели в виду, что типы данных будут хранить только подмножество кодировки UTF-16 (поскольку UCS-2 является подмножеством). Кроме того, даже если в нем указана кодировка символов UTF-16, это все равно будет неправильно, потому что байты, которые вы передаете, будут сохранены (при условии достаточного свободного места в столбце или переменной).

person Solomon Rutzky    schedule 03.09.2020
comment
Удивительный ответ! Я очень ценю все усилия и время, потраченное на это. Большое спасибо за то, что поделились своими знаниями! Но... - person Vitox; 03.09.2020
comment
Тем не менее, есть одна последняя вещь, которую я не понял о низкоуровневом материале: UTF-16 использует суррогатные пары для расширения возможных кодовых точек за пределы 2 байтов (BMP) до 4 байтов. Хорошо, вот как он может хранить большую кодовую точку 0x1F60D. Как может UCS-2 всего с 2 байтами хранить значение, превышающее 2 байта? Движок разбивает большую кодовую точку 0x1F60D, а чем использует 2 набора по 2 байта? - person Vitox; 03.09.2020
comment
Я имею в виду, когда я передаю значение 0x1F60D длиной 4 байта в кодировку, которая отображает только до 2 байтов, как она правильно разбивает его!? Как он вообще может позволить получить гораздо большее число (то есть кодовую точку)? - person Vitox; 03.09.2020
comment
Я узнал (и везде это говорится), что кодировка отвечает за отображение de code point в последовательность байтов и наоборот. Итак, как может кодировка типа UCS-2, которая ограничена 2 байтами, обрабатывать (даже не интерпретируя) значение 4 байта? - person Vitox; 03.09.2020
comment
Это ожидаемое поведение происходит, если я использую еще более ограниченный encoding, например 1 байт Windows-1252. Он просто не может хранить большое значение 0x1F60D, независимо от того, сколько места у меня есть. Он не будет сопоставлять 0x1F60D ни с какой допустимой последовательностью байтов... - person Vitox; 03.09.2020
comment
А в другом ответе по предоставленной вами ссылке (кстати, еще один отличный ответ) вы заявили, что [...] И диапазон 65536 - 1114111 (0x10000 - 0x10FFFF) UTF-16 состоит из двух кодовых точек. в диапазоне UCS-2 (в частности, в диапазонах 0xD800 — 0xDBFF и 0xDC00 — 0xDFFF). Хорошо, это имеет смысл, но только если я сопоставляю UCS-2 с UTF-16. Как вы сказали, UTF-16 знает, что эти байты нужно объединить. Но как это может работать наоборот? От большой кодовой точки 0x1F60D к двум меньшим кодовым точкам? Кто занимается этим преобразованием? - person Vitox; 03.09.2020
comment
@Vitox Добро пожаловать. И UTF-16, и UCS-2 работают с 2-байтовыми блоками. Дополнительные символы (например, 0x1F60D) не кодируются в UCS-2, поскольку для них никогда не было перевода. НО, UTF-16 просто переводит эту точку дополнительного кода в две единицы кода BMP/UCS-2. Не думай о Супе. Символы имеют размер 4 байта, думайте о них как о всего 4 байта. UTF-8 использует четыре 1-байтовых кода unit для кодирования Sup. Chars, но это все же 8-битная кодировка, а не 32-битная. UCS-2 не знает, что такое кодовые точки выше U+FFFF, но без проблем хранит U+D83D и U+DCA9 (продолжение...) - person Solomon Rutzky; 03.09.2020
comment
две кодовые точки BMP, используемые для кодирования дополнительной кодовой точки U+1F4A9. Это связано с тем, что 0x3DD8A9DC — кодировка UCS-2/UTF-16 Little Endian этих двух кодовых точек BMP — допустима в UCS-2: это старший суррогат, за которым следует младший суррогат. Разница между UTF-16 и UCS-2 заключается в том, что UCS-2 не рассматривает эту комбинацию закодированных кодовых точек как что-то особенное, кроме двух суррогатных кодовых точек, не имеющих значения. UTF-16 видит эту комбинацию и сообщает, что это не две кодовые точки, а фактически одна кодовая точка, которая оказывается дополнительной. (продолжение) - person Solomon Rutzky; 03.09.2020
comment
Имейте в виду, что кодовые точки не являются единицами кода. Кодовые баллы — это просто числа, обозначающие определенный символ. Они ничего не говорят о том, как они хранятся (то есть кодируются) в памяти или на диске. Количество байтов кодовой точки не имеет значения (технически самая старшая кодовая точка, U+10FFFF, будет состоять только из 3 байтов, если это число будет представлять байты). Кодовые единицы — это 1-, 2- или 4-байтовые блоки, используемые для физического представления кодовых точек в кодировках UTF-8, UTF-16 и UTF-32. , соответственно. Кодовые точки и кодовые единицы одинаковы для символов BMP в UTF-16 и UCS-2 (продолжение) - person Solomon Rutzky; 03.09.2020
comment
что может быть очень запутанным, но это не одно и то же. Наконец, причина, по которой вы можете хранить дополнительные символы в UCS-2, заключается в том, что а) вы предоставляете кодировку (т.е. 0x3DD8A9DC или NCHAR(0xD83D) + NCHAR(0xDCA9)) и б) байты — это просто байты, а драйвер шрифта/дисплея Обработчик /OS, отображающий представленные байты — им все равно, как эти байты были сохранены. Помните, что для того, чтобы это работало в Windows XP, не требовалось изменений в SSMS, только изменение реестра и шрифта, содержащего символы. - person Solomon Rutzky; 03.09.2020