Basculer entre les bases de données avec SQL dynamique

8

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 en use [test_db]tant que commande, plutôt que dynamiquement, alors il renvoie le nom correct.

Existe-t-il un moyen de le faire qui permute correctement entre les bases de données?

SeanR
la source

Réponses:

9

Les modifications au niveau de la session effectuées dans un sous-processus (c'est-à-dire EXEC/ sp_executesql) disparaissent à la fin de ce sous-processus. Cela couvre USEet les SETdéclarations ainsi que toutes les tables temporaires locales créées dans ce sous-processus. La création de tables temporaires globales survivra au sous-processus, tout comme les modifications apportées aux tables temporaires locales qui existent avant le démarrage du sous-processus, ainsi que toute modification 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 suivantes qui s'appuient également sur le nouveau contexte de base de données dans ce Dynamic SQL.

Solomon Rutzky
la source
12

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.

Monsieur Magoo
la source
0

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.

Derreck Dean
la source
1
Il existe un moyen encore plus simple. exec OtherDatabase.sys.sp_executesql N'select db_name()'
David Browne - Microsoft
A voté pour votre commentaire car le vôtre est encore plus concis
Derreck Dean
@ DavidBrowne-Microsoft, c'est ce que fait Derreck ici, mais avec "OtherDatabase" passé en paramètre - n'est-ce pas? Ils se retrouvent donc avec OtherDatabase.sys.sp_executesql dans la variable "proc" au lieu d'être codés en dur.
Monsieur Magoo
Eh bien, il a spécifié dynamiquement l'autre base de données. Si vous n'en avez pas besoin, il est plus simple d'appeler directement.
David Browne - Microsoft
J'utilise toujours le mien depuis que je l'utilise pour parcourir un ensemble spécifique de bases de données connexes et y effectuer des actions telles que l'indexation, les sauvegardes, etc. en utilisant les scripts Ola Hallengren que j'ai intégrés dans la base de données `` principale '' de mon application ( pas le maître réel db). Il est bon de savoir que je peux appeler directement une base de données spécifique comme dans son commentaire.
Derreck Dean
0

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

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;
Inconnue
la source