Визначте, чи є якісь значення в стовпцях NVARCHAR насправді unicode


14

Я успадкував деякі бази даних SQL Server. Є одна таблиця (я зателефоную "G") із 86,7 мільйонами рядків та 41 колоною завширшки із вихідної бази даних (я буду називати "Q") у стандарті SQL Server 2014, який переходить 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 стовпців - NVARCHAR (80)
  • 9 стовпців - NVARCHAR (30)
  • 2 колонки - NVARCHAR (8).

Це не моя конструкція.

Я хотів би ВЗНАЧИТИ [P]. [G] (цільові) типи даних стовпців від NVARCHAR до VARCHAR. Я хочу це зробити безпечно (без втрати даних від конверсії).

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

Запит (DMVs?), Який може перевірити кожне значення (в циклі?) Кожного стовпця NVARCHAR і підкаже мені, чи будь-яке значення є справжнім Unicode, було б ідеальним рішенням, але інші методи вітаються.


2
Спочатку розгляньте свій процес та спосіб використання даних. Дані в [G]ETLed передаються в [P]. Якщо [G]є varchar, і процес ETL - єдиний спосіб надходження даних [P], то, якщо процес додає справжні символи Unicode, їх не повинно бути. Якщо в інші процеси додаються або змінюються дані [P], вам потрібно бути більш уважними - лише тому, що всі поточні дані можуть бути varcharне означає, що nvarcharдані не можуть бути додані завтра. Так само можливо, що все, що споживає дані, [P]потребує nvarcharданих.
RDFozz

Відповіді:


10

Припустимо, один із ваших стовпців не містить даних про однокодування. Щоб переконатися, що вам потрібно буде прочитати значення стовпця для кожного рядка. Якщо у вас немає індексу на стовпці, з таблицею зберігання рядків вам потрібно буде прочитати кожну сторінку даних із таблиці. Зважаючи на це, я думаю, що має сенс поєднувати всі перевірки стовпців в один запит проти таблиці. Таким чином, ви не будете читати дані таблиці багато разів, і вам не доведеться кодувати курсор чи інший цикл.

Щоб перевірити один стовпець, вважайте, що ви можете просто зробити це:

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

Акторський склад NVARCHAR до VARCHARповинен дати вам такий же результат, за винятком випадків, коли є символи unicode. Символи Unicode будуть перетворені в ?. Таким чином, наведений вище код повинен 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 ), і ви можете скасувати операцію дуже легко.

Також майте на увазі, що у вас, можливо, немає даних про unicode, можливо, майбутній ETL може завантажувати unicode у попередньо чистий стовпчик. Якщо у вашому процесі ETL немає перевірки на це, слід подумати про те, щоб додати це, перш ніж робити це перетворення.


Хоча відповідь та обговорення від @srutzky були досить хорошими та мали корисну інформацію, Джо надав мені те, про що моє запитання просив: запит, щоб сказати мені, чи є у будь-яких значеннях стовпців Unicode. Тому я позначив відповідь Джо як прийняту відповідь. Я підголосив інші відповіді, які також мені допомогли.
Джон G Hohengarten

@JohnGHohengarten and Joe: Це добре. Я не згадував запит, оскільки він був у цій відповіді так само, як і у Скотта. Я б просто сказав, що не потрібно перетворювати NVARCHARстовпчик, NVARCHARоскільки це вже такий тип. І не впевнений, як ви визначили неконвертований символ, але ви можете перетворити стовпець, VARBINARYщоб отримати послідовності байтів UTF-16. І UTF-16 - це зворотний порядок байт, так що p= 0x7000і тоді ви повернете ці два байти, щоб отримати Code Point 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, тоді вам потрібно буде переконатися, що для зіставлення стовпця пункту призначення використовується та сама сторінка коду, що і джерельна колонка. Це передбачає наявність одного джерела або декількох джерел з використанням однієї і тієї ж кодової сторінки (не обов'язково однакову Збірку, хоча). Але якщо є кілька джерел з декількома кодовими сторінками, ви потенційно можете зіткнутися з такою проблемою:

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;

Повернення (2-й набір результатів):

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, АЛЕ, ви не сказали, що таке Видання. Якщо ви трапляєте в Enterprise Edition, то забудьте про всі ці матеріали конверсії (оскільки ви, ймовірно, робите це просто для економії місця), і ввімкніть стиснення даних:

Реалізація стиснення Unicode

Якщо ви використовуєте Standard Edition (а тепер здається, що ви 😞), є ще одна можливість looooong-shot: оновлення до 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для заміни імен стовпців "поточних" стовпців, які мають бути " _Старий ", а потім нові" _tmp "стовпці, щоб просто видалити" _tmp "з імен. Потім зателефонуйте sp_reconfigureна таблицю, щоб визнати недійсними будь-які кешовані плани з посиланням на таблицю, і якщо є перегляди, на які посилається таблиця, вам потрібно буде зателефонувати sp_refreshview(або щось подібне). Після того, як ви перевірили додаток і ETL правильно працює з ним, ви можете скидати стовпці.


Я запустив запит CodePage, який ви надали як у вихідному, так і в цільовому, а CodePage - 1252, а назва_меншення - SQL_Latin1_General_CP1_CI_AS на BOTH source AND target.
Джон Г Хогенгартен

@JohnGHohengarten Я просто оновився знову, внизу. Щоб вам було зручно, ви можете зберегти той самий Collation, навіть якщо Latin1_General_100_CI_ASвін набагато кращий за той, який ви використовуєте. Легко означає, що поведінка щодо сортування та порівняння між ними буде однаковою, навіть якщо не такою ж доброю, як новіша зібрання, яку я тільки що згадував.
Соломон Руцький

4

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

Ось короткий приклад використання копії бази даних Super User з дампів 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.