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 résultant en une table- réécrire sur 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, cette même chose dans SQL Server de Microsoft semble générer une erreur. Comparez cette fonction db fiddle , où la commande ADD
(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 db fiddle 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?
De manière générale, non. SQL Server compile l'ensemble du lot à la portée 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 Résolution de nom différée mais cela 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 Joe's answer ), 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 b
dans 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.
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;
À 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 pour ne pas apporter de modifications DDL en même temps que vous faites 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 en 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! Voici la preuve:
[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]
Alors oui, vous pouvez effectuer DDL et DML dans le même lot pour certaines versions ou correctifs de SQL Server comme @ AndriyM - dbfiddle sur SQL 2017 le souligne, mais tous les DML ne sont pas pris en charge et il n'y a aucune garantie que cela 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.
[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.