Определите, являются ли какие-либо значения в столбцах NVARCHAR фактически Юникодом

14

Я унаследовал некоторые базы данных SQL Server. Существует одна таблица (я назову «G»), с примерно 86,7 миллионами строк и шириной 41 столбец, из исходной базы данных (я назову «Q») в SQL Server 2014 Standard, которая передает ETL в целевая база данных (я назову «P») с тем же именем таблицы в SQL Server 2008 R2 Standard.

то есть [Q]. [G] ---> [P]. [G]

РЕДАКТИРОВАТЬ: 20.03.2017: Некоторые люди спрашивали, является ли исходная таблица ЕДИНСТВЕННЫМ источником для целевой таблицы. Да, это единственный источник. Что касается ETL, то никаких реальных преобразований не происходит; фактически он предназначен для копирования исходных данных 1: 1. Поэтому не планируется добавлять дополнительные источники в эту целевую таблицу.

Чуть более половины столбцов в [Q]. [G] - это VARCHAR (исходная таблица):

  • 13 колонн VARCHAR (80)
  • 9 столбцов VARCHAR (30)
  • 2 из столбцов VARCHAR (8).

Аналогично, те же столбцы в [P]. [G] являются NVARCHAR (целевой таблицей) с одинаковым количеством столбцов одинаковой ширины. (Другими словами, такой же длины, но NVARCHAR).

  • 13 из столбцов НВАРЧАР (80)
  • 9 из столбцов являются NVARCHAR (30)
  • 2 из столбцов являются NVARCHAR (8).

Это не мой дизайн.

Я хотел бы изменить тип данных столбцов [P]. [G] (целевой) от NVARCHAR до VARCHAR. Я хочу сделать это безопасно (без потери данных при конвертации).

Как я могу посмотреть на значения данных в каждом столбце NVARCHAR в целевой таблице, чтобы подтвердить, действительно ли столбец содержит какие-либо данные Unicode?

Запрос (DMV?), Который может проверять каждое значение (в цикле?) Каждого столбца NVARCHAR и сообщать мне, является ли ЛЮБОЙ из значений подлинным Unicode, был бы идеальным решением, но приветствуются другие методы.

Джон Г Хоэнгартен
источник
2
Сначала рассмотрите ваш процесс и то, как используются данные. Данные в [G]ETLed для [P]. Если [G]это так varchar, и процесс ETL является единственным способом ввода данных [P], то, если процесс не добавляет истинные символы Юникода, их не должно быть. Если другие процессы добавляют или изменяют данные [P], вам нужно быть более осторожными - просто потому, что все текущие данные могут быть varchar, не означает, что nvarcharданные не могут быть добавлены завтра. Точно так же возможно, что все, что потребляет данные, [P]нуждается в nvarcharданных.
RDFozz

Ответы:

10

Предположим, что один из ваших столбцов не содержит данных Unicode. Чтобы убедиться, что вам нужно будет прочитать значение столбца для каждой строки. Если у вас нет индекса по столбцу, с таблицей хранилища строк вам нужно будет прочитать каждую страницу данных из таблицы. Имея это в виду, я думаю, что имеет смысл объединить все проверки столбцов в один запрос к таблице. Таким образом, вы не будете много раз читать данные таблицы, и вам не придется кодировать курсор или какой-либо другой цикл.

Чтобы проверить один столбец, верьте, что вы можете просто сделать это:

SELECT COLUMN_1
FROM [P].[Q]
WHERE CAST(COLUMN_1 AS VARCHAR(80)) <> CAST(COLUMN_1 AS NVARCHAR(80));

Приведение из NVARCHARв VARCHARдолжно дать вам тот же результат, за исключением случаев, когда есть символы Юникода. Символы Юникода будут преобразованы в ?. Поэтому приведенный выше код должен NULLправильно обрабатывать случаи. У вас есть 24 столбца для проверки, поэтому вы проверяете каждый столбец в одном запросе с помощью скалярных агрегатов. Одна реализация ниже:

SELECT 
  MAX(CASE WHEN CAST(COLUMN_1 AS VARCHAR(80)) <> CAST(COLUMN_1 AS NVARCHAR(80)) THEN 1 ELSE 0 END) COLUMN_1_RESULT
