Phát hiện nếu có bất kỳ giá trị nào trong các cột NVARCHAR thực sự là unicode


14

Tôi đã kế thừa một số cơ sở dữ liệu SQL Server. Có một bảng (tôi sẽ gọi "G"), với khoảng 86,7 triệu hàng và rộng 41 cột, từ cơ sở dữ liệu nguồn (tôi sẽ gọi "Q") trên Tiêu chuẩn SQL Server 2014 được ETL'd chuyển đến cơ sở dữ liệu đích (tôi sẽ gọi "P") với cùng tên bảng trên Tiêu chuẩn SQL Server 2008 R2.

tức là [Q]. [G] ---> [P]. [G]

EDIT: 3/20/2017: Một số người đã hỏi liệu bảng nguồn có phải là nguồn CHỈ cho bảng đích không. Vâng, nó là nguồn duy nhất. Theo như ETL, không có bất kỳ sự chuyển đổi thực sự nào xảy ra; nó thực sự được dự định là bản sao 1: 1 của dữ liệu nguồn. Do đó, không có kế hoạch để thêm các nguồn bổ sung vào bảng mục tiêu này.

Hơn một nửa số cột trong [Q]. [G] là VARCHAR (bảng nguồn):

  • 13 trong số các cột là VARCHAR (80)
  • 9 trong số các cột là VARCHAR (30)
  • 2 trong số các cột là VARCHAR (8).

Tương tự, các cột giống nhau trong [P]. [G] là NVARCHAR (bảng đích), có cùng # cột có cùng độ rộng. (Nói cách khác, cùng độ dài, nhưng NVARCHAR).

  • 13 trong số các cột là NVARCHAR (80)
  • 9 trong số các cột là NVARCHAR (30)
  • 2 trong số các cột là NVARCHAR (8).

Đây không phải là thiết kế của tôi.

Tôi muốn thay đổi kiểu dữ liệu cột [P]. [G] (đích) từ NVARCHAR đến VARCHAR. Tôi muốn làm điều đó một cách an toàn (không mất dữ liệu từ chuyển đổi).

Làm cách nào tôi có thể xem các giá trị dữ liệu trong mỗi cột NVARCHAR trong bảng đích để xác nhận xem cột có thực sự chứa bất kỳ dữ liệu Unicode nào không?

Một truy vấn (DMV?) Có thể kiểm tra từng giá trị (trong một vòng lặp?) Của từng cột NVARCHAR và cho tôi biết nếu BẤT K of giá trị nào là Unicode chính hãng sẽ là giải pháp lý tưởng, nhưng các phương pháp khác đều được chào đón.


2
Đầu tiên, hãy xem xét quá trình của bạn và cách sử dụng dữ liệu. Dữ liệu trong [G]được ETLed qua [P]. Nếu [G]varchar , và quy trình ETL là cách duy nhất để dữ liệu đi vào [P], trừ khi quy trình thêm các ký tự Unicode thực sự, không nên có bất kỳ. Nếu các quy trình khác thêm hoặc sửa đổi dữ liệu [P], bạn cần cẩn thận hơn - chỉ vì tất cả dữ liệu hiện tại có thể varcharkhông có nghĩa là nvarchardữ liệu không thể được thêm vào ngày mai. Tương tự như vậy, có thể là bất cứ điều gì đang tiêu thụ dữ liệu trong [P]nhu cầu nvarchardữ liệu.
RDFozz

Câu trả lời:


10

Giả sử một trong các cột của bạn không chứa bất kỳ dữ liệu unicode nào. Để xác minh rằng bạn sẽ cần đọc giá trị cột cho mỗi hàng. Trừ khi bạn có một chỉ mục trên cột, với bảng lưu trữ hàng, bạn sẽ cần đọc mọi trang dữ liệu từ bảng. Với ý nghĩ đó, tôi nghĩ sẽ rất có ý nghĩa khi kết hợp tất cả các kiểm tra cột thành một truy vấn duy nhất đối với bảng. Bằng cách đó, bạn sẽ không đọc dữ liệu của bảng nhiều lần và bạn không phải mã hóa con trỏ hoặc một loại vòng lặp khác.

Để kiểm tra một cột duy nhất tin rằng bạn chỉ có thể làm điều này:

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

