Переключение между базами данных с динамическим SQL

8

У меня есть процесс, который включает в себя выполнение различных команд между несколькими базами данных - однако, когда я использую динамический SQL для изменения БД с помощью 'use @var', тогда он фактически не меняет базу данных.

Выполнение этого в [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]в качестве команды, а не динамически, то он вернет правильное имя.

Есть ли способ сделать это, который будет правильно переключаться между базами данных?

SeanR
источник

Ответы:

9

Изменения на уровне сеанса, сделанные в подпроцессе (т.е. EXEC/ sp_executesql), исчезают, когда этот подпроцесс завершается. Это касается USEи SETоператоров, а также любых локальных временных таблиц, созданных в этом подпроцессе. Создание глобальных временных таблиц переживет подпроцесс, как и изменения, внесенные в локальные временные таблицы, которые существовали до запуска подпроцесса, и любые изменения 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()'
Дэвид Браун - Microsoft
Upvoted ваш комментарий, так как ваш еще более лаконичен
Дерек Дин
@ DavidBrowne-Microsoft это то, что здесь делает Дерек, но с параметром «OtherDatabase», передаваемым в качестве параметра - не так ли? Таким образом, они заканчиваются с OtherDatabase.sys.sp_executesql в переменной «proc» вместо жестко запрограммированного.
Мистер Магу,
Ну, у него есть другая база данных, указанная динамически. Если вам это не нужно, проще позвонить напрямую.
Дэвид Браун - Microsoft
Я все еще использую свою, поскольку я использую это для циклического прохождения определенного набора связанных баз данных и выполнения над ними таких действий, как индексация, резервное копирование и т. Д. С использованием сценариев Ola Hallengren, которые я встроил в базу данных «master» моего приложения ( не фактический мастер БД). Приятно знать, что я могу обратиться к конкретной базе данных напрямую, как в его комментарии.
Дерек Дин
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
ASG
источник
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;
неизвестный
источник