Ermitteln Sie, ob Werte in NVARCHAR-Spalten tatsächlich Unicode sind

14

Ich habe einige SQL Server-Datenbanken geerbt. Es gibt eine Tabelle (ich nenne sie "G") mit etwa 86,7 Millionen Zeilen und 41 Spalten Breite aus einer Quellendatenbank (ich nenne sie "Q") in SQL Server 2014 Standard, auf die ETL übertragen wird eine Zieldatenbank (ich werde "P" nennen) mit demselben Tabellennamen in SQL Server 2008 R2 Standard.

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

EDIT: 20.03.2017: Einige Leute haben gefragt, ob die Quelltabelle die EINZIGE Quelle zur Zieltabelle ist. Ja, es ist die einzige Quelle. Was die ETL angeht, findet keine echte Transformation statt. Eigentlich soll es eine 1: 1-Kopie der Quelldaten sein. Daher ist nicht geplant, dieser Zieltabelle zusätzliche Quellen hinzuzufügen.

Etwas mehr als die Hälfte der Spalten in [Q]. [G] sind VARCHAR (Quelltabelle):

  • 13 der Spalten sind VARCHAR (80)
  • 9 der Spalten sind VARCHAR (30)
  • 2 der Spalten sind VARCHAR (8).

In ähnlicher Weise sind dieselben Spalten in [P]. [G] NVARCHAR (Zieltabelle) mit derselben Anzahl von Spalten mit derselben Breite. (Mit anderen Worten, gleiche Länge, aber NVARCHAR).

  • 13 der Spalten sind NVARCHAR (80)
  • 9 der Spalten sind NVARCHAR (30)
  • 2 der Spalten sind NVARCHAR (8).

Das ist nicht mein Design.

Ich möchte die Datentypen der Spalten [P]. [G] (Ziel) von NVARCHAR nach VARCHAR ÄNDERN. Ich möchte es sicher machen (ohne Datenverlust durch Konvertierung).

Wie kann ich anhand der Datenwerte in jeder NVARCHAR-Spalte in der Zieltabelle feststellen, ob die Spalte tatsächlich Unicode-Daten enthält oder nicht?

Eine Abfrage (DMVs?), Die jeden Wert (in einer Schleife?) Jeder NVARCHAR-Spalte überprüft und angibt, ob einer der Werte echtes Unicode ist, ist die ideale Lösung. Andere Methoden sind jedoch willkommen.

John G. Hohengarten
quelle
2
Betrachten Sie zuerst Ihren Prozess und wie die Daten verwendet werden. Die Daten in [G]werden an ETL übergeben [P]. Wenn dies der Fall [G]ist varcharund der ETL-Prozess der einzige Weg ist, auf dem Daten [P]eingehen, sollte es keinen geben, sofern der Prozess keine echten Unicode-Zeichen hinzufügt. Wenn andere Prozesse Daten hinzufügen oder ändern [P], müssen Sie vorsichtiger sein - nur weil alle aktuellen Daten vorhanden sein können, varcharbedeutet dies nicht, dass nvarcharDaten morgen nicht hinzugefügt werden können. Ebenso ist es möglich, dass alles, was Daten verbraucht, Daten [P]benötigt nvarchar.
RDFozz

Antworten:

10

Angenommen, eine Ihrer Spalten enthält keine Unicode-Daten. Um sicherzustellen, dass Sie den Spaltenwert für jede Zeile lesen müssen. Sofern Sie keinen Index für die Spalte haben, müssen Sie bei einer Rowstore-Tabelle jede Datenseite aus der Tabelle lesen. Vor diesem Hintergrund halte ich es für sehr sinnvoll, alle Spaltenprüfungen in einer einzigen Abfrage für die Tabelle zu kombinieren. Auf diese Weise werden Sie die Daten der Tabelle nicht viele Male lesen und müssen keinen Cursor oder eine andere Art von Schleife codieren.

Um eine einzelne Spalte zu überprüfen, gehen Sie folgendermaßen vor:

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

