web-dev-qa-db-ja.com

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

複数のデータベース間でさまざまなコマンドを実行するプロセスがありますが、動的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()

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

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

8
SeanR

サブプロセスで行われたセッションレベルの変更(つまり、EXEC/sp_executesql)そのサブプロセスが終了したら終了します。これには、USEおよびSETステートメント、およびそのサブプロセスで作成されたlocal一時テーブルが含まれます。 global一時テーブルの作成はサブプロセスを存続します。したがって、サブプロセスの開始前に存在するローカル一時テーブルに加えられた変更、およびCONTEXT_INFO (私は信じている)。

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

9
Solomon Rutzky

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

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

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

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

12
Mister Magoo

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

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

0
Derreck Dean

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

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;
0
UnKnown

これも機能します。

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
ASG