是否有解决方案来获取给定存储过程的参数的默认值?

时间:2011-08-09 07:09:00

标签: sql sql-server

我现在使用INFORMATION_SCHEMA.PARAMETERS来获取有关存储过程参数的信息。我需要知道参数的默认值。是否有解决方案来获取给定存储过程的参数的默认值?

4 个答案:

答案 0 :(得分:4)

如果你是通过SQL命令来解析SQL代码......

信息未存储在系统表中。从sys.parameters(你期望它),has_default_value和default_value列,我们被告知解析SQL:

  

SQL Server仅在此目录视图中维护CLR对象的默认值;因此,对于Transact-SQL对象,此列的值为0。要查看Transact-SQL对象中参数的默认值,请查询sys.sql_modules目录视图的definition列,或使用OBJECT_DEFINITION系统函数。

     

如果has_default_value为1,则此列的值为参数的默认值;否则,为NULL。

要证明:

CREATE PROC dbo.Paramtest (@foo int = 42)
AS
SET NOCOUNT ON;
GO

SELECT OBJECT_NAME(object_id), has_default_value, default_value
FROM sys.parameters
WHERE name = '@foo' AND object_id = OBJECT_ID('dbo.Paramtest')

-- gives Paramtest, 0, NULL

答案 1 :(得分:2)

Declare @pSProcName NVARCHAR(MAX)='ProcedureName'

DECLARE @SQLTEXT NVARCHAR(MAX),@start int ,@end int,@SearchCode NVARCHAR(MAX)
SELECT @SQLTEXT =OBJECT_DEFINITION(OBJECT_ID(@pSProcName))
SELECT @start=CHARINDEX('@',@SQLTEXT,1)
SELECT @end =min(val) FROM (
SELECT PATINDEX('%'+CHAR(10)+'AS'+CHAR(10)+'%',@SQLTEXT ) AS val
UNION ALL SELECT PATINDEX('%'+CHAR(10)+'AS'+CHAR(13)+'%',@SQLTEXT )
UNION ALL SELECT PATINDEX('%'+CHAR(13)+'AS'+CHAR(10)+'%',@SQLTEXT )
UNION ALL SELECT PATINDEX('%'+CHAR(13)+'AS'+CHAR(13)+'%',@SQLTEXT )
UNION ALL SELECT PATINDEX('%'+CHAR(10)+'AS'+CHAR(32)+'%',@SQLTEXT )
UNION ALL SELECT PATINDEX('%'+CHAR(32)+'AS'+CHAR(10)+'%',@SQLTEXT )
UNION ALL SELECT PATINDEX('%'+CHAR(32)+'AS'+CHAR(32)+'%',@SQLTEXT )
UNION ALL SELECT PATINDEX('%'+CHAR(13)+'AS'+CHAR(32)+'%',@SQLTEXT )
UNION ALL SELECT PATINDEX('%'+CHAR(32)+'AS'+CHAR(13)+'%',@SQLTEXT )
) S 
Where s.val <> 0

SELECT @SearchCode=SUBSTRING(@sqltext,@start,@end - @start)
SELECT S2.parameter_id,S2.ParameterName,S2.DataType,S1.Default_Value FROM
(
SELECT CASE WHEN Data like '%=%' then RIGHT(Data,len(Data)-CHARINDEX('=',Data,1)) ELSE '' END as Default_Value, 
CASE WHEN Data like '%=%' then 1 ELSE 0 END as Has_default_Value
,Data FROM(
SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) AS Data  
 FROM  
 (
     SELECT  CAST ('<M>' + REPLACE(@SearchCode, ',', '</M><M>') + '</M>' AS XML) AS Data  

 ) AS A CROSS APPLY Data.nodes ('/M') AS Split(a))s
 )S1 

INNER JOIN
(
Select p.parameter_id,p.name as ParameterName,UPPER(t.name) AS DataType from sys.all_parameters p
Inner JOIN sys.types t
on t.user_type_id = p.user_type_id
where Object_name(OBJECT_ID) = @pSProcName
) S2
ON S1.Data LIKE '%'+S2.ParameterName+'%'+S2.DataType+'%'

