动态检索 T10=] 存储过程中的参数名称和当前值

Dynamically Retrieve Parameter Names & Current Values Inside T-SQL Stored Procedure

我有数百个用于商业智能的模板化 ETL 存储过程。他们记录他们的操作 activity 以审计 table。缺少的一件事是记录传递给它们的参数信息。问题是从一个 SP 到另一个 SP 的参数并不总是相同的。我正在寻找一段标准代码,我可以将其粘贴到可以循环遍历 proc 的所有参数并检索传入的当前值的过程中。我计划将它们混合在一个字符串中以同时记录到 table.有什么想法吗?

提前感谢您的指点! -蒂姆

我正在寻找一段标准代码,我可以将其粘贴到可以循环遍历 proc 的所有参数并检索传入的当前值的过程中--

您可以使用以下查询获取为 sp 传入的所有值

示例:
我有下面的存储过程,它为我提供了销售详细信息(仅供演示)

alter  proc dbo.getsales
(
@salesid int
)
as
begin
select 
* from sales where cust_id=@salesid
end

我已经像下面这样调用了我的 sp..

exec  dbo.getsales 4

现在如果我想传递值,我可以使用下面的查询

select top 10* from sys.dm_exec_cached_plans cp
cross apply
sys.dm_exec_text_query_plan(cp.plan_handle,default,default)
where objtype='proc'

下面显示的是编译时值

话虽如此,有很多事情要考虑..我们可以使用xml方法来获取这个值

现在会发生什么,如果我 运行 相同的存储过程再次为值 2 ..

<ColumnReference Column="@salesid" ParameterCompiledValue="(4)" ParameterRuntimeValue="(2)" />

这里有一个重要的问题,当我选择要从 ssms 显示的执行计划时会显示上述值。

但是缓存中的值是多少,让我们再次使用上面的计划缓存查询查看它

<ColumnReference Column="@salesid" ParameterCompiledValue="(4)"/>

它仍然显示编译值,加上 usecounts 列为 5--`这意味着该计划已被使用 5 次,最初编译计划时传递的参数是 4.which 也意味着,运行 时间值未存储在缓存的计划详细信息中..

所以总而言之,您可以获得 运行 传递给存储过程的时间值

  • 1.Values 在编译语句时传递(
    您可以在一段时间内开始收集此信息并将它们记录到存储过程中,我认为随着时间的推移服务器重新启动,计划重新编译您可以获得一组新的参数值 )
  • 2.Getting 与 DEV 团队联系也是一个好方法,因为他们可以为您提供可以传递的参数的完整列表,如果这个 练习很麻烦

您不必动态检索名称和参数存储过程中,因为一旦存储过程被创建或改变它不能改变它的参数,直到它被再次改变或重新创建。

相反,您可能在存储过程中有参数列表 static,但是为了不手动枚举参数,您可以通过 DDL 触发器动态生成它。

定义一些注释标签,用于标记参数应在存储过程中列出的位置,并在适当的情况下将它们添加到存储过程的主体中。触发器应该找到标记并改变 proc 在标记之间插入参数名称及其值的静态列表。示例如下。

DDL-触发器

create trigger StoredProc_ListParams on database
for CREATE_PROCEDURE, ALTER_PROCEDURE
as
begin
    set nocount on;

    if @@nestlevel > 1
        return;

    declare @evt xml, @sch sysname, @obj sysname, @text nvarchar(max);

    set @evt = eventdata();
    set @text = @evt.value('(/EVENT_INSTANCE/TSQLCommand/CommandText/text())[1]', 'nvarchar(max)');

    if @text is NULL -- skip encrypted
        return;

    set @sch = @evt.value('(/EVENT_INSTANCE/SchemaName/text())[1]', 'sysname');
    set @obj = @evt.value('(/EVENT_INSTANCE/ObjectNa1me/text())[1]', 'sysname');

    declare @listParams nvarchar(max);

    set @listParams = '
    select name, value
    from (values ' + stuff(
        (select ',
        ' + '(' + cast(p.parameter_id as varchar(10)) + ', ''' + p.name + '''' +
        ', cast(' + p.name + ' as sql_variant))'
    from sys.parameters p
        join sys.objects o on o.object_id = p.object_id and o.type = 'P'
        join sys.schemas s on s.schema_id = o.schema_id
    where s.name = @sch and o.name = @obj
    order by p.parameter_id
    for xml path(''), type).value('text()[1]', 'nvarchar(max)'), 1, 1, '') + '
        ) p(num, name, value)
    order by num';

    declare @startMarker nvarchar(100), @endMarker nvarchar(100);
    set @startMarker = '--%%LIST_PARAMS_START%%';
    set @endMarker = '--%%LIST_PARAMS_END%%';

    if left(@text, 6) = 'create'
        set @text = stuff(@text, 1, 6, 'alter');

    declare @ixStart int, @ixEnd int;
    set @ixStart = nullif(charindex(@startMarker, @text), 0) + len(@startMarker);
    set @ixEnd = nullif(charindex(@endMarker, @text), 0);

    if @ixStart is NULL or @ixEnd is NULL
        return;

    set @text = stuff(@text, @ixStart, @ixEnd - @ixStart, @listParams + char(13) + char(10));

    if @text is NULL
        return;

    exec(@text);
end

测试的存储过程脚本:

create procedure dbo.TestProc
(
    @id int,
    @name varchar(20),
    @someFlag bit,
    @someDate datetime
)
as
begin
    set nocount on;

--%%LIST_PARAMS_START%%
    -- list params for me here, please
--%%LIST_PARAMS_END%%

end

以下是执行上述创建脚本后存储过程在数据库中的实际外观:

alter procedure [dbo].[Test]
(
    @id int,
    @name varchar(20),
    @someFlag bit,
    @someDate datetime
)
as
begin
    set nocount on;

--%%LIST_PARAMS_START%%
    select name, value
    from (values 
        (1, '@id', cast(@id as sql_variant)),
        (2, '@name', cast(@name as sql_variant)),
        (3, '@someFlag', cast(@someFlag as sql_variant)),
        (4, '@someDate', cast(@someDate as sql_variant))
        ) p(num, name, value)
    order by num
--%%LIST_PARAMS_END%%

end

这种方法的一个限制是它不适用于加密的存储过程。如果您希望处理 table 类型的参数,您还必须进行一些调整。

自 SQL Server 2014 我们有 sys.dm_exec_input_buffer,它是一个 table 值函数,输出列 event_info 给出了完整的执行语句(包括参数) .

您可以阅读完整的执行语句,然后从中解析参数值。

例如:

这些行包含在正在执行的存储过程中

-- get the full execution statement
declare @statement nvarchar(max)
select  @statement = event_info
from    sys.dm_exec_input_buffer(@@spid, current_request_id())

-- parse params from the statement
declare @proc_name varchar(128) = object_name(@@procid)
declare @param_idx int = charindex(@proc_name, @statement) + len(@proc_name)
declare @param_len int = len(@statement) - @param_idx 
declare @params nvarchar(max) = right(@statement, @param_len)

print @params

-- or log them...
exec log_error sysdatetime(), @proc_name, @params, error_line(), error_message()

我用它来记录 catch 块中的错误。