J'ai un processus qui implique l'exécution de diverses commandes entre plusieurs bases de données - cependant, lorsque j'utilise SQL dynamique pour changer de base de données avec 'use @var', il ne change pas réellement la base de données.
Exécuter ceci dans [test_db]:
declare @currentDB varchar(max)
declare @sql varchar(max)
set @currentDB = DB_NAME()
set @sql = 'use [' + @currentDB +']'
use master
exec(@sql)
select DB_NAME()
Renvoie [Master] comme nom de base de données actuel - si je mets use [test_db]
en tant que commande, plutôt que dynamiquement, puis il renvoie le nom correct.
Existe-t-il un moyen de le faire qui permute correctement entre les bases de données?
Modifications au niveau de la session effectuées dans un sous-processus (c'est-à-dire EXEC
/sp_executesql
) disparaît lorsque ce sous-processus se termine. Cela couvre les instructions USE
et SET
ainsi que toutes les tables temporaires locales créées dans ce sous-processus. La création de globales tables temporaires survivra au sous-processus, ainsi que les modifications apportées aux tables temporaires locales qui existent avant le démarrage du sous-processus, ainsi que toutes les modifications apportées à CONTEXT_INFO
(Je crois).
Donc non, vous ne pouvez pas modifier dynamiquement la base de données actuelle. Si vous devez faire quelque chose comme ça, vous devrez exécuter toutes les instructions ultérieures qui s'appuient également sur le nouveau contexte de base de données dans ce Dynamic SQL.
Bien sûr, il y a un moyen - il y a toujours un moyen ...
Si vous déclarez une variable et y stockez la base de données et la procédure à exécuter, vous pouvez l'exécuter, avec des paramètres.
Exemple
use tempdb;
select db_name();
declare @db sysname = 'master.sys.sp_executesql';
exec @db N'select db_name()';
set @db = 'msdb.sys.sp_executesql';
exec @db N'select db_name()';
Il est trivial de passer ensuite une requête avec des paramètres à exécuter dans n'importe quelle base de données
declare @proc sysname, @sql nvarchar(max), @params nvarchar(max);
select
@proc = 'ssc.sys.sp_executesql'
, @sql = N'select top 10 name from sys.tables where name like @table order by name;'
, @params = N'@table sysname';
exec @proc @sql, @params, @table = 'Tally%'
Je sais que cela ne change pas le contexte de la base de données dans la requête principale, mais je voulais montrer comment vous pouvez facilement travailler dans une autre base de données d'une manière paramétrée sûre sans trop de peine.
Fonder cela sur la réponse de @Mister Magoo ...
CREATE PROCEDURE dbo.Infrastructure_ExecuteSQL
(
@sql NVARCHAR(MAX),
@dbname NVARCHAR(MAX) = NULL
)
AS BEGIN
/*
PURPOSE
Runs SQL statements in this database or another database.
You can use parameters.
TEST
EXEC dbo.Infrastructure_ExecuteSQL 'SELECT @@version, db_name();', 'master';
REVISION HISTORY
20180803 DKD
Created
*/
/* For testing.
DECLARE @sql NVARCHAR(MAX) = 'SELECT @@version, db_name();';
DECLARE @dbname NVARCHAR(MAX) = 'msdb';
--*/
DECLARE @proc NVARCHAR(MAX) = 'sys.sp_executeSQL';
IF (@dbname IS NOT NULL) SET @proc = @dbname + '.' + @proc;
EXEC @proc @sql;
END;
J'ai beaucoup d'utilisations liées à la maintenance pour cela.
En apprenant du post précédent, je suis allé un peu plus loin et je me suis impressionné ...
DECLARE @Debug BIT = 1
DECLARE @NameOfDb NVARCHAR(200) = DB_NAME()
DECLARE @tsql NVARCHAR(4000) = ''
IF OBJECT_ID('Tempdb.dbo.#tbl001') IS NOT NULL DROP TABLE #tbl001
CREATE TABLE #tbl001(
NameOfDb VARCHAR(111))
INSERT INTO #tbl001(NameOfDb)
VALUES('db1'),('db2'),('db3'),('db4')
SET @tsql = N'
DECLARE @sql nvarchar(max)
set @sql = N''
;WITH a AS (
SELECT NumOf = COUNT(*),
c.Field1,
c.Field2,
c.Field3
FROM ''+@NameOfDb2+''.dbo.TBLname c
WHERE Field3 = ''''TOP SECRET''''
GROUP BY
c.Field1,
c.Field2,
c.Field3
HAVING COUNT(*)>1
)
SELECT a.NumOf, c.*
FROM ''+@NameOfDb2+''.dbo.TBLname c
JOIN a ON c.Field1=a.Field1 AND c.Field2=a.Field2 AND c.Field3=a.Field3''
exec (@sql)
'
DECLARE SmplCrsr CURSOR STATIC LOCAL FORWARD_ONLY READ_ONLY FOR
SELECT * FROM #tbl001
OPEN SmplCrsr;
FETCH NEXT FROM SmplCrsr
INTO @NameOfDb
WHILE @@Fetch_Status=0
BEGIN
IF (@Debug = 1)
BEGIN
EXEC sys.sp_executesql @tsql,N'@NameOfDb2 varchar(111)',@NameOfDb
END
ELSE
BEGIN
PRINT @tsql + '-- DEBUG OFF'
END
FETCH NEXT FROM SmplCrsr
INTO @NameOfDb
END
CLOSE SmplCrsr;
DEALLOCATE SmplCrsr;
Cela fonctionne aussi.
declare @Sql nvarchar(max),@DatabaseName varchar(128)
set @DatabaseName = 'TestDB'
set @Sql = N'
declare @Sql nvarchar(max) = ''use ''+@DatabaseName
set @Sql = @Sql +''
select db_name()
''
exec (@Sql)
'
exec sp_executesql @Sql,N'@DatabaseName varchar(128)',@DatabaseName