ตรวจสอบว่าค่าใด ๆ ในคอลัมน์ NVARCHAR เป็น Unicode จริงหรือไม่


14

ฉันได้รับฐานข้อมูล SQL Server บางตัว มีตารางหนึ่งตาราง (ฉันจะเรียกว่า "G") มีประมาณ 86.7 ล้านแถวและกว้าง 41 คอลัมน์จากฐานข้อมูลต้นทาง (ฉันจะเรียกว่า "Q") บนมาตรฐาน SQL Server 2014 ที่ได้รับ ETL ฐานข้อมูลเป้าหมาย (ฉันจะเรียกว่า "P") ด้วยชื่อตารางเดียวกันใน SQL Server 2008 R2 Standard

เช่น [Q]. [G] ---> [P]. [G]

แก้ไข: 3/20/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

สมมติว่าหนึ่งในคอลัมน์ของคุณไม่มีข้อมูล Unicode ใด ๆ เพื่อตรวจสอบว่าคุณจะต้องอ่านค่าคอลัมน์สำหรับทุกแถว ถ้าคุณไม่มีดัชนีในคอลัมน์ด้วยตาราง rowstore คุณจะต้องอ่านทุกหน้าข้อมูลจากตาราง โดยที่ในใจฉันคิดว่ามันสมเหตุสมผลมากที่จะรวมการตรวจสอบคอลัมน์ทั้งหมดลงในแบบสอบถามแบบเดี่ยวกับตาราง ด้วยวิธีนี้คุณจะไม่สามารถอ่านข้อมูลของตารางได้หลายครั้งและคุณไม่จำเป็นต้องเขียนโค้ดเคอร์เซอร์หรือวนลูปแบบอื่น

หากต้องการตรวจสอบคอลัมน์เดียวให้เชื่อว่าคุณทำได้ดังนี้:

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ว่ามีค่าใดที่มี unicode ผลที่ได้0หมายความว่าข้อมูลทั้งหมดสามารถแปลงได้อย่างปลอดภัย

ฉันขอแนะนำให้ทำสำเนาของตารางด้วยคำจำกัดความคอลัมน์ใหม่และคัดลอกข้อมูลของคุณที่นั่น คุณจะได้รับการแปลงที่มีราคาแพงถ้าคุณทำมันดังนั้นการทำสำเนาอาจจะไม่ช้าไปกว่านั้นทั้งหมด มีสำเนาหมายความว่าคุณสามารถตรวจสอบได้ง่ายว่าข้อมูลทั้งหมดยังคงมีอยู่ (วิธีหนึ่งคือการใช้คำหลักยกเว้น ) และคุณสามารถยกเลิกการดำเนินการได้อย่างง่ายดายมาก

นอกจากนี้โปรดทราบว่าคุณอาจไม่มีข้อมูล unicode ใด ๆ ในปัจจุบันเป็นไปได้ว่า ETL ในอนาคตสามารถโหลด unicode ลงในคอลัมน์ที่สะอาดก่อนหน้านี้ หากไม่มีการตรวจสอบสิ่งนี้ในกระบวนการ ETL ของคุณคุณควรพิจารณาเพิ่มสิ่งนั้นก่อนที่จะทำการแปลง


ในขณะที่คำตอบและการสนทนาจาก @srutzky ค่อนข้างดีและมีข้อมูลที่เป็นประโยชน์โจให้สิ่งที่คำถามของฉันถาม: แบบสอบถามเพื่อบอกฉันว่าค่าใด ๆ ในคอลัมน์มี Unicode จริงหรือไม่ ดังนั้นฉันจึงทำเครื่องหมายคำตอบของโจว่าเป็นคำตอบที่ยอมรับได้ ฉันโหวตคำตอบอื่น ๆ ที่ช่วยฉันด้วยเช่นกัน
John G Hohengarten

