Erlaubt SQL Server DDL innerhalb einer Transaktion für die Transaktion vor dem Festschreiben (sichtbar zu machen)?

9

In PostgreSQL kann ich eine Tabelle mit einigen Testdaten erstellen und diese dann in einer Transaktion in eine neue Spalte eines anderen Typs migrieren, was zu einem erneuten Schreiben der Tabelle COMMITführt.

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

Gefolgt von,

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

Dasselbe in Microsoft SQL Server scheint jedoch einen Fehler zu generieren. Vergleichen Sie diese funktionierende DB-Geige , bei der sich der ADDBefehl (Spalte) außerhalb der Transaktion befindet.

-- 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;

zu dieser db Geige, die nicht funktioniert,

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

Aber stattdessen Fehler

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

Gibt es überhaupt eine Möglichkeit, diese Transaktion in Bezug auf DDL sichtbar zu machen und sich wie PostgreSQL zu verhalten?

Evan Carroll
quelle

Antworten:

17

Im Allgemeinen nein. SQL Server kompiliert den gesamten Stapel vor der Ausführung im aktuellen Bereich, sodass referenzierte Entitäten vorhanden sein müssen (Neukompilierungen auf Anweisungsebene können auch später erfolgen). Die Hauptausnahme ist die Auflösung des verzögerten Namens. Dies gilt jedoch für Tabellen und nicht für Spalten:

Die verzögerte Namensauflösung kann nur verwendet werden, wenn Sie auf nicht vorhandene Tabellenobjekte verweisen. Alle anderen Objekte müssen zum Zeitpunkt der Erstellung der gespeicherten Prozedur vorhanden sein. Wenn Sie beispielsweise auf eine vorhandene Tabelle in einer gespeicherten Prozedur verweisen, können Sie keine nicht vorhandenen Spalten für diese Tabelle auflisten.

Häufige Problemumgehungen umfassen dynamischen Code (wie in Joes Antwort ) oder die Trennung von DML und DDL in separate Stapel.

Für diesen speziellen Fall könnten Sie auch schreiben:

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;

Sie können immer noch nicht auf die umbenannte Spalte bim selben Stapel und Bereich zugreifen , aber die Aufgabe wird erledigt.

In Bezug auf SQL Server gibt es eine Denkschule, die besagt, dass das Mischen von DDL und DML in einer Transaktion keine gute Idee ist. In der Vergangenheit gab es Fehler, bei denen dies zu einer falschen Protokollierung und einer nicht wiederherstellbaren Datenbank führte. Trotzdem machen es die Leute, besonders mit temporären Tischen. Dies kann zu einem ziemlich schwer zu verfolgenden Code führen.

Paul White 9
quelle
12

Ist es das, wonach du suchst?

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;
Joe Obbish
quelle
2

Auf die "allgemein nein" -Statement zu Paul Whites Antwort bietet das Folgende hoffentlich eine direkte Antwort auf die Frage, dient aber auch dazu, die systemischen Einschränkungen eines solchen Prozesses aufzuzeigen und Sie von Methoden fernzuhalten, die sich nicht für eine einfache Verwaltung und Aufdeckung eignen Risiken.

Es kann oft erwähnt werden, dass DDL-Änderungen nicht gleichzeitig mit DML vorgenommen werden. Eine gute Programmierung trennt diese Funktionen, um die Unterstützbarkeit aufrechtzuerhalten und Änderungen der Spaghetti-Saiten zu vermeiden.

Und wie Paul kurz und bündig hervorhob, arbeitet SQL Server in Stapeln .

Nun, für diejenigen, die bezweifeln, dass dies funktioniert, funktioniert es wahrscheinlich nicht auf Ihrer Instanz, aber einige Versionen wie 2017 können tatsächlich funktionieren! Hier ist der Beweis: Geben Sie hier die Bildbeschreibung ein

[TESTCODE - Funktioniert möglicherweise nicht mit vielen Versionen von 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

[FAZIT]

Ja, Sie können DDL und DML für bestimmte Versionen oder Patches von SQL Server im selben Stapel ausführen, wie @AndriyM - dbfiddle unter SQL 2017 hervorhebt , aber nicht alle DMLs werden unterstützt, und es gibt keine Garantie dafür, dass dies immer der Fall ist. Wenn dies funktioniert, kann dies eine Abweichung von Ihrer SQL Server-Version sein. Dies kann zu dramatischen Problemen führen, wenn Sie patchen oder auf neue Versionen migrieren.

  • Außerdem sollte Ihr Design im Allgemeinen Änderungen vorwegnehmen. Ich verstehe die Bedenken, die das Ändern / Hinzufügen von Spalten in einer Tabelle haben kann, aber Sie können dies in Stapeln richtig gestalten.

[ZUSÄTZLICHES KREDIT]

Wie Paul bereits sagte, gibt es für die EXISTS-Anweisung viele andere Möglichkeiten, den Code zu validieren, bevor Sie mit dem nächsten Schritt in Ihrem Code fortfahren.

  • Mit der EXISTS-Anweisung können Sie Code erstellen, der auf allen Versionen von SQL Server funktioniert
  • Es ist eine boolesche Funktion, die komplexe Überprüfungen in einer Anweisung ermöglicht
clifton_h
quelle
Nein, Sie können nicht in die neue Spalte einfügen, wenn Sie dies in demselben Stapel tun, in dem Sie die Spalte erstellen. Im Allgemeinen können Sie die neuen Spalten im selben Stapel nicht statisch referenzieren, nachdem Sie sie gerade erstellt haben. Der Trick IF EXISTS funktioniert in diesem Fall nicht. Rufen Sie die DML entweder dynamisch auf oder führen Sie sie in einem anderen Stapel aus.
Andriy M
@AndriyM sorry, habe falsch eine Aussage über dbfiddle gemacht. Aber haben Sie dies auf Ihrer Instanz versucht? Es funktioniert auf 2017 SP1. Ich werde ein GIF hochladen, aber haben Sie dies auf Ihren Systemen getestet?
clifton_h
i.imgur.com/fhAC7lB.png Sie können tatsächlich feststellen, dass es nicht basierend auf der Wellenlinie unter bin der insert-Anweisung kompiliert wird . Ich verwende SQL Server 2014
Andriy M.
@AndriyM interessant. Ich hatte zuvor gesehen, dass dieser Effekt funktioniert, und er scheint auf einigen Versionen von SQL Server zu funktionieren, wie dem von mir erwähnten SQL Server 2017.
clifton_h
@AndriyM sehen neue Bearbeitung zu posten
clifton_h