我已经将动态SQL用于许多任务并不断遇到同样的问题:打印Dynamic T-SQL语句中使用的变量值。
EG:
Declare @SQL nvarchar(max), @Params nvarchar(max), @DebugMode bit, @Foobar int
select @DebugMode=1,@Foobar=364556423
set @SQL='Select @Foobar'
set @Params=N'@Foobar int'
if @DebugMode=1 print @SQL
exec sp_executeSQL @SQL,@Params
,@Foobar=@Foobar
上述代码的打印结果只是“选择@Foobar”。有没有办法动态打印值和&正在执行的sql的变量名?或者在进行打印时,将参数替换为实际值,以便SQL可以重新运行?
我使用创建一个或两个函数来完成类似的操作,但是使用数据类型转换,模式匹配截断问题和非动态解决方案。我很好奇其他开发人员如何在不手动手动打印每个变量的情况下解决此问题。
答案 0 :(得分:3)
我不相信评估的语句是可用的,这意味着你的示例查询'Select @FooBar'永远不会像'Select 364556243'那样持久存在
即使在探查器跟踪中,您也会看到语句命中缓存为'(@Foobar int)select @foobar'
这是有道理的,因为使用sp_executesql的一大好处是它能够以可靠的形式缓存语句而不评估变量,否则如果它替换了变量并执行了该语句,我们就会看到执行计划膨胀。
已更新:这是正确方向的步骤:
所有这些都可以通过输入(@ Statement,@ ParamDef,@ ParamVal)清理并包装在一个很好的函数中,并返回“准备好”的语句。我会留下一些作为练习,但是当你改进它时请回复!
使用此处的分割功能link
set nocount on;
declare @Statement varchar(100), -- the raw sql statement
@ParamDef varchar(100), -- the raw param definition
@ParamVal xml -- the ParamName -to- ParamValue mapping as xml
-- the internal params:
declare @YakId int,
@Date datetime
select @YakId = 99,
@Date = getdate();
select @Statement = 'Select * from dbo.Yak where YakId = @YakId and CreatedOn > @Date;',
@ParamDef = '@YakId int, @Date datetime';
-- you need to construct this xml manually... maybe use a table var to clean this up
set @ParamVal = ( select *
from ( select '@YakId', cast(@YakId as varchar(max)) union all
select '@Date', cast(@Date as varchar(max))
) d (Name, Val)
for xml path('Parameter'), root('root')
)
-- do the work
declare @pStage table (pName varchar(100), pType varchar(25), pVal varchar(100));
;with
c_p (p)
as ( select replace(ltrim(rtrim(s)), ' ', '.')
from dbo.Split(',', @ParamDef)d
),
c_s (pName, pType)
as ( select parsename(p, 2), parsename(p, 1)
from c_p
),
c_v (pName, pVal)
as ( select p.n.value('Name[1]', 'varchar(100)'),
p.n.value('Val[1]', 'varchar(100)')
from @ParamVal.nodes('root/Parameter')p(n)
)
insert into @pStage
select s.pName, s.pType, case when s.pType = 'datetime' then quotename(v.pVal, '''') else v.pVal end -- expand this case to deal with other types
from c_s s
join c_v v on
s.pName = v.pName
-- replace pName with pValue in statement
select @Statement = replace(@Statement, pName, isnull(pVal, 'null'))
from @pStage
where charindex(pName, @Statement) > 0;
print @Statement;
答案 1 :(得分:2)
关于大多数人如何做的话题,我只会说我做的事情:
我能想到的最好的方法是使用SQL Trace捕获查询。如果在查询字符串中放置一些唯一的东西(作为注释),则很容易在跟踪中为它应用过滤器,这样就不会捕获超出需要的内容。
然而,并非所有的桃子和霜。
这仅适用于开发环境,可能 QA,具体取决于您的商店的严格程度。
如果查询需要很长时间才能运行,您可以通过在@DebugMode = 1时向查询字符串添加“TOP 1”,“WHERE 1 = 2”或类似的限制子句来缓解这种情况。否则,您可以最后等待一段时间才能完成。
对于长时间查询,您无法仅为调试模式添加查询字符串,您可以在StmtStarted事件中捕获命令文本,然后在获得命令后立即取消查询。
如果查询是INSERT / UPDATE / DELETE,则需要强制回滚,如果@DebugMode = 1并且您不希望发生更改。如果您当前没有使用显式事务,那么这将是额外的开销。
如果你走这条路,你可以实现一些自动化,让生活更轻松。您可以为跟踪创建和启动/停止操作创建模板。您可以将结果记录到文件或表中,并以编程方式从那里处理命令文本。