...
, MAX(CASE WHEN CAST(COLUMN_14 AS VARCHAR(30)) <> CAST(COLUMN_14 AS NVARCHAR(30)) THEN 1 ELSE 0 END) COLUMN_14_RESULT
...
, MAX(CASE WHEN CAST(COLUMN_23 AS VARCHAR(8)) <> CAST(COLUMN_23 AS NVARCHAR(8)) THEN 1 ELSE 0 END) COLUMN_23_RESULT
FROM [P].[Q];

Для каждого столбца вы получите результат, 1если любое из его значений содержит юникод. Результат 0означает, что все данные могут быть безопасно преобразованы.

Я настоятельно рекомендую сделать копию таблицы с новыми определениями столбцов и скопировать туда ваши данные. Вы будете делать дорогостоящие преобразования, если сделаете это на месте, поэтому создание копии может быть не намного медленнее. Наличие копии означает, что вы можете легко проверить, что все данные все еще там (одним из способов является использование ключевого слова EXCEPT ), и вы можете отменить операцию очень легко.

Также имейте в виду, что в настоящее время у вас могут отсутствовать данные Юникода, возможно, что в будущем ETL может загрузить Юникод в ранее чистый столбец. Если в вашем процессе ETL нет проверки на это, вам следует рассмотреть возможность добавления этого перед выполнением этого преобразования.

Джо Оббиш
источник
В то время как ответ и обсуждение @srutzky были довольно хорошими и содержали полезную информацию, Джо предоставил мне то, о чем просил мой вопрос: запрос, чтобы сказать мне, действительно ли какие-либо значения в столбцах имеют Unicode. Поэтому я отметил ответ Джо как принятый ответ. Я проголосовал за другие ответы, которые также помогли мне.
Джон Г Хоэнгартен
@JohnGHohengarten и Джо: все в порядке. Я не упомянул вопрос, так как он был в этом ответе, а также Скотт. Я бы просто сказал, что нет необходимости преобразовывать NVARCHARстолбец в NVARCHARтот тип, который уже есть. И не уверен, как вы определили необратимый символ, но вы можете конвертировать столбец в, VARBINARYчтобы получить последовательности байтов UTF-16. И UTF-16 - это обратный порядок байтов, поэтому p= 0x7000и затем вы обращаете эти два байта в обратном порядке, чтобы получить кодовую точку U+0070. Но если источником является VARCHAR, то это не может быть символ Unicode. Что-то еще происходит. Нужно больше информации.
Соломон Руцки
@srutzky Я добавил приведение, чтобы избежать проблем с приоритетами типов данных. Вы можете быть правы, что это не нужно. Для другого вопроса я предложил UNICODE () и SUBSTRING (). Этот подход работает?
Джо Оббиш
@JohnGHohengarten и Joe: приоритетность типов данных не должна быть проблемой, поскольку VARCHARбудет неявно преобразовываться в NVARCHARнее, но это может быть лучше CONVERT(NVARCHAR(80), CONVERT(VARCHAR(80), column)) <> column. SUBSTRINGиногда работает, но не работает с дополнительными символами, когда используются сопоставления, которые не заканчиваются _SC, а тот, который использует Джон, - нет, хотя вряд ли проблема здесь. Но преобразование в VARBINARY всегда работает. И CONVERT(VARCHAR(10), CONVERT(NVARCHAR(10), '›'))не приводит ?, поэтому я хотел бы видеть байты. Процесс ETL, возможно, преобразовал это.
Соломон Руцкий
5

