动态检索 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 块中的错误。
我有数百个用于商业智能的模板化 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 块中的错误。