打印动态参数值

发布于 2024-11-05 19:31:56 字数 538 浏览 0 评论 0原文

我在许多任务中使用了动态 SQL,并且不断遇到同样的问题:打印动态 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

上述代码的打印结果只是“Select @Foobar”。有什么方法可以动态打印值&正在执行的sql的变量名?或者在进行打印时,将参数替换为实际值,以便 SQL 可以重新运行?

我尝试过创建一两个函数来完成类似的事情,但涉及数据类型转换、模式匹配截断问题和非动态解决方案。我很好奇其他开发人员如何在不手动打印每个变量的情况下解决这个问题。

I've used dynamic SQL for many tasks and continuously run into the same problem: Printing values of variables used inside the Dynamic T-SQL statement.

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

The print results of the above code are simply "Select @Foobar". Is there any way to dynamically print the values & variable names of the sql being executed? Or when doing the print, replace parameters with their actual values so the SQL is re-runnable?

I have played with creating a function or two to accomplish something similar, but with data type conversions, pattern matching truncation issues, and non-dynamic solutions. I'm curious how other developers solve this issue without manually printing each and every variable manually.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

梦屿孤独相伴 2024-11-12 19:31:56

我不相信评估的语句可用,这意味着您的示例查询“Select @FooBar”永远不会在任何地方保留为“Select 364556243”

即使在探查器跟踪中,您也会看到该语句以“(@Foobar int)select @foobar”的形式命中缓存这

是有道理的,因为使用 sp_executesql 的一大好处是它能够以可靠的形式缓存语句,而无需评估变量,否则如果它替换变量并执行该语句,我们只会看到执行计划膨胀。

更新:这是朝着正确方向迈出的一步:

所有这些都可以清理并包装在一个很好的函数中,其中包含输入(@Statement、@ParamDef、@ParamVal)和将返回“准备好的”声明。我会留下一些作为练习给您,但请在您改进后回复!

使用这里的 split 函数 链接

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;

I dont believe the evaluated statement is available, meaning your example query 'Select @FooBar' is never persisted anywhere as 'Select 364556243'

Even in a profiler trace you would see the statement hit the cache as '(@Foobar int)select @foobar'

This makes sense, since a big benefit of using sp_executesql is that it is able to cache the statement in a reliable form without variables evaluated, otherwise if it replaced the variables and executed that statement we would just see the execution plan bloat.

updated: Here's a step in right direction:

All of this could be cleaned up and wrapped in a nice function, with inputs (@Statement, @ParamDef, @ParamVal) and would return the "prepared" statement. I'll leave some of that as an exercise for you, but please post back when you improve it!

Uses split function from here 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;
困倦 2024-11-12 19:31:56

关于大多数人如何做到这一点的主题,我只会谈论我所做的事情:

  • 创建一个测试脚本,该脚本将使用各种有效和无效输入来运行该过程。如果参数是整数,我将发送“4”(而不是 4),但我只会尝试 1 个奇怪的字符串值,例如“agd”。
  • 针对我正在做的事情,针对具有代表性大小和数据值分布的数据集运行这些值。使用您最喜欢的数据生成工具(市场上有几个不错的工具)来加速这一过程。
  • 我通常会在更临时的基础上进行这样的调试,因此从 SSMS 结果窗口收集结果就足够了。

我能想到的最好方法是使用 SQL 跟踪捕获通过网络传输的查询。如果您在查询字符串中放置一些唯一的内容(作为注释),则可以很容易地在跟踪中为其应用过滤器,这样您就不会捕获超出您需要的内容。

然而,这并不全是桃子和桃子。奶油。

这仅适用于开发环境,也许 QA,具体取决于您的商店的严格程度。

如果查询需要很长时间才能运行,您可以通过在 @DebugMode = 1 的情况下向查询字符串添加“TOP 1”、“WHERE 1=2”或类似的限制子句来缓解这种情况。否则,您最终可能会等待每次都需要一段时间才能完成。

对于无法仅在调试模式下添加查询字符串的长查询,您可以在 StmtStarted 事件中捕获命令文本,然后在获得命令后立即取消查询。

如果查询是 INSERT/UPDATE/DELETE,并且 @DebugMode = 1 并且您不希望发生更改,则需要强制回滚。如果您当前没有使用显式事务,那么这样做会产生额外的开销。

如果你走这条路,你可以实现一些自动化,让生活变得更轻松。您可以为跟踪创建和启动/停止操作创建模板。您可以将结果记录到文件或表中,并以编程方式处理其中的命令文本。

On the topic of how most people do it, I will only speak to what I do:

  • Create a test script that will run the procedure using a wide range of valid and invalid input. If the parameter is an integer, I will send it '4' (instead of 4), but I'll only try 1 oddball string value like 'agd'.
  • Run the values against a data set of representative size and data value distribution for what I'm doing. Use your favorite data generation tool (there are several good ones on the market) to speed this up.
  • I'm generally debugging like this on a more ad hoc basis, so collecting the results from the SSMS results window is as far as I need to take it.

The best way I can think of is to capture the query as it comes across the wire using a SQL Trace. If you place something unique in your query string (as a comment), it is very easy to apply a filter for it in the trace so that you don't capture more than you need.

However, it isn't all peaches & cream.

This is only suitable for a Dev environment, maybe QA, depending on how rigid your shop is.

If the query takes a long time to run, you can mitigate that by adding "TOP 1", "WHERE 1=2", or a similar limiting clause to the query string if @DebugMode = 1. Otherwise, you could end up waiting a while for it to finish each time.

For long queries where you can't add something the query string only for debug mode, you could capture the command text in a StmtStarted event, then cancel the query as soon as you have the command.

If the query is an INSERT/UPDATE/DELETE, you will need to force a rollback if @DebugMode = 1 and you don't want the change to occur. In the event you're not currently using an explicit transaction, doing that would be extra overhead.

Should you go this route, there is some automation you can achieve to make life easier. You can create a template for the trace creation and start/stop actions. You can log the results to a file or table and process the command text from there programatically.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文