Mendeteksi jika ada nilai dalam kolom NVARCHAR sebenarnya unicode

14

Saya telah mewarisi beberapa database SQL Server. Ada satu tabel (saya akan memanggil "G"), dengan sekitar 86,7 juta baris, dan lebar 41 kolom, dari basis data sumber (saya akan memanggil "Q") pada SQL Server 2014 Standard yang membuat ETL beralih ke database target (saya akan memanggil "P") dengan nama tabel yang sama pada SQL Server 2008 R2 Standard.

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

EDIT: 3/20/2017: Beberapa orang bertanya apakah tabel sumber adalah sumber HANYA ke tabel target. Ya, itu satu-satunya sumber. Sejauh ETL berjalan, tidak ada transformasi nyata yang terjadi; itu secara efektif dimaksudkan untuk menjadi salinan data sumber 1: 1. Karenanya, tidak ada rencana untuk menambahkan sumber tambahan ke tabel target ini.

Sedikit lebih dari setengah kolom di [Q]. [G] adalah VARCHAR (tabel sumber):

  • 13 kolom adalah VARCHAR (80)
  • 9 kolom adalah VARCHAR (30)
  • 2 kolom adalah VARCHAR (8).

Demikian pula, kolom yang sama dalam [P]. [G] adalah NVARCHAR (tabel target), dengan # kolom yang sama dengan lebar yang sama. (Dengan kata lain, panjangnya sama, tetapi NVARCHAR).

  • 13 kolom adalah NVARCHAR (80)
  • 9 kolom adalah NVARCHAR (30)
  • 2 kolomnya adalah NVARCHAR (8).

Ini bukan desain saya.

Saya ingin ALTER [P]. [G] (target) kolom tipe data dari NVARCHAR ke VARCHAR. Saya ingin melakukannya dengan aman (tanpa kehilangan data dari konversi).

Bagaimana saya bisa melihat nilai data di setiap kolom NVARCHAR di tabel target untuk mengonfirmasi apakah kolom tersebut benar-benar berisi data Unicode atau tidak?

Kueri (DMV?) Yang dapat memeriksa setiap nilai (dalam satu lingkaran?) Dari setiap kolom NVARCHAR dan memberi tahu saya jika ada nilai yang asli Unicode akan menjadi solusi ideal, tetapi metode lain dipersilakan.

John G Hohengarten
sumber
2
Pertama, pertimbangkan proses Anda, dan bagaimana data digunakan. Data dalam [G]ETLed ke [P]. Jika [G]adalah varchar, dan proses ETL adalah satu-satunya cara data yang datang ke dalam [P], maka kecuali proses menambahkan karakter Unicode yang benar, ada tidak boleh ada. Jika proses lain menambah atau memodifikasi data [P], Anda harus lebih berhati-hati - hanya karena semua data saat varcharini tidak berarti bahwa nvarchardata tidak dapat ditambahkan besok. Demikian pula, ada kemungkinan bahwa apa pun yang mengkonsumsi data dalam data [P]kebutuhan nvarchar.
RDFozz

Jawaban:

10

Misalkan salah satu kolom Anda tidak mengandung data unicode. Untuk memverifikasi bahwa Anda perlu membaca nilai kolom untuk setiap baris. Kecuali jika Anda memiliki indeks pada kolom, dengan tabel rowstore Anda harus membaca setiap halaman data dari tabel. Dengan mengingat hal itu, saya pikir sangat masuk akal untuk menggabungkan semua pemeriksaan kolom ke dalam satu kueri terhadap tabel. Dengan begitu Anda tidak akan membaca data tabel berkali-kali dan Anda tidak perlu kode kursor atau semacam loop lainnya.

Untuk memeriksa satu kolom yakin bahwa Anda bisa melakukan ini:

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

