Wechseln zwischen Datenbanken mit dynamischem SQL

8

Ich habe einen Prozess, bei dem verschiedene Befehle zwischen mehreren Datenbanken ausgeführt werden. Wenn ich jedoch dynamisches SQL verwende, um die Datenbank mit 'use @var' zu ändern, ändert sich die Datenbank nicht wirklich.

Ausführen 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()

Gibt [Master] als aktuellen Datenbanknamen zurück. Wenn ich use [test_db]nicht dynamisch, sondern als Befehl eingebe, wird der richtige Name zurückgegeben.

Gibt es eine Möglichkeit, die korrekt zwischen Datenbanken wechselt?

SeanR
quelle

Antworten:

9

Änderungen auf Sitzungsebene, die in einem Unterprozess (dh EXEC/ sp_executesql) vorgenommen wurden, verschwinden, wenn dieser Unterprozess endet. Dies umfasst USEund SETAnweisungen sowie alle lokalen temporären Tabellen, die in diesem Unterprozess erstellt wurden. Das Erstellen globaler temporärer Tabellen überlebt den Unterprozess, ebenso wie Änderungen an lokalen temporären Tabellen, die vor dem Start des Unterprozesses vorhanden sind, sowie Änderungen an CONTEXT_INFO(glaube ich).

Nein, Sie können die aktuelle Datenbank nicht dynamisch ändern. Wenn Sie so etwas tun müssen, müssen Sie alle nachfolgenden Anweisungen ausführen, die auf dem neuen Datenbankkontext basieren, auch in diesem dynamischen SQL.

Solomon Rutzky
quelle
12

Klar, es gibt einen Weg - es gibt immer einen Weg ...

Wenn Sie eine Variable deklarieren und die Datenbank und die auszuführende Prozedur darin speichern, können Sie sie mit Parametern ausführen.

Beispiel

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

Es ist trivial, dann eine Abfrage mit Parametern zu übergeben, die in einer beliebigen Datenbank ausgeführt werden sollen

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

Ich weiß, dass dies den Datenbankkontext in der Hauptabfrage nicht ändert, wollte aber zeigen, wie Sie bequem und sicher in einer anderen Datenbank auf sichere, parametrisierte Weise arbeiten können.

Herr Magoo
quelle
0

Basierend auf der Antwort von @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;

Ich habe viele wartungsbezogene Anwendungen dafür.

Derreck Dean
quelle
1
Es gibt einen noch einfacheren Weg. exec OtherDatabase.sys.sp_executesql N'select db_name()'
David Browne - Microsoft
Ihr Kommentar wurde positiv bewertet, da Ihr Kommentar noch knapper ist
Derreck Dean
@ DavidBrowne-Microsoft, genau das macht Derreck hier, aber mit "OtherDatabase" als Parameter - nicht wahr? Sie erhalten also OtherDatabase.sys.sp_executesql in der Variablen "proc" anstatt fest codiert.
Herr Magoo
Nun, er hat die andere Datenbank dynamisch spezifiziert. Wenn Sie das nicht brauchen, ist es einfacher, direkt anzurufen.
David Browne - Microsoft
Ich verwende meine immer noch, da ich diese verwende, um einen bestimmten Satz verwandter Datenbanken zu durchlaufen und Aktionen wie Indexierung, Sicherungen usw. mit den Ola Hallengren-Skripten auszuführen, die ich in die 'Master'-Datenbank meiner App gebacken habe ( nicht der eigentliche Master db). Es ist gut zu wissen, dass ich wie in seinem Kommentar direkt auf eine bestimmte Datenbank zugreifen kann.
Derreck Dean
0

Das funktioniert auch.

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

Als ich aus dem vorherigen Beitrag gelernt habe, bin ich etwas tiefer gegangen und habe mich selbst beeindruckt ...

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;
Unbekannt
quelle