NVARCHAR sütunlarındaki herhangi bir değerin gerçekten unicode olup olmadığını tespit edin


14

Bazı SQL Server veritabanlarını devralmıştım. SQL Server 2014 Standard üzerinde ETL'leri alan bir kaynak veritabanından ("Q" diyeceğim) yaklaşık 86.7 milyon satır ve 41 sütun genişliğinde bir tablo ("G" diyeceğim) var. SQL Server 2008 R2 Standard'da aynı tablo adına sahip bir hedef veritabanı ("P" olarak adlandıracağım).

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

EDIT: 3/20/2017: Bazı insanlar kaynak tabloyu hedef tablo için SADECE kaynak olup olmadığını sordu. Evet, tek kaynak bu. ETL'ye gelince, gerçek bir dönüşüm gerçekleşmiyor; etkin bir şekilde kaynak verilerin 1: 1 kopyası olması amaçlanmıştır. Bu nedenle, bu hedef tabloya ek kaynak ekleme planı yoktur.

[Q] içindeki sütunların yarısından biraz fazlası. [G] VARCHAR (kaynak tablosu):

  • Sütunların 13'ü VARCHAR (80)
  • Sütunların 9'u VARCHAR'dır (30)
  • Sütunların 2'si VARCHAR'dır (8).

Benzer şekilde, [P]. [G] 'deki aynı sütunlar, aynı genişliklere sahip aynı sütun sayısına sahip NVARCHAR'dır (hedef tablo). (Başka bir deyişle, aynı uzunluk, ancak NVARCHAR).

  • Sütunların 13'ü NVARCHAR'dır (80)
  • Sütunların 9'u NVARCHAR'dır (30)
  • Sütunlardan 2'si NVARCHAR'dır (8).

Bu benim tasarımım değil.

ALTER [P]. [G] (hedef) sütun veri türlerini NVARCHAR'dan VARCHAR'a değiştirmek istiyorum. Güvenli bir şekilde yapmak istiyorum (dönüşümden veri kaybı olmadan).

Sütunun gerçekte herhangi bir Unicode veri içerip içermediğini doğrulamak için hedef tablodaki her NVARCHAR sütunundaki veri değerlerine nasıl bakabilirim?