Para pemain dari NVARCHARke VARCHARharus memberi Anda hasil yang sama kecuali jika ada karakter unicode. Karakter Unicode akan dikonversi menjadi ?. Jadi kode di atas harus menangani NULLkasus dengan benar. Anda memiliki 24 kolom untuk diperiksa, jadi Anda memeriksa setiap kolom dalam satu permintaan dengan menggunakan agregat skalar. Satu implementasi di bawah ini:

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

Untuk setiap kolom Anda akan mendapatkan hasil 1jika salah satu nilainya berisi unicode. Hasil dari 0berarti bahwa semua data dapat dikonversi dengan aman.

Saya sangat menyarankan untuk membuat salinan tabel dengan definisi kolom baru dan menyalin data Anda di sana. Anda akan melakukan konversi yang mahal jika Anda melakukannya sehingga membuat salinan mungkin tidak terlalu lambat. Memiliki salinan berarti Anda dapat dengan mudah memvalidasi bahwa semua data masih ada (salah satu caranya adalah dengan menggunakan KECUALI kata kunci) dan Anda dapat membatalkan operasi dengan sangat mudah.

Perlu diketahui juga bahwa Anda mungkin tidak memiliki data unicode saat ini, mungkin saja ETL di masa depan dapat memuat unicode ke dalam kolom yang sebelumnya bersih. Jika tidak ada pemeriksaan untuk ini dalam proses ETL Anda, Anda harus mempertimbangkan untuk menambahkannya sebelum melakukan konversi ini.

Joe Obbish
sumber
Sementara jawaban dan diskusi dari @srutzky cukup bagus dan memiliki info yang membantu, Joe memberi saya apa yang diminta pertanyaan saya: permintaan untuk memberi tahu saya jika ada nilai dalam kolom yang benar-benar memiliki Unicode. Karena itu saya menandai jawaban Joe sebagai jawaban yang diterima. Saya memilih jawaban lain yang juga membantu saya.
John G Hohengarten
@ JohnGHohengarten dan Joe: Tidak apa-apa. Saya tidak menyebutkan pertanyaan karena ada dalam jawaban ini dan juga Scott. Saya hanya akan mengatakan bahwa tidak perlu mengkonversi NVARCHARkolom NVARCHARkarena sudah tipe itu. Dan tidak yakin bagaimana Anda menentukan karakter yang tidak dapat dikonversi, tetapi Anda dapat mengonversi kolom VARBINARYuntuk mendapatkan urutan UTF-16 byte. Dan UTF-16 adalah urutan byte terbalik, jadi p= 0x7000dan kemudian Anda membalikkan dua byte tersebut untuk mendapatkan Code Point U+0070. Tetapi, jika sumbernya adalah VARCHAR, maka itu bukan karakter Unicode. Sesuatu yang lain sedang terjadi. Perlu info lebih lanjut.
Solomon Rutzky
@srutzky Saya menambahkan para pemain untuk menghindari masalah prioritas tipe data. Anda mungkin benar bahwa itu tidak diperlukan. Untuk pertanyaan lain, saya menyarankan UNICODE () dan SUBSTRING (). Apakah pendekatan itu berhasil?
Joe Obbish
@ JohnGHohengarten dan Joe: diutamakan tipe data seharusnya tidak menjadi masalah karena VARCHARsecara implisit akan dikonversi ke NVARCHAR, tetapi mungkin lebih baik untuk dilakukan CONVERT(NVARCHAR(80), CONVERT(VARCHAR(80), column)) <> column. SUBSTRINGkadang-kadang berfungsi, tetapi tidak berfungsi dengan Karakter Tambahan saat menggunakan Collations yang tidak berakhir _SC, dan yang digunakan John tidak, meskipun kemungkinan tidak menjadi masalah di sini. Tetapi mengonversi ke VARBINARY selalu berhasil. Dan CONVERT(VARCHAR(10), CONVERT(NVARCHAR(10), '›'))tidak menghasilkan ?, jadi saya ingin melihat byte. Proses ETL mungkin telah mengubahnya.
Solomon Rutzky
5

