Tengo una tabla en el servidor SQL que se ve así:
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
Estoy trabajando en un procedimiento almacenado para diferenciar, que toma datos de entrada y un número de versión. Los datos de entrada tienen columnas de Nombre hasta campoZ. Se espera que la mayoría de las columnas de campo sean NULL, es decir, cada fila generalmente tiene datos solo para los primeros campos, el resto son NULL. El nombre, la fecha y la versión forman una restricción única en la tabla.
Necesito diferenciar los datos que se ingresan con respecto a esta tabla, para una versión dada. Cada fila debe diferenciarse: una fila se identifica por el nombre, la fecha y la versión, y cualquier cambio en cualquiera de los valores en las columnas del campo deberá mostrarse en la diferencia.
Actualización: no es necesario que todos los campos sean de tipo decimal. Algunos de ellos pueden ser nvarchars. Preferiría que se produzca el diff sin convertir el tipo, aunque la salida de diff podría convertir todo a nvarchar, ya que se debe usar solo para la visualización intencionada.
Supongamos que la entrada es la siguiente y la versión solicitada es 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
El diff debe estar en el siguiente formato:
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
Mi solución hasta ahora es generar primero un diff, usando EXCEPT y UNION. Luego, convierta el diff al formato de salida deseado usando unir y aplicar cruz. Aunque esto parece estar funcionando, me pregunto si hay una forma más limpia y eficiente de hacerlo. El número de campos es cercano a 100, y cada lugar en el código que tiene un ... es en realidad una gran cantidad de líneas. Se espera que tanto la tabla de entrada como la tabla existente sean bastante grandes con el tiempo. Soy nuevo en SQL y todavía estoy tratando de aprender el ajuste del rendimiento.
Aquí está el SQL para ello:
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)
¡Gracias!
Edite con respecto a los campos que tienen diferentes tipos, no solo
decimal
.Puedes intentar usar
sql_variant
type. Nunca lo usé personalmente, pero puede ser una buena solución para su caso. Para probarlo, simplemente reemplace todo[decimal](38, 10)
consql_variant
el script SQL. La consulta en sí permanece exactamente como está, no se necesita una conversión explícita para realizar la comparación. El resultado final tendría una columna con valores de diferentes tipos. Lo más probable es que eventualmente tenga que saber de alguna manera qué tipo está en qué campo procesar los resultados en su aplicación, pero la consulta en sí misma debería funcionar bien sin conversiones.Por cierto, es una mala idea almacenar fechas como
int
.En lugar de usar
EXCEPT
yUNION
para calcular la diferencia, usaríaFULL JOIN
. Para mí, personalmente, es difícil seguir la lógicaEXCEPT
y elUNION
enfoque.Comenzaría por desconectar los datos, en lugar de hacerlo en último lugar (usando
CROSS APPLY(VALUES)
como lo hace). Puede deshacerse de la desconexión de la entrada, si lo hace con anticipación, en el lado de la persona que llama.Tendría que enumerar las 100 columnas solo en
CROSS APPLY(VALUES)
.La consulta final es bastante simple, por lo que la tabla temporal no es realmente necesaria. Creo que es más fácil de escribir y mantener que su versión. Aquí está SQL Fiddle .
Configurar datos de muestra
Consulta principal
CTE_Main
son datos originales sin pivotar filtrados a los dadosVersion
.CTE_Input
es la tabla de entrada, que podría proporcionarse ya en este formato. La consulta principal utilizaFULL JOIN
, que se agrega a las filas de resultados conBee
. Creo que deberían devolverse, pero si no desea verlos, puede filtrarlos agregandoAND CTE_Input.FieldValue IS NOT NULL
o tal vez usando enLEFT JOIN
lugar deFULL JOIN
, no busqué detalles allí, porque creo que deberían devolverse.Resultado
fuente