答案 2 :(得分:0)

@ N.Dinesh.Reddy有一个了不起的答案。我做了一些修改,以提供对参数定义之间的换行的支持,并支持其默认值带有XML字符(例如@Parameter1 NVARCHAR(MAX) = N'<')的参数。

DECLARE @ProcedureName NVARCHAR(MAX) = N'dbo.TestProcedure';


DECLARE @ObjectId INT = OBJECT_ID(@ProcedureName);
DECLARE @DefinitionText NVARCHAR(MAX) = REPLACE(REPLACE(OBJECT_DEFINITION(@ObjectId), CHAR(10), N' '), CHAR(13), N' ');
DECLARE @FirstParameterIndex INT = CHARINDEX('@',@DefinitionText, 1);

-- Pull out only the parameters, and xml-encode them.
SET @DefinitionText = (SELECT SUBSTRING(@DefinitionText, @FirstParameterIndex, PATINDEX(N'% AS %', @DefinitionText) - @FirstParameterIndex) FOR XML PATH(N''));

-- Find the parameter names.
SELECT b.parameter_id, b.name, b.system_type_id, b.user_type_id, b.max_length, b.is_output, a.has_default_value FROM (
    SELECT LEFT(ParameterDefinition, CHARINDEX(N' ', ParameterDefinition, 1)) parameter_name, CAST(CASE WHEN EqualSignIndex = 0 THEN 0 ELSE 1 END AS BIT) has_default_value FROM (
        SELECT ParameterDefinition, CHARINDEX(N'=', ParameterDefinition, 1) EqualSignIndex FROM (
            SELECT LTRIM(RTRIM(Split.ParameterDefinition.value(N'.', N'NVARCHAR(100)'))) ParameterDefinition FROM (
                SELECT CAST(CONCAT('<a>', REPLACE(@DefinitionText, ',', '</a><a>'), '</a>') AS XML) Xml
            ) a CROSS APPLY Xml.nodes('/a') AS Split(ParameterDefinition)
        ) a
    ) a
) a
FULL JOIN sys.all_parameters b ON a.parameter_name = b.name
WHERE b.object_id = @ObjectId;

答案 3 :(得分:0)

我仔细研究了 SQL Server Management Studio 的反汇编,以了解 Microsoft 自己是如何做到的,因为我想确保我的方法完全正确。

正如其他发帖人所推测的那样,令我惊讶的是(阅读:震惊)SSMS 针对 sys.sql_modules.definition 运行 RegEx 以提取 {{1} 中不可用的参数信息}}。

从 SQL Server Management Studio 18 开始,此操作的逻辑在 sys.parameters 中,特别是类 Microsoft.SqlServer.SqlEnum.dll。您还会在其中找到其他有用的正则表达式。

这是模式:

如果您的过程是用 Microsoft.SqlServer.Management.Smo.PostProcessParam 保存的,则使用此模式:

SET QUOTED_IDENTIFIER

如果您的过程new Regex( "(/\\*(([^/\\*])|(\\*(?=[^/]))|(/(?=[^\\*])))*|(/\\*(?>/\\*(?<DEPTH>)|\\*/(?<-DEPTH>)|(.|[\n])?)*(?(DEPTH)(?!))\\*/)\\*/)|(--[^\n]*)|(\"((\"\")|[^\"])*\")|(//[^\n]*)|(?<delim>\\b((AS)|(RETURNS))\\b)|(?:(?<param>@[\\w_][\\w\\d_$$@#]*)((\\s)|((--[^\n]*))|((/\\*(([^/\\*])|(\\*(?=[^/]))|(/(?=[^\\*])))*|(/\\*(?>/\\*(?<DEPTH>)|\\*/(?<-DEPTH>)|(.|[\n])?)*(?(DEPTH)(?!))\\*/)\\*/)))*(AS){0,1})|(?<val>(((\"((\"\")|[^\"])*\"))|((N{0,1}'(('')|[^'])*)')|((0x[0-9a-f]+))|(((\\+|\\-){0,1}((\\d+\\.\\d*)|(\\d*\\.\\d+)|(\\d+))(e((\\+)|(\\-))\\d+){0,1}))|((\\[((\\]\\])|[^\\]])*\\]))|(([\\w_][\\w;\\d_]*))))|(?<comma>,)|(?<eq>=)|(\\([\\d, ]*\\))", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); 保存,则使用此模式:

SET QUOTED_IDENTIFIER

要获取每个参数的默认值,请针对您的 new Regex( "(/\\*(([^/\\*])|(\\*(?=[^/]))|(/(?=[^\\*])))*|(/\\*(?>/\\*(?<DEPTH>)|\\*/(?<-DEPTH>)|(.|[\n])?)*(?(DEPTH)(?!))\\*/)\\*/)|(--[^\n]*)|(//[^\n]*)|(?<delim>\\b((AS)|(RETURNS))\\b)|(?:(?<param>@[\\w_][\\w\\d_$$@#]*)((\\s)|((--[^\n]*))|((/\\*(([^/\\*])|(\\*(?=[^/]))|(/(?=[^\\*])))*|(/\\*(?>/\\*(?<DEPTH>)|\\*/(?<-DEPTH>)|(.|[\n])?)*(?(DEPTH)(?!))\\*/)\\*/)))*(AS){0,1})|(?<val>(((\"((\"\")|[^\"])*\"))|((N{0,1}'(('')|[^'])*)')|((0x[0-9a-f]+))|(((\\+|\\-){0,1}((\\d+\\.\\d*)|(\\d*\\.\\d+)|(\\d+))(e((\\+)|(\\-))\\d+){0,1}))|((\\[((\\]\\])|[^\\]])*\\]))|(([\\w_][\\w;\\d_]*))))|(?<comma>,)|(?<eq>=)|(\\([\\d, ]*\\))", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); 运行适当的正则表达式并注意 sql.sql_modules_definition 组。


TL;DR:

以下是使其工作所需的完整说明:

  • 要求:
    • 本地 SQL Server(使用 SQL Server 2014、2016 和 207 测试)。这不适用于 Azure SQL,因为 Azure SQL 不支持 SQL-CLR。
    • 您不需要 Visual Studio - 只需通过命令行访问 val,这样您就可以自己编译 SQL-CLR 程序集。
  1. 复制并粘贴下面描述的 csc.exe 文件。
  2. 在终端/命令提示符中运行此命令以将 GetParameterDefaults.cs 编译为 SQL-CLR 程序集 GetParameterDefaults.cs
    ProcParamDefs.dll
  3. 找到您在第 2 步中创建的 csc.exe /noconfig /nowarn:1701,1702,2008 /fullpaths /nostdlib+ /errorreport:prompt /warn:4 /define:DEBUG;TRACE /errorendlocation /preferreduilang:en-US /highentropyva+ /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\mscorlib.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Data.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xml.dll" /debug+ /debug:full /optimize- /out:ProcParamDefs.dll /subsystemversion:6.00 /target:library /warnaserror- /utf8output /langversion:7.3 GetParameterDefaults.cs 并将其转换为十六进制二进制字符串(基数为 16)。
    • 有些网站可以免费执行此操作,或使用此 PowerShell 命令:
      ProcParamDefs.dll
  4. ( Get-Content ".\ProcParamDefs.dll" | Format-Hex | Select-Object -Expand Bytes | ForEach-Object { '{0:x2}' -f $_ }) -join '' 文件(在本文末尾)复制并粘贴到 SSMS 会话中。
  5. 用第 3 步中的 Base16/hex 替换 Install.sql。注意前导 <PASTE BASE-16 (HEX) DLL HERE KEEP THE LEADING 0x PREFIX> 需要存在,尾随 0x 也是如此,所以它应该看起来像这样:< /li>