Một diễn viên từ NVARCHARđến VARCHARsẽ cho bạn kết quả tương tự ngoại trừ nếu có các ký tự unicode. Ký tự Unicode sẽ được chuyển đổi thành ?. Vì vậy, các mã trên nên xử lý NULLcác trường hợp chính xác. Bạn có 24 cột để kiểm tra, vì vậy bạn kiểm tra từng cột trong một truy vấn bằng cách sử dụng tổng hợp vô hướng. Một cách thực hiện dưới đây:

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];

Đối với mỗi cột, bạn sẽ nhận được kết quả 1nếu bất kỳ giá trị nào của nó chứa unicode. Một kết quả 0có nghĩa là tất cả dữ liệu có thể được chuyển đổi một cách an toàn.

Tôi thực sự khuyên bạn nên tạo một bản sao của bảng với các định nghĩa cột mới và sao chép dữ liệu của bạn ở đó. Bạn sẽ thực hiện các chuyển đổi đắt tiền nếu bạn thực hiện tại chỗ để tạo một bản sao có thể không chậm hơn nhiều. Có một bản sao có nghĩa là bạn có thể dễ dàng xác nhận rằng tất cả dữ liệu vẫn còn đó (một cách là sử dụng từ khóa EXCEPT ) và bạn có thể hoàn tác thao tác rất dễ dàng.

Ngoài ra, hãy lưu ý rằng hiện tại bạn có thể không có bất kỳ dữ liệu unicode nào, có thể một ETL trong tương lai có thể tải unicode vào một cột sạch trước đó. Nếu không có kiểm tra cho việc này trong quy trình ETL của bạn, bạn nên xem xét thêm điều đó trước khi thực hiện chuyển đổi này.


Mặc dù câu trả lời và thảo luận từ @srutzky khá tốt và có thông tin hữu ích, Joe đã cung cấp cho tôi câu hỏi mà tôi đang hỏi: một truy vấn để cho tôi biết nếu có bất kỳ giá trị nào trong các cột thực sự có Unicode. Vì vậy, tôi đã đánh dấu câu trả lời của Joe là câu trả lời được chấp nhận. Tôi đã bình chọn những câu trả lời khác cũng giúp tôi.
John G Hohengarten

@JohnGHohengarten và Joe: Điều đó tốt. Tôi đã không đề cập đến truy vấn vì nó nằm trong câu trả lời này cũng như của Scott. Tôi chỉ muốn nói rằng không cần phải chuyển đổi NVARCHARcột thành NVARCHARkiểu đó. Và không chắc chắn làm thế nào bạn xác định được ký tự không thể chuyển đổi, nhưng bạn có thể chuyển đổi cột thành VARBINARYđể có được chuỗi byte UTF-16. Và UTF-16 là thứ tự byte đảo ngược, vì vậy p= 0x7000và sau đó bạn đảo ngược hai byte đó để lấy Code Point U+0070. Nhưng, nếu nguồn là VARCHAR, thì đó không thể là ký tự Unicode. Có một cái gì đó đang xảy ra. Cần thêm thông tin.
Solomon Rutzky

@srutzky Tôi đã thêm các diễn viên để tránh các vấn đề ưu tiên kiểu dữ liệu. Bạn có thể đúng rằng nó không cần thiết. Đối với câu hỏi khác, tôi đề nghị UNICODE () và SUBSTRING (). Cách tiếp cận đó có hiệu quả không?
Joe Obbish

@JohnGHohengarten và Joe: ưu tiên loại dữ liệu không phải là một vấn đề vì VARCHARsẽ hoàn toàn chuyển đổi sang NVARCHAR, nhưng có lẽ tốt hơn nên làm CONVERT(NVARCHAR(80), CONVERT(VARCHAR(80), column)) <> column. SUBSTRINGđôi khi hoạt động, nhưng nó không hoạt động với các ký tự bổ sung khi sử dụng Collations không kết thúc _SCvà cái mà John đang sử dụng không, mặc dù không có khả năng là vấn đề ở đây. Nhưng chuyển đổi sang VARBINARY luôn hoạt động. Và CONVERT(VARCHAR(10), CONVERT(NVARCHAR(10), '›'))không có kết quả ?, vì vậy tôi muốn xem các byte. Quá trình ETL có thể đã chuyển đổi nó.
Solomon Rutzky

