NVARCHAR 열의 값이 실제로 유니 코드인지 감지


14

일부 SQL Server 데이터베이스를 상속했습니다. SQL Server 2014 Standard의 소스 데이터베이스 ( "Q"라고 함)에서 ETL을받는 테이블이 하나 있는데 ( "G"라고 함) 약 8670 만 개의 행과 41 열의 너비가 있습니다. SQL Server 2008 R2 Standard에서 동일한 테이블 이름을 가진 대상 데이터베이스 ( "P"라고 함)

즉 [Q]. [G] ---> [P]. [G]

편집 : 2017 년 3 월 20 일 : 일부 사람들은 소스 테이블이 대상 테이블의 유일한 소스인지 묻습니다. 예, 유일한 소스입니다. 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)입니다.

이것은 내 디자인이 아닙니다.

NVARCHAR에서 VARCHAR로 [P]. [G] (대상) 열 데이터 유형을 ALTER하고 싶습니다. 변환으로 인한 데이터 손실없이 안전하게하고 싶습니다.

대상 테이블의 각 NVARCHAR 열에있는 데이터 값을보고 열에 실제로 유니 코드 데이터가 포함되어 있는지 확인하려면 어떻게해야합니까?

각 NVARCHAR 열의 각 값 (루프에서?)을 검사하고 값 중 하나가 진짜 유니 코드인지 여부를 알려주는 쿼리 (DMV?)는 이상적인 솔루션이지만 다른 방법도 환영합니다.


