Phr*_*cis 15 sql-server scripting dynamic-sql sqlcmd
我必须重构并记录一些foo.sql
查询,这些查询将由一个数据库技术支持团队共享(用于客户配置等)。有些类型的票会定期出现,每个客户都有自己的服务器和数据库,但除此之外,架构是完全相同的。
存储过程目前不是一个选项。我正在争论是使用动态还是 SQLCMD,我没有使用过太多,因为我对 SQL Server 有点陌生。
SQLCMD 脚本我觉得对我来说绝对“看起来”更干净,更容易阅读并根据需要对查询进行小的更改,但也会强制用户启用 SQLCMD 模式。由于使用字符串操作编写查询而导致语法突出显示丢失,因此动态更加困难。
这些正在使用 Management Studio 2012,SQL 版本 2008R2 进行编辑和运行。这两种方法的优缺点是什么,或者一种方法或另一种方法的一些 SQL Server“最佳实践”是什么?其中一个比另一个“更安全”吗?
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);
Run Code Online (Sandbox Code Playgroud)
: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));
Run Code Online (Sandbox Code Playgroud)
Sol*_*zky 13
只是为了摆脱这些:
从技术上讲,这两个选项都是“动态”/即席查询,在提交之前不会被解析/验证。而且两者很容易受到SQL注入,因为它们没有参数(虽然与SQLCMD脚本,如果你是在一个变量传递从CMD脚本,那么你也有机会取代'
用''
,这可能会或可能不会工作,具体情况取决于正在使用变量)。
每种方法各有利弊:
如果您的支持人员没有进行临时查询而只是填写这些变量,那么他们就不需要在 SSMS 中编辑这些脚本并进行不需要的更改。
我会创建 CMD 脚本来提示用户输入所需的变量值,然后使用这些值调用SQLCMD.EXE。CMD 脚本甚至可以将执行记录到文件中,并提交时间戳和变量值。
为每个 SQL 脚本创建一个 CMD 脚本并将其放置在网络共享文件夹中。用户双击 CMD 脚本,它就可以工作了。
这是一个例子:
%OrderIDsSeparatedByCommas%
作为 SQLCMD 变量传入$(OrderIDsSeparatedByCommas)
测试 SQL 脚本(名为:FixProblemX.sql):
SELECT *
FROM sys.objects
WHERE [schema_id] IN ($(OrderIdsSeparatedByCommas));
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
请务必ScriptLogPath
在脚本顶部编辑变量。
此外,SQL 脚本(由SQLCMD.EXE的-i
命令行开关指定)可能会受益于完全限定的路径,但并不完全确定。