5

Trước khi làm bất cứ điều gì, vui lòng xem xét các câu hỏi được đặt ra bởi @RDFozz trong một nhận xét về câu hỏi, cụ thể là:

  1. bất kỳ nguồn khác ngoài việc điền [Q].[G]vào bảng này không?

    Nếu phản hồi là bất cứ điều gì ngoài "Tôi chắc chắn 100% rằng đây là nguồn dữ liệu duy nhất cho bảng đích này", thì đừng thực hiện bất kỳ thay đổi nào, bất kể dữ liệu hiện tại trong bảng có thể được chuyển đổi mà không mất dữ liệu.

  2. Có ở đó không bất kỳ kế hoạch / thảo luận liên quan đến việc thêm nguồn bổ sung để cư dữ liệu này trong tương lai gần?

    Và tôi sẽ thêm một câu hỏi liên quan: Đã có bất kỳ cuộc thảo luận nào về việc hỗ trợ nhiều ngôn ngữ trong bảng nguồn hiện tại (tức là [Q].[G]) bằng cách chuyển đổi thành NVARCHAR?

    Bạn sẽ cần phải hỏi xung quanh để hiểu được những khả năng này. Tôi cho rằng bạn hiện chưa được nói bất cứ điều gì sẽ chỉ theo hướng này nếu không bạn sẽ hỏi câu hỏi này, nhưng nếu những câu hỏi này được cho là "không", thì chúng cần được hỏi và được hỏi về đối tượng đủ rộng để có được câu trả lời chính xác / đầy đủ nhất.

Vấn đề chính ở đây không phải là có quá nhiều điểm mã Unicode không thể chuyển đổi (bao giờ), mà còn có nhiều điểm mã không phù hợp với một trang mã. Đó là điều hay về Unicode: nó có thể chứa các ký tự từ TẤT CẢ các trang mã. Nếu bạn chuyển đổi từ NVARCHAR- nơi bạn không cần phải lo lắng về các trang mã - sang VARCHAR, thì bạn sẽ cần đảm bảo rằng Collation của cột đích đang sử dụng cùng một trang mã với cột nguồn. Điều này giả sử có một nguồn hoặc nhiều nguồn sử dụng cùng một trang mã (mặc dù không nhất thiết phải là Collation giống nhau). Nhưng nếu có nhiều nguồn với nhiều trang mã, thì bạn có khả năng gặp phải vấn đề sau:

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;

Trả về (tập kết quả thứ 2):

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

Như bạn có thể thấy, tất cả các ký tự đó có thể chuyển đổi thành VARCHAR, chỉ không trong cùng một VARCHARcột.

Sử dụng truy vấn sau để xác định trang mã nào cho mỗi cột trong bảng nguồn của bạn:

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';

ĐÓ LÀ NÓI ....

Bạn đã đề cập đến trên SQL Server 2008 R2, NHƯNG, bạn không nói Phiên bản nào. NẾU bạn tình cờ có trên Phiên bản doanh nghiệp, sau đó quên tất cả nội dung chuyển đổi này (vì bạn có thể làm việc đó chỉ để tiết kiệm dung lượng) và bật Nén dữ liệu:

Thực hiện nén Unicode

Nếu sử dụng Phiên bản Chuẩn (và có vẻ như bạn là 😞) thì có một khả năng khác là: nâng cấp lên SQL Server 2016 kể từ SP1 bao gồm khả năng cho tất cả các Phiên bản sử dụng Nén dữ liệu (hãy nhớ, tôi đã nói "bắn lâu ").

Tất nhiên, bây giờ chúng ta mới làm rõ rằng chỉ có một nguồn cho dữ liệu, thì bạn không có gì phải lo lắng vì nguồn này không chứa bất kỳ ký tự hoặc ký tự Unicode nào ngoài mã cụ thể của nó trang. Trong trường hợp đó, điều duy nhất bạn cần lưu ý là sử dụng Collation giống như cột nguồn hoặc ít nhất một cái đang sử dụng cùng một Trang Mã. Có nghĩa là, nếu cột nguồn đang sử dụng SQL_Latin1_General_CP1_CI_AS, thì bạn có thể sử dụngLatin1_General_100_CI_AS tại đích.