Ein Cast von NVARCHARbis VARCHARsollte dasselbe Ergebnis liefern, außer wenn es Unicode-Zeichen gibt. Unicode-Zeichen werden in konvertiert ?. Daher sollte der obige Code die NULLFälle korrekt behandeln. Sie müssen 24 Spalten überprüfen, sodass Sie jede Spalte in einer einzelnen Abfrage mithilfe von skalaren Aggregaten überprüfen können. Eine Implementierung ist unten:

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

Für jede Spalte erhalten Sie das Ergebnis, 1ob einer der Werte Unicode enthält. Das Ergebnis von 0bedeutet, dass alle Daten sicher konvertiert werden können.

Es wird dringend empfohlen, eine Kopie der Tabelle mit den neuen Spaltendefinitionen zu erstellen und Ihre Daten dort zu kopieren. Sie werden teure Konvertierungen durchführen, wenn Sie dies direkt tun, sodass das Erstellen einer Kopie möglicherweise nicht so viel langsamer ist. Wenn Sie über eine Kopie verfügen , können Sie auf einfache Weise überprüfen, ob alle Daten noch vorhanden sind (eine Möglichkeit ist die Verwendung des Schlüsselworts EXCEPT ), und Sie können den Vorgang sehr einfach rückgängig machen.

Beachten Sie außerdem, dass Sie derzeit möglicherweise keine Unicode-Daten haben. Möglicherweise lädt eine zukünftige ETL Unicode in eine zuvor bereinigte Spalte. Wenn dies in Ihrem ETL-Prozess nicht überprüft wird, sollten Sie dies hinzufügen, bevor Sie diese Konvertierung durchführen.

Joe Obbish
quelle
Während die Antwort und Diskussion von @srutzky recht gut war und hilfreiche Informationen enthielt, stellte Joe mir das zur Verfügung, was meine Frage verlangte: eine Abfrage, um mir mitzuteilen, ob Werte in Spalten tatsächlich Unicode enthalten. Deshalb habe ich Joes Antwort als akzeptierte Antwort markiert. Ich habe die anderen Antworten, die mir auch geholfen haben, hochgestuft.
John G Hohengarten
@JohnGHohengarten und Joe: Das ist in Ordnung. Ich habe die Frage nicht erwähnt, da sie in dieser Antwort genauso enthalten war wie die von Scott. Ich würde nur sagen, dass die NVARCHARSpalte nicht konvertiert werden muss, NVARCHARda es sich bereits um diesen Typ handelt. Und nicht sicher, wie Sie das nicht konvertierbare Zeichen ermittelt haben, aber Sie können die Spalte konvertieren VARBINARY, um die UTF-16-Byte-Sequenzen zu erhalten. Und UTF-16 ist die umgekehrte Bytereihenfolge, also p= 0x7000und dann kehren Sie diese beiden Bytes um, um den Codepunkt zu erhalten U+0070. Wenn die Quelle jedoch VARCHAR ist, kann es sich nicht um ein Unicode-Zeichen handeln. Etwas anderes ist los. Benötigen Sie weitere Informationen.
Solomon Rutzky
@srutzky Ich habe die Besetzung hinzugefügt, um Probleme mit der Priorität von Datentypen zu vermeiden. Sie können richtig sein, dass es nicht benötigt wird. Für die andere Frage schlug ich UNICODE () und SUBSTRING () vor. Funktioniert dieser Ansatz?
Joe Obbish
@JohnGHohengarten und Joe: Datentypvorrang sollte kein Problem sein, da VARCHARimplizit konvertiert wird NVARCHAR, aber es ist möglicherweise besser, dies zu tun CONVERT(NVARCHAR(80), CONVERT(VARCHAR(80), column)) <> column. SUBSTRINGManchmal funktioniert es, aber es funktioniert nicht mit Zusatzzeichen, wenn Kollatierungen verwendet werden, die nicht auf enden _SC, und die, die John verwendet, funktioniert nicht, obwohl dies hier wahrscheinlich kein Problem darstellt. Die Konvertierung in VARBINARY funktioniert jedoch immer. Und CONVERT(VARCHAR(10), CONVERT(NVARCHAR(10), '›'))führt nicht dazu ?, also würde ich die Bytes sehen wollen. Der ETL-Prozess hat ihn möglicherweise konvertiert.
Solomon Rutzky
5

