Creazione di un trigger di SQL Server per la transizione da una chiave naturale per una chiave surrogata
-
30-09-2019 - |
Domanda
Backstory
Al lavoro dove abbiamo intenzione di deprecando una colonna chiave naturale in una delle nostre tabelle primarie. Il progetto si compone di oltre 100 applicazioni che puntano a questa tabella / colonna; 400+ stored procedure che fanno riferimento direttamente questa colonna; e una vasta gamma di tabelle comuni tra queste applicazioni anche Riferimenti questa colonna.
Il Big Bang e ripartire da metodi di lavoro vengono fuori dal quadro. Stiamo andando a deprecare questa colonna una sola applicazione alla volta, certifica le modifiche e passare a quello successivo ... e abbiamo ottenuto un obiettivo bersaglio lungo per rendere questo sforzo pratico.
Il problema che ho è che molte di queste applicazioni hanno condiviso le stored procedure e tabelle. Se ho completamente convertire tutti di tabelle / stored procedure di applicazione Un'applicazione B e C sarà spezzato fino convertito. Questi a loro volta possono rompersi applicazioni D, E, F ... ecc. Ho già una strategia attuata per le classi di codice e stored procedure, la parte mi sono bloccato su è stato transizione del database.
Ecco un base esempio di ciò che abbiamo:
Users
---------------------------
Code varchar(32) natural key
Access
---------------------------
UserCode varchar(32) foreign key
AccessLevel int
E noi stiamo puntando ora solo per lo stato di transizione in questo modo:
Users
---------------------------
Code varchar(32)
Id int surrogate key
Access
---------------------------
UserCode varchar(32)
UserID int foreign key
AccessLevel int
L'idea che è durante le applicazioni non-migrati fase di transizione e le stored procedure saranno ancora in grado di accedere a tutti i dati appropriati e quelli nuovi possono cominciare a spingere per le colonne corrette - Una volta completata la migrazione di tutte le procedure e le applicazioni memorizzate possiamo finalmente eliminare le colonne aggiuntive.
Ho voluto utilizzare i trigger di SQL Server per intercettare automaticamente ogni nuovo inserimento / aggiornamento e di fare qualcosa di simile a quanto segue su ciascuna delle tabelle interessate:
CREATE TRIGGER tr_Access_Sync
ON Access
INSTEAD OF INSERT(, UPDATE)
AS
BEGIN
DIM @code as Varchar(32)
DIM @id as int
SET @code = (SELECT inserted.code FROM inserted)
SET @id = (SELECT inserted.code FROM inserted)
-- This is a migrated application; find the appropriate legacy key
IF @code IS NULL AND @id IS NOT NULL
SELECT Code FROM Users WHERE Users.id = @id
-- This is a legacy application; find the appropriate surrogate key
IF @id IS NULL AND @code IS NOT NULL
SELECT Code FROM Users WHERE Users.id = @id
-- Impossible code:
UPDATE inserted SET inserted.code=@code, inserted.id=@id
END
Domanda
I 2 enormi problemi che sto avendo finora sono:
- Non riesco a fare un "dopo INSERT", perché i vincoli NULL renderanno l'inserto sicuro.
- Il "codice impossibile" che ho citato è come mi piacerebbe delega in modo pulito la query originale; Se la query originale ha x, y, z colonne in esso o semplicemente x, mi Preferirei lo stesso grilletto per fare queste. E se posso aggiungere / eliminare un'altra colonna, mi piacerebbe il grilletto a funzionare.
Chiunque ha un esempio di codice in cui questo potrebbe essere possibile, o addirittura una soluzione alternativa per mantenere queste colonne riempite correttamente quando anche uno solo dei valori viene passato SQL?
Soluzione 3
Dopo aver dormito sul problema, questa sembra essere la soluzione più generica / riutilizzabile potevo venire con all'interno della sintassi SQL. Funziona bene anche se entrambe le colonne hanno un vincolo NOT NULL, anche se non si fa riferimento alla colonna "altro" a tutti nel vostro inserto.
CREATE TRIGGER tr_Access_Sync
ON Access
INSTEAD OF INSERT
AS
BEGIN
/*-- Create a temporary table to modify because "inserted" is read-only */
/*-- "temp" is actually "#temp" but it throws off stackoverflow's syntax highlighting */
SELECT * INTO temp FROM inserted
/*-- If for whatever reason the secondary table has it's own identity column */
/*-- we need to get rid of it from our #temp table to do an Insert later with identities on */
ALTER TABLE temp DROP COLUMN oneToManyIdentity
UPDATE temp
SET
UserCode = ISNULL(UserCode, (SELECT UserCode FROM Users U WHERE U.UserID = temp.UserID)),
UserID = ISNULL(UserID, (SELECT UserID FROM Users U WHERE U.UserCode = temp.UserCode))
INSERT INTO Access SELECT * FROM temp
END
Altri suggerimenti
affare complicato ...
OK, prima di tutto: questo trigger sarà non il lavoro in molte circostanze:
SET @code = (SELECT inserted.code FROM inserted)
SET @id = (SELECT inserted.code FROM inserted)
Il grilletto può essere chiamato con un set di righe nel Inserted
pseudo-table - quale stai andando a raccogliere qui ?? Hai bisogno di scrivere il trigger in modo tale che funzionerà anche quando si ottiene 10 righe della tabella Inserted
. Se un'istruzione SQL inserti 10 righe, il trigger sarà non essere licenziato volte dieci - uno per ogni riga - ma solo una volta ??strong> per l'intera partita - è necessario prendere in conto!
Secondo punto: vorrei provare a rendere i campi IDENTITY
del ID - poi faranno sempre avere un valore - anche per le applicazioni "legacy". Queste applicazioni "vecchi" dovrebbero fornire una chiave eredità, invece - così si dovrebbe andare bene lì. L'unico problema che vedo e non sanno come gestire questi sono gli inserti da un app già convertito - non forniscono una chiave di legacy "vecchio stile", così? In caso contrario - quanto velocemente si fa a bisogno di avere una tale chiave?
Quello che sto pensando sarebbe un "lavoro di pulizia" che avrebbe eseguito sopra il tavolo e ottenere tutte le righe con una chiave NULL legacy e quindi fornire un valore significativo per esso. Rendere questo una stored procedure regolari ed eseguirlo ogni esempio giorno, quattro ore e 30 minuti - qualunque sia adatta alle vostre esigenze. Allora non avere a che fare con i trigger e tutti i limiti che hanno.
Non sarebbe possibile fare delle modifiche dello schema 'bigbang', ma creare le viste sopra la parte superiore di quelle tabelle che 'nascondere' il cambiamento?
Credo che si potrebbe trovare si sta semplicemente mettendo fuori le rotture a un secondo momento: "Stiamo andando a deprecare questa colonna una sola applicazione alla volta" - potrebbe essere la mia ingenuità, ma non riesco a vedere come che sia mai andare al lavoro.
Di certo, un disastro peggiore può verificarsi quando diverse applicazioni stanno facendo le cose in modo diverso?