Cambiar entre bases de datos con SQL dinámico

8

Tengo un proceso que implica ejecutar varios comandos entre varias bases de datos; sin embargo, cuando uso SQL dinámico para cambiar la base de datos con 'use @var', entonces en realidad no cambia la base de datos.

Ejecutando esto en [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()

Devuelve [Maestro] como el nombre de la base de datos actual: si pongo use [test_db]como comando, en lugar de dinámicamente, devuelve el nombre correcto.

¿Hay alguna manera de hacer esto que cambie correctamente entre bases de datos?

SeanR
fuente

Respuestas:

9

Los cambios a nivel de sesión realizados en un subproceso (es decir, EXEC/ sp_executesql) desaparecen cuando finaliza ese subproceso. Esto cubre USEy SETdeclaraciones, así como cualquier tabla temporal local creada en ese subproceso. La creación de tablas temporales globales sobrevivirá al subproceso, y también las modificaciones realizadas a las tablas temporales locales que existen antes del inicio del subproceso, y cualquier cambio en CONTEXT_INFO(creo).

Entonces no, no puede cambiar dinámicamente la base de datos actual. Si necesita hacer algo como esto, deberá ejecutar cualquier declaración posterior que se base en el nuevo contexto de la base de datos también dentro de ese SQL dinámico.

Solomon Rutzky
fuente
12

Claro, hay un camino, siempre hay un camino ...

Si declara variable y almacena en ella la base de datos y el procedimiento a ejecutar, puede ejecutarla con parámetros.

Ejemplo

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 trivial pasar una consulta con los parámetros que se ejecutarán en cualquier base de datos.

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

Sé que esto no cambia el contexto de la base de datos en la consulta principal, pero quería demostrar cómo puede trabajar convenientemente en otra base de datos de forma segura y parametrizada sin demasiada molestia.

Señor magoo
fuente
0

Basando esto en la respuesta de @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;

Tengo muchos usos relacionados con el mantenimiento para esto.

Derreck Dean
fuente
1
Hay una manera aún más fácil. exec OtherDatabase.sys.sp_executesql N'select db_name()'
David Browne - Microsoft
Upvoted su comentario ya que la suya es aún más conciso
Derreck Dean
@ DavidBrowne-Microsoft, eso es lo que Derreck está haciendo aquí, pero con "OtherDatabase" pasado como parámetro, ¿no? Por lo tanto, terminan con OtherDatabase.sys.sp_executesql en la variable "proc" en lugar de codificada.
Señor Magoo
Bueno, tiene la otra base de datos especificada dinámicamente. Si no necesita eso, entonces es más simple llamar directamente.
David Browne - Microsoft
Todavía estoy usando el mío, ya que lo uso para recorrer un conjunto específico de bases de datos relacionadas y realizar acciones en ellas, como indexación, copias de seguridad, etc., usando los scripts de Ola Hallengren que he incluido en la base de datos 'maestra' de mi aplicación ( no el maestro real db). Es bueno saber que puedo llamar a una base de datos específica directamente como en su comentario.
Derreck Dean el
0

Esto tambien funciona.

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

Al aprender de la publicación anterior, fui un poco más profundo y me impresioné ...

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;
Desconocido
fuente