Alternando entre bancos de dados com SQL dinâmico

8

Eu tenho um processo que envolve a execução de vários comandos entre vários bancos de dados - no entanto, quando eu uso o SQL dinâmico para alterar o banco de dados com 'use @var', ele não altera o banco de dados.

Executando isso em [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()

Retorna [Master] como o nome atual do banco de dados - se eu colocar use [test_db]como comando, em vez de dinamicamente, ele retornará o nome correto.

Existe uma maneira de fazer isso que alternará corretamente entre os bancos de dados?

SeanR
fonte

Respostas:

9

As alterações no nível da sessão feitas em um subprocesso (ie EXEC/ sp_executesql) desaparecem quando esse subprocesso termina. Isso abrange USEe SETdeclarações, bem como quaisquer tabelas temporárias locais criadas nesse subprocesso. A criação de tabelas temporárias globais sobreviverá ao subprocesso, assim como as modificações feitas nas tabelas temporárias locais que existem antes do início do subprocesso e quaisquer alterações em CONTEXT_INFO(acredito).

Portanto, não, você não pode alterar dinamicamente o banco de dados atual. Se você precisar fazer algo assim, precisará executar quaisquer instruções subseqüentes que dependam do novo contexto de banco de dados também dentro desse SQL dinâmico.

Solomon Rutzky
fonte
12

Claro, há um caminho - sempre há um caminho ...

Se você declarar variável e armazenar nele o banco de dados e o procedimento a ser executado, poderá executá-lo com parâmetros.

Exemplo

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()';

É trivial passar uma consulta com parâmetros a serem executados em qualquer banco de dados

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%'

Eu sei que isso não altera o contexto do banco de dados na consulta principal, mas queria demonstrar como você pode trabalhar convenientemente em outro banco de dados de forma segura e parametrizada sem muito incômodo.

Senhor Magoo
fonte
0

Baseando isso na resposta do Sr. 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;

Eu tenho muitos usos relacionados à manutenção para isso.

Derreck Dean
fonte
11
Existe uma maneira ainda mais fácil. exec OtherDatabase.sys.sp_executesql N'select db_name()'
David Browne - Microsoft
Upvoted seu comentário desde a sua é ainda mais concisa
Derreck Dean
@ DavidBrowne-Microsoft é o que Derreck está fazendo aqui, mas com "OtherDatabase" passado como parâmetro - não é? Então eles terminam com OtherDatabase.sys.sp_executesql na variável "proc" em vez de codificados.
Mister Magoo
Bem, ele tem o outro banco de dados especificado dinamicamente. Se você não precisar disso, é mais simples ligar diretamente.
David Browne - Microsoft
Ainda estou usando o meu, pois o uso para percorrer um conjunto específico de bancos de dados relacionados e executar ações neles, como indexação, backups etc. usando os scripts Ola Hallengren que eu inseri no banco de dados 'mestre' do meu aplicativo ( não é o mestre real). É bom saber que posso chamar um banco de dados específico diretamente, como no comentário dele.
Derreck Dean
0

Isso também funciona.

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

Aprendendo com o post anterior, fui um pouco mais fundo e me impressionei ...

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