Прежде чем что-то делать, рассмотрите вопросы, заданные @RDFozz в комментарии к вопросу, а именно:

  1. Существуют ли какие - либо другие источники , кроме [Q].[G]заполнения этой таблицы?

    Если ответ не соответствует «Я на 100% уверен, что это единственный источник данных для этой таблицы назначения», то не вносите никаких изменений, независимо от того, могут ли данные, находящиеся в настоящий момент в таблице, быть преобразованы без потеря данных.

  2. Есть ли какие- либо планы / обсуждения, связанные с добавлением дополнительных источников для заполнения этих данных в ближайшем будущем?

    И я хотел бы добавить , связанный с этим вопрос: Был ли какой - либо дискуссия вокруг поддержки нескольких языков в источнике тока таблице (то есть [Q].[G]) путем преобразования его в NVARCHAR?

    Вам нужно будет спросить вокруг, чтобы получить представление об этих возможностях. Я предполагаю, что в настоящее время вам не сказали ничего, что указывало бы в этом направлении, иначе вы бы не задавали этот вопрос, но если предполагается, что эти вопросы - «нет», то их нужно задать и попросить достаточно широкая аудитория, чтобы получить максимально точный / полный ответ.

Основной проблемой здесь является не столько наличие кодовых точек Unicode, которые не могут быть преобразованы (когда-либо), но еще и наличие кодовых точек, которые не все помещаются на одной кодовой странице. Это хорошая вещь в Unicode: он может содержать символы из ВСЕХ кодовых страниц. Если вы конвертируете из NVARCHAR- где вам не нужно беспокоиться о кодовых страницах - в VARCHAR, то вам нужно будет убедиться, что в столбце Сортировка назначения используется та же кодовая страница, что и в исходном столбце. Это предполагает наличие либо одного источника, либо нескольких источников, использующих одну и ту же кодовую страницу (хотя не обязательно один и тот же Collation). Но если есть несколько источников с несколькими кодовыми страницами, вы можете столкнуться со следующей проблемой:

DECLARE @Reporting TABLE
(
  ID INT IDENTITY(1, 1) PRIMARY KEY,
  SourceSlovak VARCHAR(50) COLLATE Slovak_CI_AS,
  SourceHebrew VARCHAR(50) COLLATE Hebrew_CI_AS,
  Destination NVARCHAR(50) COLLATE Latin1_General_CI_AS,
  DestinationS VARCHAR(50) COLLATE Slovak_CI_AS,
  DestinationH VARCHAR(50) COLLATE Hebrew_CI_AS
);

INSERT INTO @Reporting ([SourceSlovak]) VALUES (0xDE20FA);
INSERT INTO @Reporting ([SourceHebrew]) VALUES (0xE820FA);

UPDATE @Reporting
SET    [Destination] = [SourceSlovak]
WHERE  [SourceSlovak] IS NOT NULL;

UPDATE @Reporting
SET    [Destination] = [SourceHebrew]
WHERE  [SourceHebrew] IS NOT NULL;

SELECT * FROM @Reporting;

UPDATE @Reporting
SET    [DestinationS] = [Destination],
       [DestinationH] = [Destination]

SELECT * FROM @Reporting;

Возвращает (второй набор результатов):

ID    SourceSlovak    SourceHebrew    Destination    DestinationS    DestinationH
1     Ţ ú             NULL            Ţ ú            Ţ ú             ? ?
2     NULL            ט ת             ? ?            ט ת             ט ת

Как видите, все эти символы можно преобразовать VARCHAR, но не в одном VARCHARстолбце.

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

SELECT OBJECT_NAME(sc.[object_id]) AS [TableName],
       COLLATIONPROPERTY(sc.[collation_name], 'CodePage') AS [CodePage],
       sc.*
FROM   sys.columns sc
WHERE  OBJECT_NAME(sc.[object_id]) = N'source_table_name';

ЧТО, КАК ГОВОРИТСЯ....

Вы упомянули, что находитесь на SQL Server 2008 R2, НО, вы не сказали, что такое Edition. Если вы оказались в Enterprise Edition, то забудьте обо всех этих вещах (поскольку вы, вероятно, делаете это просто для экономии места) и включите сжатие данных:

Реализация сжатия Unicode

Если вы используете Standard Edition (а теперь кажется, что вы 😞), то есть еще одна возможность: «Обновление до SQL Server 2016, поскольку SP1 включает в себя возможность для всех выпусков использовать сжатие данных (помните, я уже говорил« долгосрочный »). "😉).

