Identificar filas que no coinciden con una fila maestra
-
22-10-2019 - |
Pregunta
Estoy comparando un montón de tablas de diferentes bases de datos en diferentes servidores con un registro maestro. Necesito saber qué servidores, identificados por locationID
, tienen las filas que no coinciden porque pueden necesitar mantenimiento.
Tengo un simple EXCEPT
consulta donde comparo una tabla donde cada fila es la configuración de cada servidor; table1
tiene una fila por servidor con toda la configuración más locationID
Lo cual es una columna que me dice qué servidor es. Comparo todos esto con un table1_master
tabla que tiene la configuración correcta, pero excluyo el locationID
ya que no coincidirá.
Consulta simple a continuación:
SELECT everything, but, locationID
FROM table1
EXCEPT
SELECT everything, but, locationID
FROM table1_master
Solo hay una Master Fila, comparo todos los servidores, y no selecciono su locationID
aquí.
Este es un ejemplo de las filas que estoy comparando. Cada uno tiene una clave primaria, una sola columna varchar
y una lista gigante de eso son docenas de columnas. Quiero comparar todas las columnas excepto UbicationId, pero necesito UcitionId para identificar las filas.
LocationID setting setting setting setting
CS02 C Y Y Y Y
CS03 C Y Y Y Y
CS06 C Y N Y Y
En este ejemplo, digamos, CS02 es mi registro maestro, por lo que dado que todas las configuraciones son las mismas en CS02 y CS03, esas filas no aparecen, pero CS06 sí. Pero en mi EXCEPT
Consulta, en realidad no estoy captando UcitionID, así que en realidad no sé qué fila fue devuelta.
Esto devuelve las filas que necesito pero no las locationID
, así que no sé qué filas están equivocadas. ¿Hay alguna forma de incluir locationID
¿En los resultados establecidos mientras patean las filas a juego?
La solución en la que pensé fue hacer una fila para cada servidor en el table1_master
mesa, entonces cada uno locationID
está representado, pero todos tienen los mismos datos distintos de eso. Mi EXCLUDE
la consulta debe devolver el locationID
Y mi información, pero ¿es esa la mejor manera de hacerlo?
Solución
También puede hacer esto con SQL dinámico sin tener que construir manualmente todos los nombres de la columna.
DECLARE @sql NVARCHAR(MAX), @c1 NVARCHAR(MAX), @c2 NVARCHAR(MAX);
SELECT @c1 = N'', @c2 = N'';
SELECT
@c1 = @c1 + ',' + QUOTENAME(name),
@c2 = @c2 + ' AND m.' + QUOTENAME(name) + ' = s.' + QUOTENAME(name)
FROM sys.columns
WHERE name <> 'LocationID'
AND [object_id] = OBJECT_ID('dbo.table1');
SET @sql = ';WITH s AS (
SELECT ' + STUFF(@c1, 1, 1, '') + ' FROM dbo.table1
EXCEPT
SELECT ' + STUFF(@c1, 1, 1, '') + ' FROM dbo.table1_master
)
SELECT m.LocationID
FROM s INNER JOIN dbo.table1 AS m ON 1 = 1
' + @c2;
SELECT @sql;
--EXEC sp_executesql @sql;
Puede tomar la salida de esta consulta tal como está y almacenar la consulta en algún lugar, o puede comentar el SELECT
y desagradable el EXEC
y deje que sea SQL dinámico permanente: en este caso se adaptará automáticamente a los cambios de columna en las dos tablas.
Otra idea (suponiendo que LocationId sea única), y se me ocurrió, es posible que desee incluir la fila maestra para que pueda detectar rápidamente las columnas que son diferentes:
;WITH c AS
(
SELECT t.LocationID, m.setting1, m.setting2, ...
FROM dbo.table1 AS t CROSS JOIN dbo.table1_master AS m
)
SELECT DISTINCT src = '> master', setting1, setting2, ...
FROM c
UNION ALL
(
SELECT RTRIM(LocationID), setting1, setting2, ...
FROM dbo.table1
EXCEPT
SELECT RTRIM(LocationID), setting1, setting2, ...
FROM c
)
ORDER BY src;
Esta versión es un poco más barata (principalmente evitando el DISTINCT
contra la tabla maestra, a costa de la necesidad de especificar todas las columnas una vez más, que nuevamente puede automatizar según lo anterior):
;WITH m AS
(
SELECT setting1, setting2, ...
FROM dbo.table1_master
),
c AS
(
SELECT src = RTRIM(t.LocationID), m.setting1, m.setting2, ...
FROM dbo.table1 AS t CROSS JOIN m
)
SELECT src = '> master', setting1, setting2, ...
FROM m
UNION ALL
(
SELECT RTRIM(LocationID), setting1, setting2, ...
FROM dbo.table1
EXCEPT
SELECT src, setting1, setting2, ...
FROM c
)
ORDER BY src;
Sin embargo, todas estas opciones tienen un desempeño más pobre con peores planes que Rachel's Simple LEFT JOIN
. Traté de seguir con el tema de usar EXCEPT
Aunque se trata más de sintaxis que el rendimiento.
La conclusión clave es que si el recuento de columnas es demasiado alto para tratar manualmente, puede usar el enfoque dinámico de SQL anterior para construir cualquier consulta que desee usar, y puede hacerlo una vez y almacenar el resultado, o tener el código generado cada vez. Para generar la consulta de Rachel utilizando SQL dinámico, no debe cambiar mucho:
DECLARE @sql NVARCHAR(MAX), @and NVARCHAR(MAX), @anycol NVARCHAR(128);
SELECT @sql = N'', @and = N'';
SELECT @and = @and + ' AND t.' + QUOTENAME(name) + ' = m.' + QUOTENAME(name)
FROM sys.columns
WHERE [object_id] = OBJECT_ID('dbo.table1_master');
SELECT TOP (1) @anycol = QUOTENAME(name)
FROM sys.columns
WHERE [object_id] = OBJECT_ID('dbo.table1_master')
ORDER BY name;
SET @sql = 'SELECT locationID
FROM dbo.table1 AS t
LEFT OUTER JOIN dbo.table1_master AS m ON 1 = 1'
+ @and + ' WHERE m.' + @anycol + ' IS NULL;';
SELECT @sql;
--EXEC sp_executesql @sql;
Otros consejos
Yo recomendaria:
- Creando un
Hash
campo que es una columna calculada persistida con una definición en la línea deHASHBYTES('SHA1', Field1 + Field2 + Field3...)
- Comparando solo eso
HASH
Valor de su "maestro" a sus otros registros - Mostrar todos los valores reales de filas que no coinciden
Algo como
SELECT *
FROM Table1
WHERE HashField <> (SELECT Hashfield FROM Table1_Master)
¿Qué tiene de malo simplemente unir las dos tablas en cada columna (o usar una declaración Where) y seleccionar elementos que no existan en la segunda tabla?
SELECT locationID
FROM table1
LEFT OUTER JOIN table1_master
ON table1.a = table1_master.a
AND table1.b = table1_master.b
AND table1.c = table1_master.c
WHERE table1_master.a is null
Puede que no sea bonito, pero debería funcionar