it-swarm.com.de

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

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

d.h. [Q]. [G] ---> [P]. [G]

EDIT: 20.03.2017: Einige Leute haben gefragt, ob die Quelltabelle die EINZIGE Quelle für die Zieltabelle ist. Ja, es ist die einzige Quelle. Was die ETL angeht, findet keine wirkliche Transformation statt. Es soll effektiv 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 (Quellentabelle):

  • 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 [P] ändern. [G] (Ziel) Spalten Datentypen von NVARCHAR bis VARCHAR. Ich möchte es sicher machen (ohne Datenverlust durch Konvertierung).

Wie kann ich die Datenwerte in jeder NVARCHAR-Spalte in der Zieltabelle überprüfen, um zu bestätigen, ob die Spalte tatsächlich Unicode-Daten enthält oder nicht?

Eine Abfrage (DMVs?), Die jeden Wert (in einer Schleife?) Jeder NVARCHAR-Spalte überprüfen und mir mitteilen kann, ob einer der Werte echt ist. Unicode wäre die ideale Lösung. Andere Methoden sind jedoch willkommen.

14

Angenommen, eine Ihrer Spalten enthält keine Unicode-Daten. Um zu überprüfen, ob Sie den Spaltenwert für jede Zeile lesen müssen. Sofern Sie keinen Index für die Spalte haben, müssen Sie bei einer Zeilenspeichertabelle 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 lesen Sie die Daten der Tabelle nicht oft und müssen keinen Cursor oder eine andere Art von Schleife codieren.

Um eine einzelne Spalte zu überprüfen, glauben Sie, dass Sie dies einfach tun können:

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

Eine Umwandlung von NVARCHAR in VARCHAR sollte das gleiche Ergebnis liefern, außer wenn Unicode-Zeichen vorhanden sind. Unicode-Zeichen werden in ? Konvertiert. Der obige Code sollte also NULL Fälle korrekt behandeln. Sie müssen 24 Spalten überprüfen, sodass Sie jede Spalte in einer einzelnen Abfrage mithilfe von skalaren Aggregaten überprüfen. 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 1, Wenn einer der Werte Unicode enthält. Ein Ergebnis von 0 Bedeutet, dass alle Daten sicher konvertiert werden können.

Ich empfehle dringend, 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 an Ort und Stelle tun, sodass das Erstellen einer Kopie möglicherweise nicht viel langsamer ist. Wenn Sie eine Kopie haben, können Sie leicht überprüfen, ob alle Daten noch vorhanden sind (eine Möglichkeit besteht darin, das Schlüsselwort AUSSER zu verwenden) und den Vorgang rückgängig zu machen sehr leicht.

Beachten Sie auch, dass Sie derzeit möglicherweise keine Unicode-Daten haben. Möglicherweise kann eine zukünftige ETL Unicode in eine zuvor saubere Spalte laden. Wenn dies in Ihrem ETL-Prozess nicht überprüft wird, sollten Sie dies vor dieser Konvertierung hinzufügen.

10
Joe Obbish

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

  1. Gibt es andere andere Quellen als [Q].[G], Die diese Tabelle füllen?

    Wenn die Antwort etwas außerhalb von "Ich bin zu 100% sicher, dass dies die einzige Datenquelle für diese Zieltabelle ist" ist ", dann machen Sie nicht Änderungen, unabhängig davon, ob die aktuell in der Tabelle enthaltenen Daten ohne Datenverlust konvertiert werden können oder nicht.

  2. Gibt es Pläne/Diskussionen zum Hinzufügen zusätzlicher Quellen, um diese Daten in naher Zukunft zu füllen?

    Und ich möchte eine verwandte Frage hinzufügen: Gab es eine Diskussion über die Unterstützung mehrerer Sprachen in der aktuellen Quellentabelle (dh [Q].[G]) Durch Konvertieren zu 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, sonst würden Sie diese Frage nicht stellen. Wenn diese Fragen jedoch als "Nein" angenommen wurden, müssen sie gestellt und von a gestellt werden breites Publikum, um die genaueste/vollständigste Antwort zu erhalten.