Her NVARCHAR sütununun her değerini (döngüde?) Kontrol edebilen ve değerlerden HERHANGİ BİRİNİN gerçek olup olmadığını söyleyebilen bir sorgu (DMV'ler?) İdeal çözüm olacaktır, ancak diğer yöntemler açıktır.


2
İlk olarak, işleminizi ve verilerin nasıl kullanıldığını düşünün. İçindeki veriler [G], adresine aktarılır [P]. Eğer [G]bir varcharve ETL işlem veri girer tek yolu [P], süreç gerçek Unicode karakterleri ekler sonra sürece, herhangi olmamalıdır. Diğer işlemler veri ekler veya değiştirirse [P], daha dikkatli olmanız gerekir - sadece mevcut verilerin olabilmesi varchar, nvarcharyarın verilerin eklenemeyeceği anlamına gelmez . Benzer şekilde, [P]ihtiyaç nvarcharverilerinde veri tüketen her şeyin veri olması da mümkündür .
RDFozz

Yanıtlar:


10

Sütunlarınızdan birinin unicode veri içermediğini varsayalım. Her satır için sütun değerini okumanız gerektiğini doğrulamak için. Sütunda bir dizin yoksa, bir satır mağaza tablosu ile tablodaki her veri sayfasını okumanız gerekir. Bunu akılda tutarak, tüm sütun kontrollerini tabloya karşı tek bir sorguda birleştirmek çok mantıklı olduğunu düşünüyorum. Bu şekilde tablonun verilerini defalarca okumazsınız ve bir imleci veya başka bir döngüyü kodlamanız gerekmez.

Tek bir sütunu kontrol etmek için bunu yapabileceğinizi düşünün:

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

A dökme NVARCHARiçin VARCHARsize unicode karakterler varsa haricinde aynı sonucu vermelidir. Unicode karakterler dönüştürülecek ?. Bu nedenle yukarıdaki kod, NULLvakaları doğru şekilde işlemelidir. Kontrol etmek için 24 sütununuz var, bu yüzden her sütunu tek bir sorguda skaler toplamalar kullanarak kontrol ediyorsunuz. Bir uygulama aşağıdadır:

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

Her sütun için 1, değerlerinden herhangi birinin unicode içerip içermediğinin bir sonucunu alırsınız . Sonuç, 0tüm verilerin güvenli bir şekilde dönüştürülebileceği anlamına gelir.

Tablonun yeni sütun tanımlarıyla bir kopyasını almanızı ve verilerinizi buraya kopyalamanızı önemle tavsiye ederim. Yerinde yaparsanız pahalı dönüşümler yapacaksınız, böylece bir kopya yapmak çok daha yavaş olmayabilir. Bir kopyaya sahip olmak, tüm verilerin hala orada olduğunu kolayca doğrulayabileceğiniz anlamına gelir (bir yol EXCEPT anahtar sözcüğünü kullanmaktır ) ve işlemi çok kolay bir şekilde geri alabilirsiniz.

Ayrıca, şu anda herhangi bir unicode verileriniz olmayabilir, gelecekteki bir ETL'nin daha önce temiz bir sütuna unicode yükleyebileceğini unutmayın. ETL işleminizde bunun için bir kontrol yoksa, bu dönüşümü yapmadan önce bunu eklemeyi düşünmelisiniz.


@Srutzky'nin yanıtı ve tartışması oldukça iyi ve yararlı bilgiye sahipken, Joe bana sorumun ne istediğini sundu: sütunlardaki herhangi bir değerin gerçekten Unicode olup olmadığını gösteren bir sorgu. Bu nedenle Joe'nun cevabını kabul edilen cevap olarak işaretledim. Bana yardımcı olan diğer cevapları da oyladım.
John G Hohengarten

@ JohnGHohengarten ve Joe: Sorun değil. Bu yanıta Scott yanı sıra olduğu için sorgu bahsetmedim. Ben sadece zaten bu tür olduğu gibi NVARCHARsütun dönüştürmek gerek olmadığını söyleyebilirim NVARCHAR. Ve dönüştürülemeyen karakteri nasıl belirlediğinizden emin değilsiniz, ancak VARBINARYUTF-16 bayt dizilerini almak için sütunu dönüştürebilirsiniz . Ve UTF-16 ters bayt sırasıdır, yani p= 0x7000ve sonra Kod Noktası almak için bu iki baytı ters çevirirsiniz U+0070. Ancak, kaynak VARCHAR ise, bir Unicode karakteri olamaz. Başka bir şey oluyor. Daha fazla bilgiye ihtiyacınız var.
Solomon Rutzky

@srutzky Veri türü öncelik sorunlarından kaçınmak için oyuncu kadrosunu ekledim. Gerekmediği konusunda doğru olabilirsiniz. Diğer soru için UNICODE () ve SUBSTRING () 'i önerdim. Bu yaklaşım işe yarıyor mu?
Joe Obbish

@JohnGHohengarten ve Joe: veri türü önceliği VARCHARörtük olarak dönüştürülecek bir sorun olmamalı NVARCHAR, ancak bunu yapmak daha iyi olabilir CONVERT(NVARCHAR(80), CONVERT(VARCHAR(80), column)) <> column. SUBSTRINGbazen işe yaramaz, ancak bitmeyen Harmanlamaları kullanırken Tamamlayıcı Karakterlerle çalışmaz _SCve John'un kullandığı karakter, muhtemelen burada bir sorun olmamasına rağmen değildir. Ancak VARBINARY'a dönüştürmek her zaman işe yarar. Ve CONVERT(VARCHAR(10), CONVERT(NVARCHAR(10), '›'))sonuç vermez ?, bu yüzden baytları görmek isterim. ETL süreci dönüştürmüş olabilir.
Solomon Rutzky

5

Bir şey yapmadan önce, lütfen @RDFozz tarafından sorulan bir soruya, yani:

  1. Var mıdır herhangi yanında başka kaynaklar [Q].[G]bu tabloyu doldurma?

    Yanıt " Bu hedef tablo için tek veri kaynağı olduğundan% 100 eminim" dışında bir şeyse, o anda tablodaki verilerin olmadan dönüştürülüp dönüştürülemeyeceğine bakılmaksızın herhangi bir değişiklik yapmayın veri kaybı.

  2. Bu verileri yakın gelecekte doldurmak için ek kaynaklar eklemeyle ilgili herhangi bir plan / tartışma var mı?

    Ve İlgili bir soru eklemek istiyorum: orada Has been akım kaynağı tablosundaki (yani birden çok dili destekleyen etrafında herhangi bir tartışmanın [Q].[G]dönüştürerek) bunu hiç NVARCHAR?

    Bu olasılıkları anlamak için etrafta sormanız gerekecek. Sanırım şu anda bu yönde başka bir şeye işaret etmeyeceğiniz söylenmemiştir, ancak bu soruyu sormazsınız, ancak bu soruların "hayır" olduğu varsayılırsa, o zaman sorulmaları ve bir soru sormaları gerekir. en doğru / eksiksiz cevabı almak için yeterince geniş kitle.

Buradaki ana sorun, dönüştürülemeyen (hiç) Unicode kod noktalarına sahip olmak değil , daha çok hepsi tek bir kod sayfasına sığmayacak kod noktalarına sahip olmaktır. Unicode hakkında güzel bir şey var: TÜM kod sayfalarındaki karakterleri tutabilir. NVARCHAR- kod sayfaları hakkında endişelenmenize gerek olmayan bir yerden - dönüştürürseniz VARCHAR, hedef sütunun Harmanlaması'nın kaynak sütununla aynı kod sayfasını kullandığından emin olmanız gerekir. Bu, aynı kod sayfasını kullanan bir kaynağa veya birden çok kaynağa sahip olduğunu varsayar (yine de aynı Harmanlama değil). Ancak, birden çok kod sayfası olan birden fazla kaynak varsa, aşağıdaki sorunla karşılaşabilirsiniz:

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;

İade (2. sonuç kümesi):

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

Gördüğünüz gibi, tüm bu karakterler aynı sütunda değil , dönüştürülebilir .VARCHARVARCHAR

Kaynak tablonuzun her sütunu için kod sayfasının ne olduğunu belirlemek için aşağıdaki sorguyu kullanı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';

SÖYLENİYOR Kİ....

SQL Server 2008 R2'de olmaktan bahsettiniz, ANCAK, hangi Sürümü söylemediniz. Enterprise Edition'daysanız, tüm bu dönüşüm şeylerini unutun (çünkü sadece yer kazanmak için yapıyorsunuz) ve Veri Sıkıştırmayı etkinleştirin:

Unicode Sıkıştırma Uygulaması

Standard Edition kullanıyorsanız (ve şimdi 😞 gibi görünüyor) o zaman başka bir looooong-shot olasılığı var: SP1, tüm sürümlerin Veri Sıkıştırma kullanma yeteneğini içerdiğinden (hatırlayın, "uzun çekim" dedim) "😉).

Tabii ki, şimdi veriler için sadece bir kaynak olduğu açıklığa kavuşturuldu, o zaman endişelenecek bir şeyiniz yok, çünkü kaynak sadece Unicode karakterleri veya özel kodunun dışındaki karakterler içeremedi sayfa. Bu durumda, dikkat etmeniz gereken tek şey, kaynak sütunla aynı Harmanlamayı veya aynı Kod Sayfasını kullanan en az birini kullanmaktır. Yani, kaynak sütun kullanıyorsa SQL_Latin1_General_CP1_CI_AS, Latin1_General_100_CI_AShedefte kullanabilirsiniz .

Hangi Harmanlamayı kullanacağınızı öğrendikten sonra:

  • ALTER TABLE ... ALTER COLUMN ...olmak VARCHAR(geçerli belirtmek emin olun NULL/ NOT NULLbiraz zaman 87 milyon satır için işlem günlük çok yer gerektirir ayar) VEYA

  • Her biri için yeni "ColumnName_tmp" sütunları oluşturun ve UPDATEyaparak yavaşça doldurun TOP (1000) ... WHERE new_column IS NULL. Tüm satırlar doldurulduktan (ve hepsinin doğru bir şekilde kopyalandığını doğruladıktan sonra! Varsa UPDATE'leri işlemek için bir tetikleyiciye ihtiyacınız olabilir), açık bir işlemde, sp_rename"geçerli" sütunların sütun adlarını " _Old "ve ardından yeni" _tmp "sütunlarını basitçe adlardan" _tmp "kaldırmak için. Ardından sp_reconfigure, tabloya başvuran önbelleğe alınmış planları geçersiz kılmak için tabloyu arayın ve tabloya referans veren herhangi bir Görünüm varsa aramanız gerekir sp_refreshview(veya bunun gibi bir şey). Uygulamayı doğruladıktan ve ETL onunla doğru bir şekilde çalıştıktan sonra sütunları bırakabilirsiniz.


Sağladığınız CodePage sorgusunu hem kaynak hem de hedefte çalıştırdım ve CodePage 1252 ve harmanlama_adı hem kaynak hem de hedef üzerinde SQL_Latin1_General_CP1_CI_AS.
John G Hohengarten

@ JohnGHohengarten En altta tekrar güncelledim. Kolay olmak için Latin1_General_100_CI_AS, kullandığınızdan daha iyi olmasına rağmen aynı Harmanlamayı koruyabilirsiniz . Daha önce bahsettiğim yeni Harmanlama kadar iyi olmasa bile, sıralama ve karşılaştırma davranışının aralarında aynı olacağı anlamına gelir.
Solomon Rutzky

4

Gerçek bir işim varken arkadan biraz deneyimim var. O zaman temel verileri korumak istediğimden ve karışıklıkta kaybolacak karakterlere sahip olabilecek yeni verileri de hesaba katmam gerektiğinden, kalıcı olmayan bir hesaplanmış sütunla gittim.

İşte SO veri dökümünden Süper Kullanıcı veritabanının bir kopyasını kullanan hızlı bir örnek .

Unicode karakterleri olan DisplayNames olduğunu hemen görebiliyoruz:

Fındık

Öyleyse kaç tane hesaplamak için hesaplanmış bir sütun ekleyelim! GörünenAd sütunu 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

Sayı ~ 3000 satır döndürür

Fındık

Yine de, yürütme planı biraz sürükleniyor. Sorgu hızla bitiyor, ancak bu veri kümesi çok büyük değil.

Fındık

Hesaplanan sütunların bir dizin eklemek için kalıcı olması gerekmediğinden, bunlardan birini yapabiliriz:

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

Bu bize biraz daha düzenli bir plan verir:

Fındık

Bu değilse anlarım o mimari değişiklikleri içerir, ancak, muhtemelen kendini yine tabloyu katılmasını sorgu ile başa çıkmak için indeksleri eklemeyi bakıyoruz verilerin boyutunu dikkate beri cevap.

Bu yardımcı olur umarım!


1

Bir alanın unicode veri içerip içermediğini kontrol etme örneğini kullanarak, her sütundaki verileri okuyabilir CASTve aşağıdaki ve kontrol edebilirsiniz:

--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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.