web-dev-qa-db-fra.com

Basculer entre les bases de données avec SQL dynamique

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?

8
SeanR

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.

9
Solomon Rutzky

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.

12
Mister Magoo

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.

0
Derreck Dean

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

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
0
ASG