Sebelum melakukan sesuatu, harap pertimbangkan pertanyaan yang diajukan oleh @RDFozz dalam komentar pada pertanyaan, yaitu:

  1. Apakah ada setiap sumber lain selain [Q].[G]mengisi tabel ini?

    Jika responsnya adalah sesuatu di luar "Saya 100% yakin bahwa ini adalah satu - satunya sumber data untuk tabel tujuan ini", maka jangan membuat perubahan apa pun, terlepas dari apakah data saat ini dalam tabel dapat dikonversi tanpa Data hilang.

  2. Apakah ada setiap rencana / diskusi terkait dengan menambahkan sumber tambahan untuk mengisi data ini dalam waktu dekat?

    Dan saya akan menambahkan pertanyaan terkait: Sudah ada diskusi sekitar yang mendukung beberapa bahasa dalam tabel sumber arus (yaitu [Q].[G]) dengan mengkonversi itu untuk NVARCHAR?

    Anda perlu bertanya-tanya untuk mengetahui kemungkinan-kemungkinan ini. Saya berasumsi bahwa saat ini Anda belum diberi tahu apa pun yang akan mengarah ke arah ini, Anda tidak akan mengajukan pertanyaan ini, tetapi jika pertanyaan-pertanyaan ini dianggap "tidak", maka mereka perlu ditanyakan, dan ditanya tentang audiens yang cukup luas untuk mendapatkan jawaban yang paling akurat / lengkap.

Masalah utama di sini adalah tidak begitu banyak memiliki poin kode Unicode yang tidak dapat dikonversi (pernah), tetapi lebih dari itu memiliki poin kode yang tidak akan cocok semua ke halaman kode tunggal. Itu hal yang menyenangkan tentang Unicode: itu dapat menampung karakter dari SEMUA halaman kode. Jika Anda mengonversi dari NVARCHAR- tempat Anda tidak perlu khawatir tentang halaman kode - ke VARCHAR, maka Anda perlu memastikan bahwa Kolasi kolom tujuan menggunakan halaman kode yang sama dengan kolom sumber. Ini mengasumsikan memiliki satu sumber, atau beberapa sumber menggunakan halaman kode yang sama (meskipun tidak harus sama dengan Collation). Tetapi jika ada banyak sumber dengan banyak halaman kode, maka Anda berpotensi mengalami masalah berikut:

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;

Pengembalian (set hasil 2):

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

Seperti yang Anda lihat, semua karakter itu dapat dikonversi menjadi VARCHAR, hanya saja tidak di VARCHARkolom yang sama .

Gunakan kueri berikut untuk menentukan apa halaman kode untuk setiap kolom tabel sumber Anda:

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

YANG TELAH DIBILANG....

Anda menyebutkan berada di SQL Server 2008 R2, TAPI, Anda tidak mengatakan Edisi apa. JIKA Anda berada di Edisi Perusahaan, lupakan semua hal konversi ini (karena Anda kemungkinan melakukannya hanya untuk menghemat ruang), dan aktifkan Kompresi Data:

Implementasi Kompresi Unicode

