ユーザー共有クエリ:動的SQLとSQLCMD


15

foo.sqlDB技術サポートのチームが共有する多数のクエリをリファクタリングして文書化する必要があります(顧客の構成など)。各顧客が独自のサーバーとデータベースを持っている場合に定期的に来るチケットの種類がありますが、それ以外はスキーマは全面的に同じです。

現時点では、ストアドプロシージャはオプションではありません。私は動的またはSQLCMDのどちらを使用するかを議論しています。SQLServerを少し使い始めたばかりなので、どちらもあまり使用していません。

SQLCMDスクリプティング私は間違いなく「見た目」がきれいで、必要に応じてクエリを読みやすく、小さな変更を加えやすいと感じていますが、ユーザーにSQLCMDモードを強制的に有効にします。文字列操作を使用して記述されたクエリにより、構文の強調表示が失われるため、動的はより困難になります。

これらは、Management Studio 2012、SQLバージョン2008R2を使用して編集および実行されています。どちらの方法の長所/短所、またはいずれかの方法でのSQL Serverの「ベストプラクティス」の一部は何ですか?それらの1つは他よりも「安全」ですか?

動的な例:

declare @ServerName varchar(50) = 'REDACTED';
declare @DatabaseName varchar(50) = 'REDACTED';
declare @OrderIdsSeparatedByCommas varchar(max) = '597336, 595764, 594594';

