Ich habe eine Tabelle in SQL Server, die so aussieht:
Id |Version |Name |date |fieldA |fieldB ..|fieldZ
1 |1 |Foo |20120101|23 | ..|25334123
2 |2 |Foo |20120101|23 |NULL ..|NULL
3 |2 |Bar |20120303|24 |123......|NULL
4 |2 |Bee |20120303|34 |-34......|NULL
Ich arbeite an einer gespeicherten Prozedur zum Diff, die Eingabedaten und eine Versionsnummer verwendet. Die Eingabedaten enthalten Spalten aus Name bis FeldZ. Es wird erwartet, dass die meisten Feldspalten NULL sind, dh jede Zeile enthält normalerweise nur Daten für die ersten Felder, der Rest ist NULL. Der Name, das Datum und die Version bilden eine eindeutige Einschränkung für die Tabelle.
Ich muss die Daten, die in Bezug auf diese Tabelle eingegeben werden, für eine bestimmte Version unterscheiden. Jede Zeile muss unterschiedlich sein - eine Zeile wird durch den Namen, das Datum und die Version identifiziert, und jede Änderung der Werte in den Feldspalten muss im Unterschied angezeigt werden.
Update: Alle Felder müssen nicht dezimal sein. Einige von ihnen können nvarchars sein. Ich würde es vorziehen, wenn das Diff ohne Konvertierung des Typs erfolgt, obwohl die Diff-Ausgabe alles in nvarchar konvertieren könnte, da es nur für die Anzeige verwendet werden soll.
Angenommen, die Eingabe ist die folgende und die angeforderte Version ist 2:
Name |date |fieldA |fieldB|..|fieldZ
Foo |20120101|25 |NULL |.. |NULL
Foo |20120102|26 |27 |.. |NULL
Bar |20120303|24 |126 |.. |NULL
Baz |20120101|15 |NULL |.. |NULL
Der Diff muss das folgende Format haben:
name |date |field |oldValue |newValue
Foo |20120101|FieldA |23 |25
Foo |20120102|FieldA |NULL |26
Foo |20120102|FieldB |NULL |27
Bar |20120303|FieldB |123 |126
Baz |20120101|FieldA |NULL |15
Meine bisherige Lösung besteht darin, zunächst mit EXCEPT und UNION ein Diff zu generieren. Konvertieren Sie dann das Diff mit JOIN und CROSS APPLY in das gewünschte Ausgabeformat. Obwohl dies zu funktionieren scheint, frage ich mich, ob es einen saubereren und effizienteren Weg gibt, dies zu tun. Die Anzahl der Felder liegt nahe bei 100, und jede Stelle im Code, die ein ... enthält, besteht tatsächlich aus einer großen Anzahl von Zeilen. Es wird erwartet, dass sowohl die Eingabetabelle als auch die vorhandene Tabelle im Laufe der Zeit ziemlich groß sind. Ich bin neu in SQL und versuche immer noch, die Leistungsoptimierung zu lernen.
Hier ist die SQL dafür:
CREATE TABLE #diff
( [change] [nvarchar](50) NOT NULL,
[name] [nvarchar](50) NOT NULL,
[date] [int] NOT NULL,
[FieldA] [decimal](38, 10) NULL,
[FieldB] [decimal](38, 10) NULL,
.....
[FieldZ] [decimal](38, 10) NULL
)
--Generate the diff in a temporary table
INSERT INTO #diff
SELECT * FROM
(
(
SELECT
'old' as change,
name,
date,
FieldA,
FieldB,
...,
FieldZ
FROM
myTable mt
WHERE
version = @version
AND mt.name + '_' + CAST(mt.date AS VARCHAR) IN (SELECT name + '_' + CAST(date AS VARCHAR) FROM @diffInput)
EXCEPT
SELECT 'old' as change,* FROM @diffInput
)
UNION
(
SELECT 'new' as change, * FROM @diffInput
EXCEPT
SELECT
'new' as change,
name,
date,
FieldA,
FieldB,
...,
FieldZ
FROM
myTable mt
WHERE
version = @version
AND mt.name + '_' + CAST(mt.date AS VARCHAR) IN (SELECT name + '_' + CAST(date AS VARCHAR) FROM @diffInput)
)
) AS myDiff
SELECT
d3.name, d3.date, CrossApplied.field, CrossApplied.oldValue, CrossApplied.newValue
FROM
(
SELECT
d2.name, d2.date,
d1.FieldA AS oldFieldA, d2.FieldA AS newFieldA,
d1.FieldB AS oldFieldB, d2.FieldB AS newFieldB,
...
d1.FieldZ AS oldFieldZ, d2.FieldZ AS newFieldZ,
FROM #diff AS d1
RIGHT OUTER JOIN #diff AS d2
ON
d1.name = d2.name
AND d1.date = d2.date
AND d1.change = 'old'
WHERE d2.change = 'new'
) AS d3
CROSS APPLY (VALUES ('FieldA', oldFieldA, newFieldA),
('FieldB', oldFieldB, newFieldB),
...
('FieldZ', oldFieldZ, newFieldZ))
CrossApplied (field, oldValue, newValue)
WHERE
crossApplied.oldValue != crossApplied.newValue
OR (crossApplied.oldValue IS NULL AND crossApplied.newValue IS NOT NULL)
OR (crossApplied.oldValue IS NOT NULL AND crossApplied.newValue IS NULL)
Vielen Dank!
quelle
Bearbeiten Sie nicht nur Felder mit unterschiedlichen Typen
decimal
.Sie können versuchen,
sql_variant
type zu verwenden . Ich habe es nie persönlich benutzt, aber es kann eine gute Lösung für Ihren Fall sein. Um es zu versuchen, ersetzen Sie einfach alle[decimal](38, 10)
durchsql_variant
im SQL-Skript. Die Abfrage selbst bleibt genau so, wie sie ist. Für den Vergleich ist keine explizite Konvertierung erforderlich. Das Endergebnis würde eine Spalte mit Werten unterschiedlichen Typs enthalten. Höchstwahrscheinlich müssten Sie irgendwann wissen, welcher Typ sich in welchem Feld befindet, um die Ergebnisse in Ihrer Anwendung zu verarbeiten, aber die Abfrage selbst sollte ohne Konvertierungen problemlos funktionieren.Übrigens ist es eine schlechte Idee, Daten als zu speichern
int
.Anstatt den Diff zu verwenden
EXCEPT
undUNION
zu berechnen, würde ich verwendenFULL JOIN
. Für mich persönlich ist es schwierig, der Logik dahinter zu folgenEXCEPT
und mich zuUNION
nähern.Ich würde damit beginnen, die Daten zu entfernen, anstatt sie zuletzt zu tun (
CROSS APPLY(VALUES)
wie Sie es tun). Sie können das Aufheben der Eingabe auf der Anruferseite beseitigen, wenn Sie dies im Voraus tun.Sie müssten alle 100 Spalten nur in auflisten
CROSS APPLY(VALUES)
.Die endgültige Abfrage ist ziemlich einfach, so dass eine temporäre Tabelle nicht wirklich benötigt wird. Ich denke, es ist einfacher zu schreiben und zu warten als Ihre Version. Hier ist SQL Fiddle .
Richten Sie Beispieldaten ein
Hauptabfrage
CTE_Main
Es werden nicht gedrehte Originaldaten auf die angegebenen gefiltertVersion
.CTE_Input
ist eine Eingabetabelle, die bereits in diesem Format bereitgestellt werden könnte. Hauptabfrage verwendetFULL JOIN
, die zu Ergebniszeilen mit hinzufügtBee
. Ich denke, sie sollten zurückgegeben werden, aber wenn Sie sie nicht sehen möchten, können Sie sie herausfiltern, indem Sie sie hinzufügenAND CTE_Input.FieldValue IS NOT NULL
oder verwenden.LEFT JOIN
Stattdessen habeFULL JOIN
ich dort keine Details untersucht, da ich denke, dass sie zurückgegeben werden sollten.Ergebnis
quelle