2
먼저 프로세스와 데이터 사용 방법을 고려하십시오. 의 데이터는 [G]에 ETL됩니다 [P]. 경우 [G]이며 varchar, 그리고 ETL 프로세스로 데이터를 제공하는 유일한 방법입니다 [P], 프로세스가 진정한 유니 코드 문자를 추가 한 후 언급되어 있지 않는 한,이 안된다. 다른 프로세스가에 데이터를 추가 또는 수정 [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당신에게 유니 코드 문자가있는 경우를 제외하고 동일한 결과를 제공해야합니다. 유니 코드 문자는로 변환됩니다 ?. 따라서 위의 코드는 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의 답변과 토론은 꽤 좋았고 유용한 정보가 있었지만 Joe는 내 질문에 대한 정보를 제공했습니다. 열의 값에 실제로 유니 코드가 있는지 알려주는 쿼리입니다. 따라서 나는 Joe의 대답을 수용된 대답으로 표시했습니다. 나에게도 도움이 된 다른 답변을 투표했습니다.
존 G Hohengarten

@ JohnGHohengarten과 Joe : 좋습니다. Scott의 답변뿐만 아니라이 답변에 있기 때문에 쿼리에 대해서는 언급하지 않았습니다. 나는 이미 그 유형이므로 NVARCHAR열을 변환 할 필요가 없다고 말하고 싶습니다 NVARCHAR. 변환 할 수없는 문자를 어떻게 결정했는지 확실하지 않지만 VARBINARYUTF-16 바이트 시퀀스를 얻기 위해 열을 변환 할 수 있습니다 . UTF-16은 바이트 순서의 역순이므로 p= 0x7000를 입력 한 다음이 두 바이트를 반대로하여 Code Point를 가져옵니다 U+0070. 그러나 소스가 VARCHAR이면 유니 코드 문자가 될 수 없습니다. 다른 일이 일어나고 있습니다. 더 많은 정보가 필요합니다.
Solomon Rutzky 2012 년

@ srutzky 데이터 유형 우선 순위 문제를 피하기 위해 캐스트를 추가했습니다. 필요하지 않은 것이 맞을 수도 있습니다. 다른 질문에 대해서는 UNICODE () 및 SUBSTRING ()을 제안했습니다. 그 접근 방식이 효과가 있습니까?
Joe Obbish 2012 년

@JohnGHohengarten 및 Joe : 데이터 형식 우선 순위 VARCHAR는 암시 적으로로 변환 되는 문제가 아니어야 NVARCHAR하지만 그렇게하는 것이 좋습니다 CONVERT(NVARCHAR(80), CONVERT(VARCHAR(80), column)) <> column. SUBSTRING때로는 작동하지만로 끝나지 않는 콜 레이션을 사용할 때 보조 문자와 함께 작동하지 않으며 _SCJohn이 사용하고있는 콜 레이션은 여기서 문제가되지는 않습니다. 그러나 VARBINARY로 변환하면 항상 작동합니다. 그리고 CONVERT(VARCHAR(10), CONVERT(NVARCHAR(10), '›'))결과가 없으므로 ?바이트를보고 싶습니다. ETL 프로세스가 변환했을 수 있습니다.
Solomon Rutzky

5

무엇을하기 전에 @RDFozz가 제기 한 질문을 질문에 대한 의견에서 고려하십시오.

  1. 있습니까 어떤 외에 다른 소스 [Q].[G]이 테이블을 채우기는?

    응답이 " 이 대상 테이블 의 유일한 데이터 원본 임을 100 % 확신합니다"이외의 항목 인 경우 , 현재 테이블에있는 데이터를 변환 할 수 있는지 여부에 관계없이 변경하지 마십시오. 데이터 손실.

  2. 이 있습니까 어떤 가까운 장래에이 데이터를 채우기 위해 추가 소스를 추가 관련 계획 / 토론?

    그리고 관련 질문을 추가합니다 :이나요되어 현재의 소스 테이블 (즉, 여러 언어를 지원 주위에 어떤 논의 [Q].[G]변환) NVARCHAR?

    이러한 가능성에 대한 이해를 얻으려면 주변에 문의해야합니다. 나는 현재 당신이이 방향을 가리키는 어떤 말도하지 않았다고 가정합니다. 그렇지 않으면 당신은이 질문을하지 않을 것입니다. 가장 정확하고 완전한 답변을 얻을 수있는 충분한 청중.

여기서 주요 문제는 변환 할 수없는 유니 코드 코드 포인트가 아니라 단일 코드 페이지에 모두 맞지 않는 코드 포인트가 있다는 것입니다. 이것이 유니 코드의 좋은 점입니다. 모든 코드 페이지의 문자를 보유 할 수 있습니다. 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;

반환 (두 번째 결과 집합) :

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을 사용하는 경우 공간을 절약하기 위해이 변환 작업을 모두 잊고 데이터 압축을 활성화하십시오.

유니 코드 압축 구현

Standard Edition을 사용하는 경우 (그리고 현재는 😞), 또 다른 looooong-shot 가능성이 있습니다. SP1부터 모든 Edition이 데이터 압축을 사용할 수있는 기능이 포함되어 있기 때문에 SQL Server 2016으로 업그레이드 할 수 있습니다. "😉).

물론 데이터에 대한 소스가 하나뿐이라는 사실이 밝혀 졌으므로 소스에 유니 코드 전용 문자 또는 특정 코드 외부의 문자를 포함 할 수 없으므로 걱정할 필요가 없습니다. 페이지. 이 경우주의해야 할 유일한 것은 소스 열과 동일한 데이터 정렬 또는 동일한 코드 페이지를 사용하는 데이터 정렬을 사용하는 것입니다. 소스 열 에서를 SQL_Latin1_General_CP1_CI_AS사용 Latin1_General_100_CI_AS하는 경우 대상에서 사용할 수 있습니다 .

사용할 데이터 정렬을 알고 나면 다음 중 하나를 수행 할 수 있습니다.

  • ALTER TABLE ... ALTER COLUMN ...VARCHAR(현재 지정해야합니다 NULL/ NOT NULL약간의 시간과 8,700 만 행에 대한 트랜잭션 로그 공간을 많이 필요로 설정), OR

  • 각각의 새로운 "ColumnName_tmp"열을 만들고 천천히 통해 채우는 UPDATETOP (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입니다.
John G Hohengarten

@JohnGHohengarten 바닥에 다시 업데이트되었습니다. 사용하기 쉬운 것 Latin1_General_100_CI_AS보다 훨씬 나아 도 동일한 데이터 정렬을 쉽게 유지할 수 있습니다 . 정렬 및 비교 동작이 방금 언급 한 최신 데이터 정렬만큼 좋지는 않더라도 정렬 및 비교 동작이 동일하다는 것을 쉽게 의미합니다.
Solomon Rutzky

4

나는 진짜 직업이 있었을 때부터 이것에 대해 약간의 경험이 있습니다. 당시에는 기본 데이터를 보존하고 셔플에서 문자가 손실 될 수있는 새 데이터도 고려해야했기 때문에 비 지속적 계산 열을 사용했습니다.

다음은 SO 데이터 덤프 의 수퍼 유저 데이터베이스 복사본을 사용하는 간단한 예 입니다.

우리는 유니 코드 문자가있는 DisplayNames가 있음을 바로 알 수 있습니다.

견과류

계산 된 열을 추가하여 몇 개인 지 알아 봅시다! 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

필드에 유니 코드 데이터가 포함되어 있는지 확인하는 방법 의 예를 사용하여 각 열의 데이터를 읽고 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.