Bevor Sie etwas unternehmen, sollten Sie die von @RDFozz gestellten Fragen in einem Kommentar zur Frage berücksichtigen, nämlich:

  1. Gibt es irgendwelche anderen Quellen außer [Q].[G]dieser Tabelle bevölkern ?

    Wenn die Antwort nicht "Ich bin zu 100% sicher, dass dies die einzige Datenquelle für diese Zieltabelle ist" lautet , nehmen Sie keine Änderungen vor, unabhängig davon, ob die aktuell in der Tabelle enthaltenen Daten konvertiert werden können oder nicht Datenverlust.

  2. Gibt es irgendwelche Pläne / Diskussionen im Zusammenhang mit zusätzlichen Quellen Addiert man diese Daten in naher Zukunft zu bevölkern ?

    Und ich möchte eine verwandte Frage hinzufügen: Gab es eine Diskussion darüber, ob mehrere Sprachen in der aktuellen Quelltabelle (dh [Q].[G]) unterstützt werden, indem sie in konvertiert werden NVARCHAR?

    Sie müssen herumfragen, um ein Gefühl für diese Möglichkeiten zu bekommen. Ich gehe davon aus, dass Ihnen derzeit nichts gesagt wurde, was in diese Richtung weisen würde, ansonsten würden Sie diese Frage nicht stellen. Wenn jedoch angenommen wurde, dass diese Fragen "nein" lauten, müssen sie von a gestellt und gestellt werden breit genug Publikum, um die genaueste / vollständigste Antwort zu erhalten.

Das Hauptproblem hierbei ist nicht so sehr, dass Unicode-Codepunkte (jemals) nicht konvertiert werden können , sondern vielmehr, dass Codepunkte nicht alle auf eine einzelne Codepage passen. Das ist das Schöne an Unicode: Es kann Zeichen aus ALLEN Codepages enthalten. Wenn Sie von NVARCHAR- wo Sie sich nicht um Codepages kümmern müssen - nach konvertieren , müssen VARCHARSie sicherstellen, dass für die Sortierung der Zielspalte dieselbe Codepage wie für die Quellspalte verwendet wird. Dies setzt voraus, dass entweder eine Quelle oder mehrere Quellen dieselbe Codepage verwenden (jedoch nicht unbedingt dieselbe Kollatierung). Wenn jedoch mehrere Quellen mit mehreren Codepages vorhanden sind, kann möglicherweise das folgende Problem auftreten:

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;

Rückgabe (2. Ergebnismenge):

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

Wie Sie sehen können, alle diese Zeichen können konvertieren VARCHAR, nur nicht in der gleichen VARCHARSpalte.

Verwenden Sie die folgende Abfrage, um die Codepage für jede Spalte Ihrer Quelltabelle zu bestimmen:

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

DAS GESAGT WERDEN ....

Sie haben erwähnt, dass Sie unter SQL Server 2008 R2 arbeiten, aber Sie haben nicht gesagt, welche Edition. WENN Sie sich zufällig in der Enterprise Edition befinden, vergessen Sie all dieses Konvertierungsmaterial (da Sie es wahrscheinlich nur tun, um Speicherplatz zu sparen) und aktivieren Sie die Datenkomprimierung:

Implementierung der Unicode-Komprimierung

Wenn Sie die Standard Edition verwenden (und es scheint, dass Sie you sind), gibt es eine weitere Möglichkeit, die noch zu lange reicht: ein Upgrade auf SQL Server 2016, da SP1 die Möglichkeit enthält, dass alle Editionen die Datenkomprimierung verwenden (denken Sie daran, ich habe "Long Shot" gesagt) "😉).

Nachdem jetzt klargestellt wurde, dass es nur eine Quelle für die Daten gibt, müssen Sie sich natürlich keine Sorgen mehr machen, da die Quelle keine reinen Unicode-Zeichen oder Zeichen außerhalb ihres spezifischen Codes enthalten darf Seite. In diesem Fall sollten Sie nur die gleiche Kollatierung wie die Quellenspalte verwenden oder mindestens eine, die dieselbe Codepage verwendet. Das heißt, wenn die Quellenspalte verwendet wird SQL_Latin1_General_CP1_CI_AS, können Sie sie Latin1_General_100_CI_ASam Ziel verwenden.

