Passaggio da un database all'altro con SQL dinamico


8

Ho un processo che prevede l'esecuzione di vari comandi tra più database - tuttavia, quando utilizzo SQL dinamico per cambiare DB con 'use @var', non cambia effettivamente il database.

Eseguendo questo in [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()

Restituisce [Master] come nome del database corrente - se inserisco use [test_db]un comando, anziché in modo dinamico, restituisce il nome corretto.

C'è un modo per farlo che passerà correttamente tra i database?

Risposte:


9

Le modifiche a livello di sessione apportate in un processo secondario (ovvero EXEC/ sp_executesql) scompaiono al termine del processo secondario. Questo copre USEe le SETdichiarazioni, nonché tutte le tabelle temporanee locali create in tale processo secondario. La creazione di tabelle temporanee globali sopravviverà al processo secondario, così come le modifiche apportate alle tabelle temporanee locali esistenti prima dell'inizio del processo secondario e qualsiasi modifica a CONTEXT_INFO(credo).

Quindi no, non è possibile modificare dinamicamente il database corrente. Se devi fare qualcosa del genere, dovrai eseguire tutte le istruzioni successive che si basano sul nuovo contesto del database anche all'interno di quel Dynamic SQL.


12

Certo, c'è un modo - c'è sempre un modo ...

Se si dichiara variabile e si memorizza in esso il database e la procedura da eseguire, è possibile eseguirlo, con parametri.

Esempio

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

È banale quindi passare una query con parametri da eseguire in qualsiasi database

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

So che questo non cambia il contesto del database nella query principale, ma volevo dimostrare come lavorare comodamente in un altro database in modo parametrizzato sicuro senza troppi problemi.


0

Basandomi sulla risposta di @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;

Per questo ho molti usi legati alla manutenzione.


1
C'è un modo ancora più semplice. exec OtherDatabase.sys.sp_executesql N'select db_name()'
David Browne - Microsoft

Ha valutato il tuo commento poiché il tuo è ancora più conciso
Derreck Dean il

@ DavidBrowne-Microsoft, ecco cosa sta facendo Derreck, ma con "OtherDatabase" passato come parametro - non è vero? Quindi finiscono con OtherDatabase.sys.sp_executesql nella variabile "proc" invece che hardcoded.
Mago Magoo,

Bene, ha l'altro database specificato dinamicamente. Se non ti serve, è più semplice chiamare direttamente.
David Browne - Microsoft

Sto ancora usando il mio poiché lo uso per scorrere un set specifico di database correlati ed eseguire azioni su di essi come indicizzazione, backup, ecc. Usando gli script di Ola Hallengren che ho inserito nel database "master" della mia app ( non l'attuale master db). È bene sapere che posso chiamare direttamente un database specifico come nel suo commento.
Derreck Dean,

0

Anche questo funziona.

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

Imparando dal post precedente sono andato un po 'più a fondo e mi sono impressionato ...

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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.