動的SQLを使用したデータベース間の切り替え


8

複数のデータベース間でさまざまなコマンドを実行するプロセスがありますが、動的SQLを使用して 'use @var'でDBを変更しても、実際にはデータベースは変更されません。

これを[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()

[マスター]を現在のデータベース名として返します- use [test_db]動的ではなくコマンドとして入力すると、正しい名前が返されます。

これを実行してデータベースを正しく切り替える方法はありますか?

回答:


9

サブプロセス(つまりEXEC/ sp_executesql)で行われたセッションレベルの変更は、そのサブプロセスが終了すると消えます。これにはUSESETステートメント、およびそのサブプロセスで作成されたローカル一時テーブルが含まれます。作成グローバル・テンポラリ・テーブルは、サブプロセスを生き残っていくだろう、とその修正がサブプロセスの開始前に存在してローカル・テンポラリ・テーブルに行われ、への変更CONTEXT_INFO(私は信じています)。

したがって、現在のデータベースを動的に変更することはできません。このようなことを行う必要がある場合は、その動的SQL内でも、新しいデータベースコンテキストに依存する後続のステートメントを実行する必要があります。


12

確かに、方法があります-常に方法があります...

変数を宣言し、それにデータベースと実行するプロシージャを格納すると、パラメーターを使用して実行できます。

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

次に、任意のデータベースで実行されるパラメーターを使用してクエリを渡すことは簡単です

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

これはメインクエリのデータベースコンテキストを変更しないことは知っていますが、あまり気にせずに、安全なパラメーター化された方法で別のデータベースを簡単に操作する方法を示す必要がありました。


0

@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;

これには、メンテナンス関連の用途がたくさんあります。


1
さらに簡単な方法があります。 exec OtherDatabase.sys.sp_executesql N'select db_name()'
デビッドブラウン-マイクロソフト、

あなたのコメントはより簡潔なので、コメントに賛成しました
Derreck Dean

@ DavidBrowne-MicrosoftそれがDerreckがここで行っていることですが、パラメーターとして "OtherDatabase"が渡されていますね。そのため、ハードコードされているのではなく、「proc」変数にOtherDatabase.sys.sp_executesqlが含まれています。
Mister Magoo

まあ彼は動的に指定された他のデータベースを持っています。それが必要ない場合は、直接呼び出すだけの方が簡単です。
David Browne-マイクロソフト、

アプリの「マスター」データベースに焼き付けたOla Hallengrenスクリプトを使用して、関連するデータベースの特定のセットをループし、インデックス作成、バックアップなどのアクションを実行するためにこれを使用しているので、私はまだ私のものを使用しています(実際のマスターデータベースではありません)。彼のコメントのように、特定のデータベースを直接呼び出すことができることを知っておくのは良いことです。
デレックディーン2018

0

これも機能します。

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

前の投稿から学んだことで、もう少し深く感動しました。

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;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.