Est-ce que SQL Server autorise (rend visible) DDL à l'intérieur d'une transaction à la transaction avant de valider?


9

Dans PostgreSQL, je peux créer une table avec des données de test, puis dans une transaction la migrer vers une nouvelle colonne d'un type différent, ce qui entraîne une réécriture de table COMMIT,

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

Suivi par,

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

Cependant, la même chose dans SQL Server de Microsoft semble générer une erreur. Comparez ce violon db fonctionnel , où la ADDcommande (colonne) est en dehors de la transaction,

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

à ce violon db qui ne fonctionne pas,

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

Mais à la place des erreurs

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

Est-il possible de rendre cette transaction visible, en ce qui concerne DDL, se comporte comme PostgreSQL?

Réponses:


17

De manière générale, non. SQL Server compile le lot entier à l'étendue actuelle avant l'exécution, de sorte que les entités référencées doivent exister (des recompilations au niveau de l'instruction peuvent également se produire ultérieurement). La principale exception est la résolution de nom différée mais qui s'applique aux tables, pas aux colonnes:

La résolution de nom différée ne peut être utilisée que lorsque vous référencez des objets de table inexistants. Tous les autres objets doivent exister au moment de la création de la procédure stockée. Par exemple, lorsque vous référencez une table existante dans une procédure stockée, vous ne pouvez pas répertorier les colonnes inexistantes pour cette table.

Les solutions de contournement courantes impliquent le code dynamique (comme dans la réponse de Joe ), ou la séparation du DML et du DDL en lots séparés.

Pour ce cas spécifique, vous pouvez également écrire:

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;

Vous ne pourrez toujours pas accéder à la colonne renommée bdans le même lot et la même portée, mais cela fait le travail.

En ce qui concerne SQL Server, il existe une école de pensée qui dit que mélanger DDL et DML dans une transaction n'est pas une bonne idée. Il y a eu des bogues dans le passé où cela a entraîné une journalisation incorrecte et une base de données irrécupérable. Néanmoins, les gens le font, surtout avec des tables temporaires. Il peut en résulter un code assez difficile à suivre.


12

C'est ce que vous cherchez?

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

À la déclaration «généralement non» sur la réponse de Paul White, ce qui suit, j'espère, offre une réponse directe à la question, mais sert également à montrer les limites systémiques d'un tel processus et vous éloigne des méthodes qui ne se prêtent pas à une gestion facile et exposent des risques.

Il peut être mentionné plusieurs fois de ne pas effectuer de modifications DDL en même temps que vous effectuez DML. Une bonne programmation sépare ces fonctions pour maintenir la prise en charge et éviter les changements de chaîne de spaghetti.

Et comme Paul l'a souligné succinctement, SQL Server fonctionne par lots .

Maintenant, pour ceux qui doutent que cela fonctionne, cela ne fonctionne probablement pas sur votre instance, mais certaines versions comme 2017, cela peut réellement fonctionner! En voici la preuve: entrez la description de l'image ici

[CODE DE TEST - PEUT ne pas fonctionner sur de nombreuses versions de 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

[CONCLUSION]

Donc, oui, vous pouvez effectuer DDL et DML dans le même lot pour certaines versions ou correctifs de SQL Server comme le souligne @AndriyM - dbfiddle sur SQL 2017 , mais tous les DML ne sont pas pris en charge et il n'y a aucune garantie que ce sera toujours le cas. Si cela fonctionne, cela peut être une aberration de votre version de SQL Server et cela peut provoquer des problèmes dramatiques lorsque vous corrigez ou migrez vers de nouvelles versions.

  • De plus, en général, votre conception doit anticiper les changements. Je comprends que la modification / l'ajout de colonnes puisse avoir sur une table, mais vous pouvez concevoir correctement cela par lots.

[CRÉDIT SUPPLÉMENTAIRE]

En ce qui concerne l'instruction EXISTS, comme Paul l'a déclaré, il existe de nombreux autres moyens de valider le code avant de passer à l'étape suivante de votre code.

  • L'instruction EXISTS peut vous aider à créer du code qui fonctionne sur toutes les versions de SQL Server
  • Il s'agit d'une fonction booléenne qui permet des vérifications complexes dans une seule instruction

Non, vous ne pouvez pas insérer dans la nouvelle colonne si vous effectuez cette opération dans le même lot que celui où vous créez la colonne. Plus généralement, vous ne pouvez pas référencer statiquement les nouvelles colonnes dans le même lot après les avoir créées. L'astuce IF EXISTS ne fonctionne pas dans ce cas. Soit invoquer le DML dynamiquement ou le faire dans un autre lot.
Andriy M

@AndriyM désolé, a incorrectement fait une déclaration sur dbfiddle. Mais avez-vous essayé cela sur votre instance? Il fonctionne sur 2017 SP1. Je vais télécharger un gif, mais avez-vous testé cela sur vos systèmes?
clifton_h

i.imgur.com/fhAC7lB.png Vous pouvez réellement dire qu'il ne compilera pas en fonction de la ligne ondulée sous bdans l'instruction d'insertion. J'utilise SQL Server 2014
Andriy M

@AndriyM intéressant. J'avais déjà vu cela affecter le travail auparavant, et il semble fonctionner sur certaines versions de SQL Server, comme le SQL Server 2017 que j'ai mentionné.
clifton_h

@AndriyM voir une nouvelle modification à publier
clifton_h
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.