J'ai une table dans SQL Server qui ressemble à ceci:
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
Je travaille sur une procédure stockée pour différencier, qui prend les données d'entrée et un numéro de version. Les données d'entrée ont des colonnes du champ Nom uptilZ. La plupart des colonnes de champ sont censées être NULL, c'est-à-dire que chaque ligne contient généralement des données uniquement pour les premiers champs, les autres sont NULL. Le nom, la date et la version forment une contrainte unique sur la table.
J'ai besoin de différencier les données entrées par rapport à ce tableau, pour une version donnée. Chaque ligne doit être différenciée - une ligne est identifiée par le nom, la date et la version, et tout changement dans l'une des valeurs dans les colonnes de champ devra apparaître dans le diff.
Mise à jour: tous les champs n'ont pas besoin d'être de type décimal. Certains d'entre eux peuvent être des nvarchars. Je préférerais que le diff se produise sans convertir le type, bien que la sortie diff puisse tout convertir en nvarchar car elle ne doit être utilisée qu'à des fins d'affichage.
Supposons que l'entrée soit la suivante et que la version demandée soit 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
Le diff doit être au format suivant:
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
Ma solution jusqu'à présent est de générer d'abord un diff, en utilisant EXCEPT et UNION. Convertissez ensuite le diff au format de sortie souhaité à l'aide de JOIN et CROSS APPLY. Bien que cela semble fonctionner, je me demande s'il existe un moyen plus propre et plus efficace de le faire. Le nombre de champs est proche de 100, et chaque place dans le code qui a un ... est en fait un grand nombre de lignes. La table d'entrée et la table existante devraient être assez importantes au fil du temps. Je suis nouveau dans SQL et j'essaie toujours d'apprendre le réglage des performances.
Voici le SQL pour cela:
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)
Je vous remercie!
Modifier les champs ayant différents types, pas seulement
decimal
.Vous pouvez essayer d'utiliser le
sql_variant
type. Je ne l'ai jamais utilisé personnellement, mais cela peut être une bonne solution pour votre cas. Pour l'essayer, remplacez tout simplement[decimal](38, 10)
parsql_variant
dans le script SQL. La requête elle-même reste exactement telle qu'elle est, aucune conversion explicite n'est nécessaire pour effectuer la comparaison. Le résultat final aurait une colonne avec des valeurs de différents types. Très probablement, vous devrez éventuellement savoir quel type se trouve dans quel champ pour traiter les résultats dans votre application, mais la requête elle-même devrait fonctionner correctement sans conversions.Soit dit en passant, c'est une mauvaise idée de stocker les dates en tant que
int
.Au lieu d'utiliser
EXCEPT
etUNION
de calculer le diff, j'utiliseraisFULL JOIN
. Pour moi, personnellement, il est difficile de suivre la logiqueEXCEPT
et l'UNION
approche.Je commencerais par annuler le pivot des données, plutôt que de le faire en dernier (en utilisant
CROSS APPLY(VALUES)
comme vous le faites). Vous pouvez vous débarrasser de la suppression de pivot de l'entrée, si vous le faites à l'avance, du côté de l'appelant.Vous devez répertorier les 100 colonnes uniquement dans
CROSS APPLY(VALUES)
.La requête finale est assez simple, donc la table temporaire n'est pas vraiment nécessaire. Je pense qu'il est plus facile d'écrire et de maintenir que votre version. Voici SQL Fiddle .
Configurer des exemples de données
Requête principale
CTE_Main
est des données originales non pivotées filtrées en fonction des donnéesVersion
.CTE_Input
est une table d'entrée, qui pourrait déjà être fournie dans ce format. La requête principale utiliseFULL JOIN
, ce qui ajoute aux lignes de résultat avecBee
. Je pense qu'ils devraient être retournés, mais si vous ne voulez pas les voir, vous pouvez les filtrer en ajoutantAND CTE_Input.FieldValue IS NOT NULL
ou peut-être en utilisant à laLEFT JOIN
place deFULL JOIN
, je n'ai pas examiné les détails, car je pense qu'ils devraient être retournés.Résultat
la source