@JohnGHohengarten และ Joe: ไม่เป็นไร ฉันไม่ได้พูดถึงคำถามเนื่องจากเป็นคำตอบนี้รวมถึงของ Scott ฉันแค่จะบอกว่าไม่จำเป็นต้องแปลงNVARCHARคอลัมน์NVARCHARเป็นประเภทที่มีอยู่แล้ว และไม่แน่ใจว่าคุณกำหนดอักขระที่ไม่สามารถแปลงได้ แต่คุณสามารถแปลงคอลัมน์เป็นVARBINARYลำดับไบต์ UTF-16 และ UTF-16 เป็นลำดับไบต์ย้อนกลับดังนั้นp=0x7000U+0070และจากนั้นคุณย้อนกลับทั้งสองไบต์ที่จะได้รับรหัสจุด แต่ถ้าแหล่งที่มาคือ VARCHAR แสดงว่าไม่สามารถเป็นอักขระ Unicode ได้ อย่างอื่นเกิดขึ้น ต้องการข้อมูลเพิ่มเติม
โซโลมอน Rutzky

@srutzky ฉันได้เพิ่มตัวบล็อกเพื่อหลีกเลี่ยงปัญหาที่มาก่อนของชนิดข้อมูล คุณอาจถูกต้องว่าไม่จำเป็น สำหรับคำถามอื่น ๆ ฉันแนะนำ UNICODE () และ SUBSTRING () วิธีการนี้ใช้งานได้หรือไม่
โจ Obbish

@JohnGHohengarten และโจ: ชนิดข้อมูลมีความสำคัญไม่ควรจะมีปัญหาเป็นVARCHARโดยปริยายจะแปลงแต่มันอาจจะดีกว่าที่จะทำNVARCHARCONVERT(NVARCHAR(80), CONVERT(VARCHAR(80), column)) <> columnSUBSTRINGบางครั้งใช้งานได้ แต่ไม่สามารถใช้งานได้กับอักขระเสริมเมื่อใช้ Collations ที่ไม่สิ้นสุด_SCและตัวที่ John ใช้ไม่ได้แม้ว่าจะไม่มีปัญหาที่นี่ แต่การแปลงเป็น VARBINARY ใช้ได้เสมอ และCONVERT(VARCHAR(10), CONVERT(NVARCHAR(10), '›'))ไม่ส่งผล?ดังนั้นฉันต้องการเห็นไบต์ กระบวนการ ETL อาจมีการแปลง
โซโลมอน Rutzky

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;

ผลตอบแทน (ชุดผลลัพธ์ที่ 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 แต่คุณไม่ได้พูดว่า Edition ใด หากคุณเกิดขึ้นกับ Enterprise Edition ให้ลืมสิ่งที่แปลงทั้งหมดนี้ (เนื่องจากคุณน่าจะทำเพื่อประหยัดพื้นที่) และเปิดใช้งานการบีบอัดข้อมูล:

การใช้การบีบอัด Unicode

ถ้าใช้ Standard Edition (และตอนนี้ดูเหมือนว่าคุณเป็น😞) แล้วมีความเป็นไปได้อีกอย่างคือ looooong-shot: อัปเกรดเป็น SQL Server 2016 เนื่องจาก SP1 มีความสามารถสำหรับทุกรุ่นในการใช้การบีบอัดข้อมูล (โปรดจำไว้ว่า "😉)

แน่นอนตอนนี้เพิ่งได้รับการชี้แจงว่ามีแหล่งข้อมูลเพียงแหล่งเดียวจากนั้นคุณไม่มีอะไรต้องกังวลเนื่องจากแหล่งข้อมูลไม่สามารถมีอักขระ Unicode-only หรืออักขระนอกรหัสที่เฉพาะเจาะจง หน้า. ในกรณีนี้สิ่งเดียวที่คุณควรระวังคือการใช้ 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 บนทั้งต้นทางและปลายทาง
John G Hohengarten

@JohnGHohengarten ฉันเพิ่งอัปเดตอีกครั้งที่ด้านล่าง เพื่อให้ง่ายคุณสามารถใช้ Collation เดียวกันได้แม้ว่าLatin1_General_100_CI_ASจะดีกว่าCollation ที่คุณใช้อยู่มาก ความหมายง่าย ๆ ว่าพฤติกรรมการเรียงลำดับและการเปรียบเทียบจะเหมือนกันระหว่างกันแม้ว่าจะไม่ดีเท่า Collation ใหม่ที่ฉันเพิ่งพูดถึง
โซโลมอน Rutzky

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.