Identificare le righe che non corrispondono fila maestro
-
22-10-2019 - |
Domanda
sto confrontando un gruppo di tabelle da database diversi su diversi server a un record master. Ho bisogno di sapere quali server, identificati da locationID
, hanno le righe non corrispondenti perché potrebbero avere bisogno di manutenzione.
Ho una semplice query EXCEPT
in cui mi confronto una tabella in cui ogni riga è la configurazione da ogni server; table1
ha una riga per server con tutte le configurazioni più locationID
che è una colonna che mi dice quale server è. Io paragono questi tutti a un tavolo table1_master
che ha le impostazioni corrette, ma escludo la locationID
dal momento che non corrisponderà.
semplice query di seguito:
SELECT everything, but, locationID
FROM table1
EXCEPT
SELECT everything, but, locationID
FROM table1_master
Non solo è una di fila maestro mi confronto tutti i server a, e non seleziono di locationID
qui.
Questo è un esempio delle righe che sto confronto. Ognuno ha una chiave primaria, un singolo varchar
colonna e una lista gigante che è decine di colonne. Voglio mettere a confronto tutte le colonne tranne LocationID, ma ho bisogno LocationID per identificare le righe.
LocationID setting setting setting setting
CS02 C Y Y Y Y
CS03 C Y Y Y Y
CS06 C Y N Y Y
In questo esempio diciamo CS02 è il mio record Maestro, così dal momento che tutte le impostazioni sono gli stessi in CS02 e CS03, le righe non si presentano, ma CS06 di fa. Ma nella mia interrogazione EXCEPT
, non sto in realtà cattura LocationID così Io in realtà non so quale riga è stata restituita.
Questo restituisce le righe di cui ho bisogno, ma non il locationID
, quindi non so quali righe sono sbagliate. C'è un modo posso includere locationID
nei risultati esposti, mentre a calci fuori le righe corrispondenti?
La soluzione che ho pensato è stato quello di fare una riga per ogni server nella tabella table1_master
, in modo che ogni locationID
è rappresentato, ma tutti hanno gli stessi dati diversi da quelli. La mia domanda EXCLUDE
dovrebbe quindi restituire il locationID
e le mie informazioni, ma è che il modo migliore per farlo?
Soluzione
Si può anche fare questo con SQL dinamico senza dover costruire manualmente tutti i nomi delle colonne.
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;
Si può prendere l'output di questa query come è e memorizzare il posto di query, oppure è possibile commentare la SELECT
e rimuovere il commento il EXEC
e lasciare come SQL dinamico permanente - in questo caso sarà automaticamente adattarsi ai cambiamenti delle colonne della due tabelle.
Un'altra idea (supponendo LocationID è unico) - e mi venne in mente che si potrebbe voler includere la fila maestro in modo da poter individuare rapidamente le colonne che sono diversi:
;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;
Questa versione è un po 'meno (per lo più evitando la DISTINCT
contro la tabella master, a costo di dover specificare tutte le colonne ancora una volta - che ancora una volta è possibile automatizzare di cui al precedente):
;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;
Tuttavia tutte queste opzioni sono esecutori più poveri con piani peggiori di semplice LEFT JOIN
di Rachel. Ho cercato di attenersi al tema del usando EXCEPT
, anche se è più sulla sintassi di prestazioni.
L'asporto chiave è che se il numero di colonne è troppo alto per affrontare manualmente, è possibile utilizzare l'approccio dinamico SQL sopra per costruire qualunque ricerca che si desidera utilizzare - e si può fare una volta e memorizzare il risultato, o avere il codice generato ogni volta. Per generare domanda di Rachel utilizzando SQL dinamico, non molto deve cambiare:
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;
Altri suggerimenti
mi raccomando:
- Creazione di un campo
Hash
che è un persisteva colonna calcolata con una definizione sulla falsariga diHASHBYTES('SHA1', Field1 + Field2 + Field3...)
- Confrontando solo che il valore
HASH
dal "master" per gli altri record - Visualizzazione tutti i valori reali righe non corrispondenti
Qualcosa di simile
SELECT *
FROM Table1
WHERE HashField <> (SELECT Hashfield FROM Table1_Master)
Cosa c'è di sbagliato con solo unendo le due tabelle su ogni colonna (o utilizzando una dichiarazione dove) e selezione delle voci che non esistono nel 2 ° tavola?
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
Potrebbe non essere abbastanza, ma dovrebbe funzionare