Конечно, теперь, когда только что было разъяснено, что существует только один источник данных, вам не о чем беспокоиться, так как источник не может содержать символы только для Unicode или символы вне своего конкретного кода. стр. В этом случае единственное, о чем вам следует помнить, - это использовать тот же Collation, что и в исходном столбце, или хотя бы тот, который использует ту же кодовую страницу. Это означает, что если исходный столбец использует SQL_Latin1_General_CP1_CI_AS, то вы можете использовать Latin1_General_100_CI_ASв месте назначения.

Как только вы узнаете, какой Collation использовать, вы можете:

  • ALTER TABLE ... ALTER COLUMN ...быть VARCHAR(обязательно укажите текущий NULL/ NOT NULLпараметр), который требует немного времени и много места в журнале транзакций для 87 миллионов строк, ИЛИ

  • Создайте новые столбцы «ColumnName_tmp» для каждого и медленно заполняйте, UPDATEделая TOP (1000) ... WHERE new_column IS NULL. После того, как все строки заполнены (и проверено, что все они скопированы правильно! Вам может понадобиться триггер для обработки UPDATE, если они есть), в явной транзакции используйте sp_renameдля замены имен столбцов «текущих» столбцов на « _Old ", а затем новые столбцы" _tmp ", чтобы просто удалить" _tmp "из имен. Затем вызовите sp_reconfigureтаблицу, чтобы сделать недействительными любые кэшированные планы, ссылающиеся на таблицу, и, если есть какие-либо представления, ссылающиеся на таблицу, вам нужно будет вызвать sp_refreshview(или что-то в этом роде). После того, как вы проверили приложение и ETL правильно работает с ним, вы можете удалить столбцы.

Соломон Руцкий
источник
Я выполнил запрос CodePage, который вы указали как для источника, так и для цели, и CodePage - 1252, а collation_name - SQL_Latin1_General_CP1_CI_AS для ОБА и источника и цели.
Джон Г Хоэнгартен
@JohnGHohengarten Я только что обновил, внизу. Для простоты вы можете сохранить тот же Latin1_General_100_CI_ASпорядок сортировки, даже если он намного лучше, чем тот, который вы используете. Это просто означает, что сортировка и сравнение между ними будут одинаковыми, даже если они не будут такими же хорошими, как в новом сопоставлении, о котором я только что говорил.
Соломон Руцки
4

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

Вот быстрый пример использования копии базы данных суперпользователя из дампа данных SO .

Мы сразу видим, что существуют DisplayNames с символами Unicode:

орешки

Итак, давайте добавим вычисляемый столбец, чтобы выяснить, сколько! Столбец DisplayName есть NVARCHAR(40).

USE SUPERUSER

ALTER TABLE dbo.Users
ADD DisplayNameStandard AS CONVERT(VARCHAR(40), DisplayName)

SELECT COUNT_BIG(*)
FROM dbo.Users AS u
WHERE u.DisplayName <> u.DisplayNameStandard

Количество возвращает ~ 3000 строк

орешки

План выполнения, однако, немного затруднителен. Запрос заканчивается быстро, но этот набор данных не очень большой.

орешки

Поскольку вычисляемые столбцы не нужно сохранять для добавления индекса, мы можем сделать одно из них:

CREATE UNIQUE NONCLUSTERED INDEX ix_helper
ON dbo.Users(DisplayName, DisplayNameStandard, Id)

Что дает нам немного более аккуратный план:

орешки

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

Надеюсь это поможет!

Эрик Дарлинг
источник
1

Используя пример в разделе Как проверить, содержит ли поле данные Unicode , вы можете прочитать данные в каждом столбце и выполнить CASTпроверку ниже:

--Test 1:
DECLARE @text NVARCHAR(100)
SET @text = N'This is non-Unicode text, in Unicode'
IF CAST(@text AS VARCHAR(MAX)) <> @text
PRINT 'Contains Unicode characters'
ELSE
PRINT 'No Unicode characters'
GO

--Test 2:
DECLARE @text NVARCHAR(100)
SET @text = N'This is Unicode (字) text, in Unicode'
IF CAST(@text AS VARCHAR(MAX)) <> @text
PRINT 'Contains Unicode characters'
ELSE
PRINT 'No Unicode characters'

GO
Скотт Ходжин
источник