Jika menggunakan Edisi Standar (dan sekarang sepertinya Anda adalah 😞) maka ada kemungkinan lain yang lebih mudah: perbarui ke SQL Server 2016 karena SP1 mencakup kemampuan untuk semua Edisi untuk menggunakan Kompresi Data (ingat, saya memang mengatakan "tembakan panjang" "😉).

Tentu saja, sekarang baru saja diklarifikasi bahwa hanya ada satu sumber untuk data, maka Anda tidak perlu khawatir karena sumber tidak dapat berisi karakter hanya Unicode, atau karakter di luar kode spesifiknya halaman. Dalam hal ini, satu-satunya hal yang harus Anda perhatikan adalah menggunakan Collation yang sama dengan kolom sumber, atau setidaknya satu yang menggunakan Halaman Kode yang sama. Artinya, jika kolom sumber menggunakan SQL_Latin1_General_CP1_CI_AS, maka Anda bisa menggunakan Latin1_General_100_CI_ASdi tempat tujuan.

Setelah mengetahui Collation apa yang akan digunakan, Anda dapat:

  • ALTER TABLE ... ALTER COLUMN ...menjadi VARCHAR(pastikan untuk menentukan pengaturan NULL/ saat ini NOT NULL), yang membutuhkan sedikit waktu dan banyak ruang log transaksi untuk 87 juta baris, ATAU

  • Buat kolom "ColumnName_tmp" baru untuk masing-masing kolom dan perlahan-lahan isi dengan UPDATEmelakukan TOP (1000) ... WHERE new_column IS NULL. Setelah semua baris diisi (dan divalidasi bahwa mereka semua disalin dengan benar! Anda mungkin perlu pemicu untuk menangani UPDATE, jika ada), dalam transaksi eksplisit, gunakan sp_renameuntuk menukar nama kolom dari kolom "saat ini" menjadi " _Old "dan kemudian kolom" _tmp "baru untuk menghapus" _tmp "dari nama. Kemudian panggil sp_reconfiguremeja untuk membatalkan rencana cache yang mereferensikan tabel, dan jika ada Tampilan yang mereferensikan tabel Anda perlu menelepon sp_refreshview(atau sesuatu seperti itu). Setelah Anda memvalidasi aplikasi dan ETL bekerja dengan benar, maka Anda dapat menjatuhkan kolom.

Solomon Rutzky
sumber
Saya menjalankan kueri CodePage yang Anda berikan pada sumber dan target, dan CodePage adalah 1252 dan collation_name adalah SQL_Latin1_General_CP1_CI_AS pada KEDUA sumber DAN target.
John G Hohengarten
@ JohnGHohengarten Saya baru saja memperbarui lagi, di bagian bawah. Agar mudah, Anda dapat menyimpan Collation yang sama, meskipun Latin1_General_100_CI_ASjauh lebih baik daripada yang Anda gunakan. Makna mudah bahwa perilaku penyortiran dan perbandingan akan sama di antara mereka, bahkan jika tidak sebagus Kolasi yang baru saya sebutkan.
Solomon Rutzky
4

Saya memiliki beberapa pengalaman dengan ini dari belakang ketika saya memiliki pekerjaan nyata. Karena pada saat itu saya ingin menyimpan data base, dan saya juga harus memperhitungkan data baru yang mungkin memiliki karakter yang akan hilang dalam shuffle, saya pergi dengan kolom komputasi yang tidak bertahan lama.

Berikut adalah contoh cepat menggunakan salinan database Super User dari dump data SO .

Kita dapat langsung melihat bahwa ada DisplayNames dengan karakter Unicode:

Gila

Jadi mari kita tambahkan kolom yang dihitung untuk mencari tahu berapa banyak! Kolom DisplayName adalah 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

Hitungannya kembali ~ 3000 baris

Gila

Namun, rencana pelaksanaannya sedikit membosankan. Kueri selesai dengan cepat, tetapi kumpulan data ini tidak terlalu besar.

Gila

Karena kolom yang dikomputasi tidak perlu dipertahankan untuk menambahkan indeks, kita dapat melakukan salah satu dari ini:

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

Yang memberi kami rencana yang sedikit lebih rapi:

Gila

Saya mengerti jika hal ini tidak dengan jawaban, karena melibatkan perubahan arsitektur, tapi mengingat ukuran data, Anda mungkin melihat menambahkan indeks untuk mengatasi permintaan yang diri bergabung meja pula.

Semoga ini membantu!

Erik Darling
sumber
1

Menggunakan contoh di Cara memeriksa apakah bidang berisi data unicode , Anda bisa membaca data di setiap kolom dan melakukan CASTdan periksa di bawah:

--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
Scott Hodgin
sumber