web-dev-qa-db-ja.com

SQL Server:ストアドプロシージャのパラメーターとしてデータベース名を取得する方法

私はsys.tablesテーブルをクエリする単純なストアドプロシージャを作成しようとしています。

CREATE PROCEDURE dbo.test
    @dbname NVARCHAR(255),
    @col NVARCHAR(255)
AS
    SET NOCOUNT ON
    SET XACT_ABORT ON

    USE @dbname

    SELECT TOP 100 *
    FROM sys.tables 
    WHERE name = @col
GO

これは機能していないようですが、USE @dbnameの後にGOを配置する必要がありますが、これによりこのプロシージャの作成が終了しますか?ユーザーがこのプロシージャのパラメータとしてデータベース名を指定できるように、このデータベース選択をこのプロシージャにどのように配置できますか?

15
jjoras

これを行うには、少なくとも2つの方法があります。

  1. Case/switchステートメント(または、この例では、単純なif..elseブロック)を使用して、パラメーターをデータベースのリストと比較し、それに基づいてusingステートメントを実行します。これには、ユーザーアカウントが権限を持つすべてのものへのアクセスを許可するのではなく、プロシージャが既知のセットにアクセスできるデータベースを制限するという利点があります。

    declare @dbname nvarchar(255);    
    set @dbname = 'db1';    
    if @dbname = 'db1'
     use db1;
    else if @dbname = 'db2'
     use db2;
    
  2. 動的SQL。 I HATE動的SQL。これは大きなセキュリティホールであり、ほとんど必要ありません。 (これを全体的に見ると、17年間の専門的な開発の中で、動的SQLを使用する実動システムをデプロイする必要がありませんでした)。この方法を選択する場合は、動的に呼び出されるか作成されるコードをusingステートメントに制限し、別のストアドプロシージャの呼び出しが実際の作業を行います。スコープルールにより、usingステートメントを単独で動的に実行することはできません。

    declare @sql nvarchar(255);
    set @sql = 'using '+@dbname+'; exec mydatabase..do_work_proc;';
    

もちろん、あなたの例では、あなたはただ行うことができます

    set @sql='select * from '+@dbname+'.sys.tables';

.<schema_name>.解決演算子を使用すると、useステートメントを使用せずに別のデータベースのオブジェクトをクエリできます。

Sprocが任意のデータベースを使用できるようにすることが望ましい、非常にまれな状況がいくつかあります。私の意見では、許容できる唯一の使用法は、コードジェネレーター、または事前に必要な情報を知ることができないある種のデータベース分析ツールです。

pdateストアドプロシージャではuseを実行できないことがわかり、動的SQLを唯一の明白な方法として残しています。それでも、私は使用を検討します

select top 100 * from db_name.dbo.table_name

useではなく。

16
3Dave

_EXEC @Var_を使用する場合(かっこなし-notEXEC (@Var))SQL Serverは、_@Var_で渡された名前に一致するストアドプロシージャを探します。これには、3つの部分の名前を使用できます。

_sys.sp_executesql_が3つの部分からなる名前で呼び出された場合、コンテキストはそれが呼び出されたデータベースに設定されます。

したがって、以下のようにzero SQLインジェクションリスクでこれを行うことができます。

_CREATE PROCEDURE dbo.test @dbname SYSNAME,
                          @col    SYSNAME
AS
    SET NOCOUNT, XACT_ABORT ON;

    DECLARE @db_sp_executesql NVARCHAR(300) = QUOTENAME(@dbname) + '.sys.sp_executesql'

    EXEC @db_sp_executesql N'
                            SELECT TOP 100 *
                            FROM sys.columns 
                            WHERE name = @col',
                           N'@col sysname',
                           @col = @col 
_

上記が不可能であったとしても、ここでのように安全な方法でこれに動的SQLを使用することは完全に可能であると私はまだ主張します。

_CREATE PROCEDURE dbo.test
    @dbname SYSNAME, /*Use Correct Datatypes for identifiers*/
    @col SYSNAME
AS
    SET NOCOUNT ON
    SET XACT_ABORT ON

    IF DB_ID(@dbname) IS NULL  /*Validate the database name exists*/
       BEGIN
       RAISERROR('Invalid Database Name passed',16,1)
       RETURN
       END

DECLARE @dynsql nvarchar(max)  

 /*Use QUOTENAME to correctly escape any special characters*/
SET @dynsql = N'USE '+ QUOTENAME(@dbname) + N'

                         SELECT TOP 100 *
                         FROM sys.tables 
                         WHERE name = @col'

 /*Use sp_executesql to leave the WHERE clause parameterised*/
EXEC sp_executesql @dynsql, N'@col sysname', @col = @col
_
26
Martin Smith

これを行う唯一の方法は、強力ですが危険な Dynamic SQL を使用することです。

この記事を最初に読んでください。

1
JNK

同じ目的を達成する別の方法は、システムストアドプロシージャを使用することです。

SQLストアドプロシージャ-複数のデータベースからの実行 を参照してください。

プロシージャ名が "sp_"で始まり、マスターデータベースにあり、sys.sp_MS_MarkSystemObjectでマークされている場合、次のように呼び出すことができます。

Exec somedb.dbo.Test;
Exec anotherdb.dbo.Test;

またはこのように:

Declare @Proc_Name sysname;
Set @Proc_Name = 'somedb.dbo.Test';
Exec @Proc_Name;

パラメータも使用できます。

この手法を使用するには、「sp_」プレフィックスを使用し、システムデータベースにコードを配置する必要があります。動的SQLを使用しないオフセットの場合は、これを選択します。

0
Roy Latham