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?

¿Fue útil?

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 de HASHBYTES('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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a dba.stackexchange
scroll top