Das Hauptproblem hierbei ist nicht so sehr, dass Unicode-Codepunkte vorhanden sind, die nicht konvertieren können (je), sondern vielmehr, dass Codepunkte nicht alle konvertieren auf eine einzelne Codepage passen. Das ist das Schöne an Unicode: Es kann Zeichen von ALLEN Codepages enthalten. Wenn Sie von NVARCHAR - wo Sie sich nicht um Codepages kümmern müssen - nach VARCHAR konvertieren, müssen Sie sicherstellen, dass die Sortierung der Zielspalte denselben Code verwendet Seite als Quellenspalte. Dies setzt voraus, dass entweder eine Quelle oder mehrere Quellen dieselbe Codepage verwenden (jedoch nicht unbedingt dieselbe Sortierung). Wenn es jedoch mehrere Quellen mit mehreren Codepages gibt, 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, können alle diese Zeichen in VARCHAR konvertiert werden, nur nicht in derselben VARCHAR -Spalte.

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

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 WIRD GESAGT ....

Sie haben erwähnt, dass Sie auf SQL Server 2008 R2 arbeiten, ABER Sie haben nicht gesagt, welche Edition. Wenn Sie sich gerade in der Enterprise Edition befinden, vergessen Sie all diese Konvertierungsaufgaben (da Sie dies wahrscheinlich nur tun, um Platz zu sparen) und aktivieren Sie die Datenkomprimierung:

Implementierung der Unicode-Komprimierung

Wenn Sie die Standard Edition verwenden (und es scheint, als wären Sie ????), gibt es eine weitere Möglichkeit: ein Upgrade auf SQL Server 2016, da SP1 die Möglichkeit für alle Editionen bietet, die Datenkomprimierung zu verwenden (denken Sie daran, ich habe gesagt " langer Schuss" ???? ).

Jetzt, da gerade 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 Nur-Unicode-Zeichen oder Zeichen außerhalb ihres spezifischen Codes enthalten konnte Seite. In diesem Fall sollten Sie nur darauf achten, dass Sie dieselbe Sortierung wie die Quellenspalte verwenden oder mindestens eine, die dieselbe Codepage verwendet. Das heißt, wenn die Quellenspalte SQL_Latin1_General_CP1_CI_AS Verwendet, können Sie am Ziel Latin1_General_100_CI_AS Verwenden.

Sobald Sie wissen, welche Kollatierung Sie verwenden sollen, können Sie entweder:

  • ALTER TABLE ... ALTER COLUMN ... Muss VARCHAR sein (geben Sie unbedingt die aktuelle Einstellung NULL/NOT NULL An), was etwas Zeit und viel Platz im Transaktionsprotokoll erfordert für 87 Millionen Zeilen, OR

  • Erstellen Sie für jede Spalte neue "ColumnName_tmp" -Spalten und füllen Sie sie langsam über UPDATE mit TOP (1000) ... WHERE new_column IS NULL. Sobald alle Zeilen ausgefüllt sind (und überprüft wurde, ob sie alle korrekt kopiert wurden! Möglicherweise benötigen Sie einen Trigger, um UPDATEs zu verarbeiten, falls vorhanden), verwenden Sie in einer expliziten Transaktion sp_rename, Um die Spaltennamen des " aktuelle "Spalten sollen" _Old "sein und dann die neuen" _tmp "-Spalten, um einfach" _tmp "aus den Namen zu entfernen. Rufen Sie dann sp_reconfigure In der Tabelle auf, um zwischengespeicherte Pläne, die auf die Tabelle verweisen, ungültig zu machen. Wenn Ansichten auf die Tabelle verweisen, müssen Sie sp_refreshview (Oder ähnliches) aufrufen. Sobald Sie die App validiert haben und ETL ordnungsgemäß damit arbeitet, können Sie die Spalten löschen.

5
Solomon Rutzky

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, die möglicherweise Zeichen enthalten könnten, die beim Mischen verloren gehen würden, habe ich mich für eine nicht persistierte berechnete Spalte entschieden.

Hier ist ein kurzes Beispiel mit einer Kopie der Super User-Datenbank aus dem SO-Daten-Dump .

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

(Nuts

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

(Nuts

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

(Nuts

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 aufgeräumteren Plan gibt:

(Nuts

Ich verstehe, wenn dies nicht die Antwort ist, da es sich um Architekturänderungen handelt, aber angesichts der Größe der Daten möchten Sie wahrscheinlich Indizes hinzufügen um mit Anfragen fertig zu werden, die sich sowieso selbst der Tabelle anschließen.

Hoffe das hilft!

4
Erik Darling

Anhand des Beispiels in So überprüfen Sie, ob ein Feld Unicode-Daten enthält können Sie die Daten in jeder Spalte lesen und CAST ausführen und unten Folgendes ü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
1
Scott Hodgin