Sobald Sie wissen, welche Sortierung verwendet werden soll, können Sie entweder:

  • ALTER TABLE ... ALTER COLUMN ...zu sein VARCHAR(achten Sie darauf, die aktuelle NULL/ NOT NULLEinstellung anzugeben ), was ein wenig Zeit und viel Transaktionsprotokollspeicher für 87 Millionen Zeilen erfordert, ODER

  • Erstellen Sie für jede Spalte neue "ColumnName_tmp" -Spalten und füllen Sie diese langsam über " UPDATEdoing" aus TOP (1000) ... WHERE new_column IS NULL. Sobald alle Zeilen ausgefüllt sind (und überprüft wurden, dass sie alle korrekt kopiert wurden!), Müssen Sie möglicherweise einen Trigger zum Behandeln von UPDATEs (sofern vorhanden) in einer expliziten Transaktion verwenden sp_rename, um die Spaltennamen der "aktuellen" zu verwendenden Spalten auszutauschen. " _Old "und dann die neuen Spalten" _tmp ", um einfach das" _tmp "aus den Namen zu entfernen. Rufen Sie dann sp_reconfiguredie Tabelle auf, um zwischengespeicherte Pläne, die auf die Tabelle verweisen, ungültig zu machen. Wenn Ansichten vorhanden sind, die auf die Tabelle verweisen, müssen Sie sie aufrufen sp_refreshview(oder so ähnlich). Sobald Sie die App validiert haben und die ETL ordnungsgemäß damit funktioniert, können Sie die Spalten löschen.

Solomon Rutzky
quelle
Ich habe die von Ihnen bereitgestellte CodePage-Abfrage sowohl für die Quelle als auch für das Ziel ausgeführt. Die CodePage ist 1252 und der Name der Sortierfolge ist SQL_Latin1_General_CP1_CI_AS für die Quelle UND das Ziel.
John G Hohengarten
@JohnGHohengarten habe ich gerade nochmal aktualisiert, ganz unten. Um einfach zu sein, können Sie dieselbe Sortierung beibehalten, auch wenn sie Latin1_General_100_CI_ASviel besser ist als die, die Sie verwenden. Das bedeutet, dass das Sortier- und Vergleichsverhalten zwischen ihnen gleich ist, auch wenn es nicht so gut ist wie die neuere Sortierung, die ich gerade erwähnt habe.
Solomon Rutzky
4

Ich habe einige Erfahrungen damit von damals, als ich einen richtigen Job hatte. Da ich zu der Zeit die Basisdaten beibehalten wollte und auch neue Daten berücksichtigen musste, bei denen möglicherweise Zeichen verloren gingen, entschied ich mich für eine nicht persistierte berechnete Spalte.

Hier ist ein kurzes Beispiel für die Verwendung einer Kopie der Super User-Datenbank aus dem SO-Daten-Dump .

Wir können sofort erkennen, dass es DisplayNames mit Unicode-Zeichen gibt:

Nüsse

Fügen wir also eine berechnete Spalte hinzu, um herauszufinden, wie viele! Die Spalte DisplayName lautet 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

Die Anzahl gibt ~ 3000 Zeilen zurück

Nüsse

Der Ausführungsplan ist allerdings ein bisschen langweilig. Die Abfrage wird schnell beendet, aber dieser Datensatz ist nicht besonders groß.

Nüsse

Da berechnete Spalten nicht beibehalten werden müssen, um einen Index hinzuzufügen, können wir eine der folgenden Aktionen ausführen:

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

Was uns einen etwas ordentlicheren Plan gibt:

Nüsse

Ich verstehe, wenn das nicht ist die Antwort ist, da es sich um architektonische Änderungen handelt. Angesichts der Größe der Daten möchten Sie jedoch wahrscheinlich Indizes hinzufügen, um Abfragen zu bewältigen, die sich selbst in die Tabelle eintragen.

Hoffe das hilft!

Erik Darling
quelle
1

Anhand des Beispiels unter So überprüfen Sie, ob ein Feld Unicode-Daten enthält , können Sie die Daten in jeder Spalte lesen und die folgenden CASTSchritte ausführen und überprüfen:

--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
quelle