私は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を配置する必要がありますが、これによりこのプロシージャの作成が終了しますか?ユーザーがこのプロシージャのパラメータとしてデータベース名を指定できるように、このデータベース選択をこのプロシージャにどのように配置できますか?
これを行うには、少なくとも2つの方法があります。
Case/switchステートメント(または、この例では、単純なif..else
ブロック)を使用して、パラメーターをデータベースのリストと比較し、それに基づいてusingステートメントを実行します。これには、ユーザーアカウントが権限を持つすべてのものへのアクセスを許可するのではなく、プロシージャが既知のセットにアクセスできるデータベースを制限するという利点があります。
declare @dbname nvarchar(255);
set @dbname = 'db1';
if @dbname = 'db1'
use db1;
else if @dbname = 'db2'
use db2;
動的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
ではなく。
_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
_
これを行う唯一の方法は、強力ですが危険な Dynamic SQL を使用することです。
同じ目的を達成する別の方法は、システムストアドプロシージャを使用することです。
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を使用しないオフセットの場合は、これを選択します。