SQL Server consente (rendere visibile) DDL all'interno di una transazione alla transazione prima del commit?


9

In PostgreSQL posso creare una tabella con alcuni dati di test, e quindi in una transazione migrarla in una nuova colonna di un tipo diverso con conseguente riscrittura di una tabella COMMIT,

CREATE TABLE foo ( a int );
INSERT INTO foo VALUES (1),(2),(3);

Seguito da,

BEGIN;
  ALTER TABLE foo ADD COLUMN b varchar;
  UPDATE foo SET b = CAST(a AS varchar);
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

Tuttavia, la stessa cosa in SQL Server di Microsoft sembra generare un errore. Confronta questo violino db funzionante , dove il ADDcomando (colonna) è al di fuori della transazione,

-- txn1
BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
COMMIT;

-- txn2
BEGIN TRANSACTION;
  UPDATE foo SET b = CAST( a AS varchar );
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

a questo violino db che non funziona,

-- txn1
BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
  UPDATE foo SET b = CAST( a AS varchar );
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

Ma invece errori

Msg 207 Level 16 State 1 Line 2
Invalid column name 'b'.

Esiste un modo per rendere visibile questa transazione, per quanto riguarda DDL, comportarsi come PostgreSQL?

Risposte:


17

In generale, no. SQL Server compila l'intero batch nell'ambito corrente prima dell'esecuzione, quindi devono esistere entità referenziate (le ricompilazioni a livello di istruzione possono anche avvenire in seguito). L'eccezione principale è la risoluzione dei nomi posticipata, ma si applica alle tabelle, non alle colonne:

La risoluzione dei nomi posticipata può essere utilizzata solo quando si fa riferimento a oggetti di tabella inesistenti. Tutti gli altri oggetti devono esistere al momento della creazione della procedura memorizzata. Ad esempio, quando si fa riferimento a una tabella esistente in una stored procedure non è possibile elencare colonne inesistenti per quella tabella.

Soluzioni alternative comuni riguardano il codice dinamico (come nella risposta di Joe ) o la separazione di DML e DDL in lotti separati.

Per questo caso specifico puoi anche scrivere:

BEGIN TRANSACTION;

    ALTER TABLE dbo.foo
        ALTER COLUMN a varchar(11) NOT NULL
        WITH (ONLINE = ON);

    EXECUTE sys.sp_rename
        @objname = N'dbo.foo.a',
        @newname = N'b',
        @objtype = 'COLUMN';

COMMIT TRANSACTION;

Non sarà ancora possibile accedere alla colonna rinominata bnello stesso batch e ambito, ma il lavoro viene svolto.

Per quanto riguarda SQL Server, esiste una scuola di pensiero che afferma che mescolare DDL e DML in una transazione non è una grande idea. Ci sono stati bug in passato in cui ciò ha comportato una registrazione errata e un database irrecuperabile. Tuttavia, le persone lo fanno, soprattutto con tavoli temporanei. Può risultare in un codice abbastanza difficile da seguire.


12

È questo quello che stai cercando?

BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
  EXEC sp_executesql N'UPDATE foo SET b = CAST( a AS varchar )';
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

2

Alla dichiarazione "generalmente no" sulla risposta di Paul White, spero che quanto segue offra una risposta diretta alla domanda ma serva anche a mostrare i limiti sistemici di tale processo e ti allontana da metodi che non si prestano a una facile gestione ed esporre rischi.

Si può menzionare più volte di non apportare modifiche al DDL nello stesso momento in cui si esegue DML. Una buona programmazione separa queste funzioni per mantenere la sostenibilità ed evitare cambiamenti di stringatura degli spaghetti.

E come Paul ha brevemente sottolineato, SQL Server funziona in batch .

Ora, per coloro che dubitano che funzioni, probabilmente non funziona sulla tua istanza ma alcune versioni come 2017 possono effettivamente funzionare! Ecco la prova: inserisci qui la descrizione dell'immagine

[CODICE DI PROVA - Potrebbe non funzionare su molte versioni di SQL Server]

USE master
GO
CREATE TABLE foo (a VARCHAR(11) )
GO
BEGIN TRANSACTION;
    INSERT INTO dbo.foo (a)
    VALUES ('entry')
/*****
[2] Check Values
*****/
    SELECT a FROM dbo.foo
/*****
[3] Add Column
*****/
    ALTER TABLE dbo.foo
        ADD b VARCHAR(11)
/*****
[3] Insert value into this new column in the same batch
-- Again, this is just an example. Please do not do this in production
*****/
    IF EXISTS (SELECT * FROM sys.columns WHERE object_ID('foo') = object_id
            AND name = 'b')
        INSERT INTO dbo.foo (b)
        VALUES ('d')
COMMIT TRANSACTION;
/*****
[4] SELECT outside transaction
-- this will fail
*****/
    --IF EXISTS (SELECT * FROM sys.columns WHERE object_ID('foo') = object_id
    --      AND name = 'b')
    --  SELECT b FROM dbo.foo
-- this will work...but a SELECT * ???
IF EXISTS (SELECT * FROM sys.columns WHERE object_ID('foo') = object_id
            AND name = 'b')
        SELECT * FROM dbo.foo

DROP TABLE dbo.foo

[CONCLUSIONE]

Quindi sì, è possibile eseguire DDL e DML nello stesso batch per determinate versioni o patch di SQL Server come @AndriyM - sottolinea dbfiddle su SQL 2017 , ma non tutto il DML è supportato e non vi è alcuna garanzia che ciò accadrà sempre. Se funziona, questa potrebbe essere un'aberrazione della tua versione di SQL Server e questo potrebbe causare problemi drammatici durante la patch o la migrazione a nuove versioni.

  • Inoltre, in generale il tuo design dovrebbe anticipare le modifiche. Comprendo che le preoccupazioni di modificare / aggiungere colonne possono avere su una tabella, ma è possibile progettare correttamente questo in batch.

[CREDITO EXTRA]

Per quanto riguarda l'istruzione EXISTS, come affermato da Paul, ci sono molti altri mezzi per convalidare il codice prima di passare al passaggio successivo nel codice.

  • L'istruzione EXISTS può aiutarti a creare codice che funziona su tutte le versioni di SQL Server
  • È una funzione booleana che consente controlli complessi in un'istruzione

No, non puoi inserirlo nella nuova colonna se lo stai facendo nello stesso batch in cui stai creando la colonna. Più in generale, non è possibile fare riferimento staticamente alle nuove colonne nello stesso batch dopo averle appena create. Il trucco IF EXISTS non funziona in questo caso. Richiamare il DML in modo dinamico o farlo in un altro batch.
Andriy M,

@AndriyM mi dispiace, ha erroneamente fatto una dichiarazione su dbfiddle. Ma hai provato questo sulla tua istanza? Funziona su SP1 2017. Caricherò una gif, ma l'hai testata sui tuoi sistemi?
clifton_h

i.imgur.com/fhAC7lB.png Puoi effettivamente dire che non verrà compilato in base alla linea ondulata sotto nell'istruzioneb insert. Sto usando SQL Server 2014
Andriy M

@AndriyM interessante. Avevo già visto questo effetto funzionare prima e sembra funzionare su alcune versioni di SQL Server, come SQL Server 2017 di cui ho parlato.
clifton_h

@AndriyM guarda la nuova modifica da pubblicare
clifton_h
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.