Khi bạn biết Collation sẽ sử dụng, bạn có thể:

  • ALTER TABLE ... ALTER COLUMN ...để được VARCHAR(chắc chắn chỉ định hiện tại NULL/ NOT NULLcài đặt), yêu cầu một chút thời gian và nhiều không gian nhật ký giao dịch cho 87 triệu hàng, HOẶC

  • Tạo các cột "ColumnName_tmp" mới cho mỗi cột và từ từ cư trú thông qua UPDATEviệc thực hiện TOP (1000) ... WHERE new_column IS NULL. Khi tất cả các hàng được điền (và được xác thực rằng tất cả chúng đều được sao chép chính xác! Bạn có thể cần một trình kích hoạt để xử lý CẬP NHẬT, nếu có), trong một giao dịch rõ ràng, hãy sử dụng sp_renameđể hoán đổi tên cột của các cột "hiện tại" thành " _Old "và sau đó các cột" _tmp "mới chỉ cần xóa" _tmp "khỏi tên. Sau đó gọi sp_reconfigurevào bảng để vô hiệu hóa bất kỳ gói được lưu trong bộ nhớ cache nào tham chiếu bảng và nếu có bất kỳ Chế độ xem nào tham chiếu bảng, bạn sẽ cần gọi sp_refreshview(hoặc đại loại như thế). Khi bạn đã xác thực ứng dụng và ETL đang hoạt động chính xác với ứng dụng đó, thì bạn có thể thả các cột.


Tôi đã chạy truy vấn CodePage mà bạn đã cung cấp trên cả nguồn và đích và CodePage là 1252 và collation_name là SQL_Latin1_General_CP1_CI_AS trên nguồn BÓNG VÀ đích.
John G Hohengarten

@JohnGHohengarten Tôi vừa cập nhật lại, ở phía dưới. Để dễ dàng, bạn có thể giữ cùng một Collation, mặc dù Latin1_General_100_CI_AStốt hơn nhiều so với cái bạn đang sử dụng. Dễ dàng có nghĩa là hành vi sắp xếp và so sánh sẽ giống nhau giữa chúng, ngay cả khi không tốt như Collation mới hơn mà tôi vừa đề cập.
Solomon Rutzky

4

Tôi có một số kinh nghiệm với điều này từ khi tôi có một công việc thực sự. Vì tại thời điểm tôi muốn lưu giữ dữ liệu cơ sở và tôi cũng phải tính đến dữ liệu mới có thể có các ký tự bị mất trong shuffle, tôi đã đi với một cột được tính toán không tồn tại.

Dưới đây là một ví dụ nhanh bằng cách sử dụng bản sao cơ sở dữ liệu Siêu người dùng từ kết xuất dữ liệu SO .

Chúng ta có thể thấy ngay con dơi có DisplayNames với các ký tự Unicode:

Quả hạch

Vì vậy, hãy thêm một cột được tính toán để tìm ra có bao nhiêu! Cột DisplayName là 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

Số lượng trả về ~ 3000 hàng

Quả hạch

Kế hoạch thực hiện là một chút kéo, mặc dù. Truy vấn kết thúc nhanh, nhưng bộ dữ liệu này không quá lớn.

Quả hạch

Vì các cột được tính toán không cần phải được duy trì để thêm chỉ mục, chúng tôi có thể thực hiện một trong các cách sau:

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

Cung cấp cho chúng tôi một kế hoạch gọn gàng hơn một chút:

Quả hạch

Tôi hiểu nếu đây không phải câu trả lời, vì nó liên quan đến thay đổi kiến ​​trúc, nhưng xem xét kích thước của dữ liệu, có lẽ bạn đang xem việc thêm chỉ mục để đối phó với các truy vấn tự tham gia bảng.

Hi vọng điêu nay co ich!


1

Sử dụng ví dụ trong Cách kiểm tra xem một trường có chứa dữ liệu unicode hay không , bạn có thể đọc dữ liệu trong mỗi cột và thực hiện CASTvà kiểm tra bên dưới:

--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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.