declare @sql_OrderCheckQuery varchar(max) = ('
use {@DatabaseName};
select 
    -- stuff
from 
    {@ServerName}.{@DatabaseName}.[dbo].[client_orders]
        as "Order"
    inner join {@ServerName}.{@DatabaseName}.[dbo].[vendor_client_orders]
        as "VendOrder" on "Order".o_id = "VendOrder".vco_oid
where "VendOrder".vco_oid in ({@OrderIdsSeparatedByCommas});
');
set @sql_OrderCheckQuery = replace( @sql_OrderCheckQuery, '{@ServerName}',   quotename(@ServerName)   );
set @sql_OrderCheckQuery = replace( @sql_OrderCheckQuery, '{@DatabaseName}', quotename(@DatabaseName) );
set @sql_OrderCheckQuery = replace( @sql_OrderCheckQuery, '{@OrderIdsSeparatedByCommas}', @OrderIdsSeparatedByCommas );
print   (@sql_OrderCheckQuery); -- For debugging purposes.
execute (@sql_OrderCheckQuery);

SQLCMDの例:

:setvar ServerName "[REDACTED]";
:setvar DatabaseName "[REDACTED]";
:setvar OrderIdsSeparatedByCommas "597336, 595764, 594594"

use $(DatabaseName)
select 
    --stuff
from 
    $(ServerName).$(DatabaseName).[dbo].[client_orders]
        as "Order"
    inner join $(ServerName).$(DatabaseName).[dbo].[vendor_client_orders]
        as "VendOrder" on "Order".o_id = "VendOrder".vco_oid
where "VendOrder".vco_oid in ($(OrderIdsSeparatedByCommas));

use ...スクリプトでの目的は何ですか?後続のクエリを正しく実行することは重要ですか?現在のデータベースの変更がクエリの期待される結果の1つである場合、動的SQLバージョンは、SQLCMDバリエーション(これは、もちろん、スコープは1つだけです)。
アンドリーM

useスコープは、とにかく、この特定のスクリプト中に変更されることはありませんよう文はおそらく、省略することができます。クロスサーバー検索が行われるいくつかのユースケースがありますが、それはこの投稿の範囲外です。
フランシス

回答:


13

これらを邪魔にならないようにするために:

  • 技術的に言えば、これらのオプションはどちらも「動的」/アドホッククエリであり、送信されるまで解析/検証されません。それらはSQLCMDスクリプトにかかわらず、(パラメータ化されていないので、あなたは、あなたが交換する機会がなければならないのCMDスクリプトから変数に渡す場合との両方が、SQLインジェクションの影響を受けやすい'''か仕事が場所に応じて可能性があり、変数が使用されています)。

  • 各アプローチには長所と短所があります。

    • SSMSのSQLスクリプトは簡単に編集でき(必要な場合は素晴らしい)、SQLCMDからの出力よりも結果の操作が簡単です。欠点は、ユーザーがIDEを使用しているため、SQLを簡単に台無しにしてしまうことです。IDEを使用すると、SQLを知らなくてもさまざまな変更を簡単に行うことができます。
    • SQLCMD.EXEを介してスクリプトを実行すると、ユーザーは簡単に変更できません(エディターでスクリプトを編集してから最初に保存する必要はありません)。ユーザーがスクリプトを変更することになっていない場合、これは素晴らしいことです。このメソッドでは、実行ごとにログを記録することもできます。欠点として、スクリプトを定期的に編集する必要がある場合、非常に面倒です。または、ユーザーが結果セットの10万行をスキャンする必要がある場合や、それらの結果をExcelなどにコピーする必要がある場合は、このアプローチでも困難です。

サポート担当者がアドホッククエリを実行せず、それらの変数に入力するだけであれば、それらのスクリプトを編集して不要な変更を加えることができるSSMSにいる必要はありません。

目的の変数値の入力を求めるCMDスクリプトを作成し、それらの値を使用してSQLCMD.EXEを呼び出します。CMDスクリプトは、実行をファイルに記録することもでき、タイムスタンプと変数値が送信されます。

SQLスクリプトごとに1つのCMDスクリプトを作成し、ネットワーク共有フォルダーに配置します。ユーザーがCMDスクリプトをダブルクリックすると、機能します。

以下に例を示します。

  • ユーザーにサーバー名の入力を要求します(まだエラーチェックはしていません)
  • ユーザーにデータベース名の入力を求めます
    • 空白のままにすると、指定されたサーバー上のデータベースが一覧表示され、再度プロンプトが表示されます
    • データベース名が無効な場合、ユーザーは再度プロンプトが表示されます
  • ユーザーにOrderIDsSeparatedByCommasの入力を求めます
    • 空白の場合、ユーザーに再度プロンプトを表示します
  • SQLスクリプトを実行%OrderIDsSeparatedByCommas%し、SQLCMD変数としての値を渡します$(OrderIDsSeparatedByCommas)
  • 実行日、時刻、ServerName、DatabaseName、およびOrde​​rIDsSeparatedByCommasを、スクリプトを実行しているWindowsログインの名前のログファイルに記録します(このように、ログディレクトリがネットワークで、これを使用する複数のユーザーがいる場合、書き込みはありませんエントリごとにUSERNAMEがファイルに記録される場合のようなログファイルの競合)
    • ログファイルディレクトリが存在しない場合は作成されます

テストSQLスクリプト(名前:FixProblemX.sql):

SELECT  *
FROM    sys.objects
WHERE   [schema_id] IN ($(OrderIdsSeparatedByCommas));

CMDスクリプト(名前:FixProblemX.cmd):

@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION

SET ScriptLogPath=\\server\share\RunSqlCmdScripts\LogFiles

CLS

SET /P ScriptServerName=Please enter in a Server Name (leave blank to exit): 

IF "%ScriptServerName%" == "" GOTO :ThisIsTheEnd

REM echo %ScriptServerName%

:RequestDatabaseName
ECHO.
SET /P ScriptDatabaseName=Please enter in a Database Name (leave blank to list DBs on %ScriptServerName%): 

IF "%ScriptDatabaseName%" == "" GOTO :GetDatabaseNames

SQLCMD -b -E -W -h-1 -r0 -S %ScriptServerName% -Q "SET NOCOUNT ON; IF (NOT EXISTS(SELECT [name] FROM sys.databases WHERE [name] = N'%ScriptDatabaseName%')) RAISERROR('Invalid DB name!', 16, 1);" 2> nul

IF !ERRORLEVEL! GTR 0 (
    ECHO.
    ECHO That Database Name is invalid. Please try again.

    SET ScriptDatabaseName=
    GOTO :RequestDatabaseName
)

:RequestOrderIDs
ECHO.
SET /P OrderIdsSeparatedByCommas=Please enter in the OrderIDs (separate multiple IDs with commas): 

IF "%OrderIdsSeparatedByCommas%" == "" (

    ECHO.
    ECHO Don't play me like that. You gots ta enter in at least ONE lousy OrderID, right??
    GOTO :RequestOrderIDs
)


REM Finally run SQLCMD!!
SQLCMD -E -W -S %ScriptServerName% -d %ScriptDatabaseName% -i FixProblemX.sql -v OrderIdsSeparatedByCommas=%OrderIdsSeparatedByCommas%

REM Log this execution
SET ScriptLogFile=%ScriptLogPath%\%~n0_%USERNAME%.log
REM echo %ScriptLogFile%

IF NOT EXIST %ScriptLogPath% MKDIR %ScriptLogPath%

ECHO %DATE% %TIME% ServerName=%ScriptServerName%    DatabaseName=[%ScriptDatabaseName%] OrderIdsSeparatedByCommas=%OrderIdsSeparatedByCommas%   >> %ScriptLogFile%

GOTO :ThisIsTheEnd

:GetDatabaseNames
ECHO.
SQLCMD -E -W -h-1 -S %ScriptServerName% -Q "SET NOCOUNT ON; SELECT [name] FROM sys.databases ORDER BY [name];"
ECHO.
GOTO :RequestDatabaseName

:ThisIsTheEnd
PAUSE

ScriptLogPathスクリプトの上部にある変数を必ず編集してください。

また、SQLスクリプト(SQLCMD.EXEの-iコマンドラインスイッチで指定、完全修飾パスを使用することでメリット得られる可能性がありますが、完全には定かではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.