;
  1. 运行它,您应该可以使用 UDF CREATE ASSEMBLY [ProcParamDefs] AUTHORIZATION [dbo] FROM 0x0x4D5A90000300000004000000FFFF0000B800etc ; dbo.GetParameterDefaultsdbo.[GetParameterDefaultsByProcedureObjectId

  2. 例如,看这个截图:

enter image description here

请注意,它仅列出具有默认值的参数。它不会列出没有任何默认定义的参数。

dbo.ParseParameterDefaultValues

GetParameterDefaults.cs

using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using System.Text.RegularExpressions; using Microsoft.SqlServer.Server; /// <summary> /// This SQL-CLR table-valued UDF will parse and expose the default-values for SQL Server stored procedures as this information is not normally available through INFORMATION_SCHEMA nor sys.parameters.<br /> /// By Dai Rees on StackOverflow: https://stackoverflow.com/questions/6992561/is-there-a-solution-for-getting-the-default-value-of-the-parameters-of-a-given-s<br /> /// This also may find its way onto my GitHub eventually too.<br /> /// The crucial regular-expressions inside this UDF were copied from Microsoft's SqlEnum.dll (they're *the exact same* regexes that SSMS uses to show parameter information) btw.<br /> /// I guess the regexes are Microsoft's copyright, but SSMS is given-away for free and Microsoft made no attempt to obscure/hide them, so I guess this is fair-use? If so, then consider my code as MIT licensed, so happy forking! /// </summary> public partial class UserDefinedFunctions { private static readonly Regex _paramRegexQI = new Regex("(/\\*(([^/\\*])|(\\*(?=[^/]))|(/(?=[^\\*])))*|(/\\*(?>/\\*(?<DEPTH>)|\\*/(?<-DEPTH>)|(.|[\n])?)*(?(DEPTH)(?!))\\*/)\\*/)|(--[^\n]*)|(\"((\"\")|[^\"])*\")|(//[^\n]*)|(?<delim>\\b((AS)|(RETURNS))\\b)|(?:(?<param>@[\\w_][\\w\\d_$$@#]*)((\\s)|((--[^\n]*))|((/\\*(([^/\\*])|(\\*(?=[^/]))|(/(?=[^\\*])))*|(/\\*(?>/\\*(?<DEPTH>)|\\*/(?<-DEPTH>)|(.|[\n])?)*(?(DEPTH)(?!))\\*/)\\*/)))*(AS){0,1})|(?<val>(((\"((\"\")|[^\"])*\"))|((N{0,1}'(('')|[^'])*)')|((0x[0-9a-f]+))|(((\\+|\\-){0,1}((\\d+\\.\\d*)|(\\d*\\.\\d+)|(\\d+))(e((\\+)|(\\-))\\d+){0,1}))|((\\[((\\]\\])|[^\\]])*\\]))|(([\\w_][\\w;\\d_]*))))|(?<comma>,)|(?<eq>=)|(\\([\\d, ]*\\))", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled ); private static readonly Regex _paramRegex = new Regex("(/\\*(([^/\\*])|(\\*(?=[^/]))|(/(?=[^\\*])))*|(/\\*(?>/\\*(?<DEPTH>)|\\*/(?<-DEPTH>)|(.|[\n])?)*(?(DEPTH)(?!))\\*/)\\*/)|(--[^\n]*)|(//[^\n]*)|(?<delim>\\b((AS)|(RETURNS))\\b)|(?:(?<param>@[\\w_][\\w\\d_$$@#]*)((\\s)|((--[^\n]*))|((/\\*(([^/\\*])|(\\*(?=[^/]))|(/(?=[^\\*])))*|(/\\*(?>/\\*(?<DEPTH>)|\\*/(?<-DEPTH>)|(.|[\n])?)*(?(DEPTH)(?!))\\*/)\\*/)))*(AS){0,1})|(?<val>(((\"((\"\")|[^\"])*\"))|((N{0,1}'(('')|[^'])*)')|((0x[0-9a-f]+))|(((\\+|\\-){0,1}((\\d+\\.\\d*)|(\\d*\\.\\d+)|(\\d+))(e((\\+)|(\\-))\\d+){0,1}))|((\\[((\\]\\])|[^\\]])*\\]))|(([\\w_][\\w;\\d_]*))))|(?<comma>,)|(?<eq>=)|(\\([\\d, ]*\\))", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled ); private const String _tableDefinition = @"ParameterName nvarchar(100), DefaultValueExpr nvarchar(4000)"; [SqlFunction( DataAccess = DataAccessKind.Read, FillRowMethodName = nameof(FillRow), IsDeterministic = false, Name = nameof(GetParameterDefaults), SystemDataAccess = SystemDataAccessKind.Read, TableDefinition = _tableDefinition )] public static IEnumerable/*<(String parameterName, String defaultValueExpr)>*/ GetParameterDefaults( String procedureName ) { // Despite the fact the function returns an IEnumerable and the SQLCLR docs saying how great streaming is, it's actually very difficult to use `yield return`: // See here: https://stackoverflow.com/questions/591191/sqlfunction-fails-to-open-context-connection-despite-dataaccesskind-read-present // SQLCLR will handle ArgumentExceptions just fine: // https://docs.microsoft.com/en-us/archive/blogs/sqlprogrammability/server-side-error-handling-part-2-errors-and-error-messages // https://docs.microsoft.com/en-us/archive/blogs/sqlprogrammability/exception-handling-in-sqlclr if( procedureName is null ) throw new ArgumentNullException( paramName: nameof(procedureName) ); if( String.IsNullOrWhiteSpace( procedureName ) ) throw new ArgumentException( message: "Value cannot be empty nor whitespace.", paramName: nameof(procedureName) ); // ( Boolean ok, String definition, Boolean isQuotedId ) = TryGetProcedureDefinitionFromName( procedureName ); if( !ok ) { throw new ArgumentException( message: "Could not find the definition of a procedure with the name \"" + procedureName + "\".", paramName: nameof(procedureName) ); } // We can't do this, boo: // foreach( var t in ParseParams( definition, quotedId ? _paramRegexQI : _paramRegex ) ) yield return t; return ParseParameterDefaultValues( definition, isQuotedId ); } [SqlFunction( DataAccess = DataAccessKind.Read, FillRowMethodName = nameof(FillRow), IsDeterministic = false, Name = nameof(GetParameterDefaultsByProcedureObjectId), SystemDataAccess = SystemDataAccessKind.Read, TableDefinition = _tableDefinition )] public static IEnumerable/*<(String parameterName, String defaultValueExpr)>*/ GetParameterDefaultsByProcedureObjectId( Int32 procedureObjectId ) { ( Boolean ok, String definition, Boolean isQuotedId ) = TryGetProcedureDefinitionFromId( procedureObjectId ); if( !ok ) { throw new ArgumentException( message: "Could not find the definition of a procedure with OBJECT_ID = " + procedureObjectId.ToString(), paramName: nameof(procedureObjectId) ); } // We can't do this, boo: // foreach( var t in ParseParams( definition, quotedId ? _paramRegexQI : _paramRegex ) ) yield return t; return ParseParameterDefaultValues( definition, isQuotedId ); } [SqlFunction( DataAccess = DataAccessKind.Read, FillRowMethodName = nameof(FillRow), IsDeterministic = false, Name = nameof(ParseParameterDefaultValues), SystemDataAccess = SystemDataAccessKind.Read, TableDefinition = _tableDefinition )] public static IEnumerable/*<(String parameterName, String defaultValueExpr)>*/ ParseParameterDefaultValues( String procedureDefinition, Boolean isQuotedId ) { List<(String parameterName, String defaultValueExpr)> list = new List<(String parameterName, String defaultValueExpr)>(); foreach( (String name, String value) t in ParseParams( procedureDefinition, isQuotedId ? _paramRegexQI : _paramRegex ) ) { list.Add( t ); } return list; } private static ( Boolean ok, String definition, Boolean quotedId ) TryGetProcedureDefinitionFromName( String procedureName ) { using( SqlConnection c = new SqlConnection( "context connection=true" ) ) { c.Open(); using( SqlCommand cmd = c.CreateCommand() ) { cmd.CommandText = @" SELECT c.[definition], CONVERT( bit, OBJECTPROPERTY( c.object_id, N'ExecIsQuotedIdentOn' ) ) AS IsQuotedId FROM sys.sql_modules AS c WHERE c.object_id = OBJECT_ID( @procedureName );"; _ = cmd.Parameters.Add( new SqlParameter( "@procedureName", SqlDbType.NVarChar ) { Value = procedureName } ); using( SqlDataReader rdr = cmd.ExecuteReader() ) { if( rdr.Read() ) { String definition = rdr.GetString(0); Boolean quotedId = rdr.GetBoolean(1); return ( ok: true, definition, quotedId ); } else { // Validate the object-name: return ( ok: false, definition: null, quotedId: default ); } } } /* using( SqlCommand cmdPostMortem = c.CreateCommand() ) { cmdPostMortem.CommandText = @" SELECT OBJECT_ID( @procedureName ) AS oid"; _ = cmdPostMortem.Parameters.Add( new SqlParameter( "@procedureName", SqlDbType.NVarChar ) { Value = procedureName } ); Object objectId = cmdPostMortem.ExecuteScalar(); if( objectId is null || objectId == DBNull.Value ) { } } */ } } private static ( Boolean ok, String definition, Boolean quotedId ) TryGetProcedureDefinitionFromId( Int32 procedureObjectId ) { using( SqlConnection c = new SqlConnection( "context connection=true" ) ) { c.Open(); using( SqlCommand cmd = c.CreateCommand() ) { cmd.CommandText = @" SELECT c.[definition], CONVERT( bit, OBJECTPROPERTY( c.object_id, N'ExecIsQuotedIdentOn' ) ) AS IsQuotedId FROM sys.sql_modules AS c WHERE c.object_id = @objId;"; _ = cmd.Parameters.Add( new SqlParameter( "@objId", SqlDbType.Int ) { Value = procedureObjectId } ); // `OBJECT_ID` returns `int` values btw. using( SqlDataReader rdr = cmd.ExecuteReader() ) { if( rdr.Read() ) { String definition = rdr.GetString(0); Boolean quotedId = rdr.GetBoolean(1); return ( ok: true, definition, quotedId ); } else { return ( ok: false, definition: null, quotedId: default ); } } } } } private static IEnumerable<(String name, String value)> ParseParams( String definition, Regex r ) { Boolean inParam = false; String currentParameterName = null; Match m = r.Match( definition ); while( m.Success && !m.Groups["delim"].Success ) { if( m.Groups["eq"].Success ) { inParam = true; } if( m.Groups["comma"].Success ) { inParam = false; currentParameterName = null; } if( inParam && currentParameterName != null && m.Groups["val"].Success ) { String defaultValue = m.Groups["val"].Value; inParam = false; yield return ( currentParameterName, defaultValue ); } if( m.Groups["param"].Success ) { currentParameterName = m.Groups["param"].Value; } m = m.NextMatch(); } } public static void FillRow( Object tupleObj, out SqlString parameterName, out SqlString defaultValueExpr ) { if( tupleObj is ValueTuple<String,String> vt ) { parameterName = vt.Item1; defaultValueExpr = vt.Item2; } else if( tupleObj is null ) { throw new ArgumentNullException( paramName: nameof(tupleObj) ); } else { throw new ArgumentException( message: "Expected first argument to be of type ValueTuple<String,String> but encountered " + tupleObj.GetType().FullName, paramName: nameof(tupleObj) ); } } }

Install.sql