Beralih di antara basis data dengan SQL dinamis

8

Saya memiliki proses yang melibatkan mengeksekusi berbagai perintah antara beberapa database - namun, ketika saya menggunakan SQL dinamis untuk mengubah DB dengan 'use @var', maka itu tidak benar-benar mengubah database.

Menjalankan ini di [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()

Mengembalikan [Master] sebagai nama basis data saat ini - jika saya menempatkan use [test_db]sebagai perintah, bukan secara dinamis, maka ia mengembalikan nama yang benar.

Apakah ada cara untuk melakukan ini yang akan beralih dengan benar di antara basis data?

SeanR
sumber

Jawaban:

9

Perubahan tingkat sesi yang dibuat dalam sub-proses (yaitu EXEC/ sp_executesql) hilang ketika sub-proses itu berakhir. Ini mencakup USEdan SETpernyataan serta tabel sementara lokal yang dibuat dalam sub-proses itu. Pembuatan tabel sementara global akan bertahan dari sub-proses, dan begitu juga modifikasi yang dibuat untuk tabel sementara lokal yang ada sebelum sub-proses dimulai, dan perubahan apa pun untuk CONTEXT_INFO(saya percaya).

Jadi tidak, Anda tidak dapat secara dinamis mengubah database saat ini. Jika Anda perlu melakukan sesuatu seperti ini, Anda harus menjalankan pernyataan berikutnya yang bergantung pada konteks basis data baru juga dalam SQL dinamis itu.

Solomon Rutzky
sumber
12

Tentu, ada jalan - selalu ada jalan ...

Jika Anda mendeklarasikan variabel dan menyimpannya di dalam database serta prosedur untuk menjalankannya, Anda dapat menjalankannya, dengan parameter.

Contoh

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

Sangat sepele untuk kemudian meneruskan kueri dengan parameter yang akan dijalankan di basis data apa pun

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

Saya tahu ini tidak mengubah konteks basis data dalam kueri utama, tetapi ingin menunjukkan bagaimana Anda bisa bekerja dengan mudah di basis data lain dengan cara parameter yang aman tanpa terlalu banyak kesulitan.

Tuan Magoo
sumber
0

Mendasarkan ini pada jawaban @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;

Saya memiliki banyak kegunaan terkait pemeliharaan untuk ini.

Derreck Dean
sumber
1
Ada cara yang bahkan lebih mudah. exec OtherDatabase.sys.sp_executesql N'select db_name()'
David Browne - Microsoft
Mengunggah komentar Anda karena komentar Anda bahkan lebih singkat
Derreck Dean
@ DavidBrowne-Microsoft itulah yang dilakukan Derreck di sini, tetapi dengan "OtherDatabase" disahkan sebagai parameter - bukan? Jadi mereka berakhir dengan OtherDatabase.sys.sp_executesql dalam variabel "proc", bukan hard-coded.
Tuan Magoo
Yah dia punya database lain yang ditentukan secara dinamis. Jika Anda tidak membutuhkannya, maka lebih mudah untuk langsung menelepon.
David Browne - Microsoft
Saya masih menggunakan milik saya karena saya menggunakan ini untuk mengulangi set tertentu dari database terkait dan melakukan tindakan pada mereka seperti pengindeksan, cadangan, dll. Menggunakan skrip Ola Hallengren yang telah saya masukkan ke dalam database 'master' dari aplikasi saya ( bukan master db sebenarnya). Senang mengetahui bahwa saya dapat memanggil langsung ke database tertentu seperti dalam komentarnya.
Derreck Dean
0

Ini juga berfungsi.

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

Belajar dari posting sebelumnya saya sedikit lebih dalam dan terkesan sendiri ...

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;
Tidak diketahui
sumber