获取存储过程参数的默认值
Get default value of stored procedure parameter
我创建了以下具有默认值的存储过程:
CREATE PROCEDURE [dbo].[Sample1]
@OrderID INT = 10285
AS
SELECT ProductName, OrderID
FROM Products P, [Order Details] Od
WHERE Od.ProductID = P.ProductID
AND Od.OrderID = @OrderID
尝试使用 sys.parameters
.
获取参数的默认值 (10285)
Select a.object_id, a.default_value
from sys.parameters a
inner join sys.types b on b.system_type_id = a.system_type_id
where Object_id = object_id('[dbo].[Sample1]')
但是我得到 NULL 作为 default_value
,而我期望 10285
作为 default_value
。
有没有办法获取默认值?
看起来 Microsoft has neglected this topic 并且没有找到参数默认值的简单方法,即使特定参数存在或不存在默认值也是如此:
As we all know, T-SQL stored procedure parameter defaults are not
stored in sys.parameters, all_parameters, and system_parameters. They
are also not exposed through sp_sproc_columns, sys.columns, or
sp_procedure_params_rowset.
来自微软的反馈:
As posted by Tibor Karaszi, BOL document that "SQL Server only
maintains default values for CLR objects in this catalog view;
therefore, this column has a value of 0 for Transact-SQL objects. To
view the default value of a parameter in a Transact-SQL object, query
the definition column of the sys.sql_modules catalog view, or use the
OBJECT_DEFINITION system function."
We dont store even the bit that indicating parameter is of default
value in Yukon.
我已经测试了第一个代码片段 in this answer,它似乎适用于您的简单示例:
SELECT
data3.name
, [default_value] = REVERSE(RTRIM(SUBSTRING(
data3.rtoken
, CASE
WHEN CHARINDEX(N',', data3.rtoken) > 0
THEN CHARINDEX(N',', data3.rtoken) + 1
WHEN CHARINDEX(N')', data3.rtoken) > 0
THEN CHARINDEX(N')', data3.rtoken) + 1
ELSE 1
END
, LEN(data3.rtoken)
)))
FROM (
SELECT
data2.name
, rtoken = REVERSE(
SUBSTRING(ptoken
, CHARINDEX('=', ptoken, 1) + 1
, LEN(data2.ptoken))
)
FROM (
SELECT
data.name
, ptoken = SUBSTRING(
data.tokens
, token_pos + name_length + 1
, ISNULL(ABS(next_token_pos - token_pos - name_length - 1), LEN(data.tokens))
)
FROM (
SELECT
sm3.tokens
, p.name
, name_length = LEN(p.name)
, token_pos = CHARINDEX(p.name, sm3.tokens)
, next_token_pos = CHARINDEX(p2.name, sm3.tokens)
FROM (
SELECT
sm2.[object_id]
, sm2.[type]
, tokens = REVERSE(SUBSTRING(sm2.tokens, ISNULL(CHARINDEX('SA', sm2.tokens) + 2, 0), LEN(sm2.tokens)))
FROM (
SELECT
sm.[object_id]
, o.[type]
, tokens = REVERSE(SUBSTRING(
sm.[definition]
, CHARINDEX(o.name, sm.[definition]) + LEN(o.name) + 1
, ABS(CHARINDEX(N'AS', sm.[definition]))
)
)
FROM sys.sql_modules sm WITH (NOLOCK)
JOIN sys.objects o WITH (NOLOCK) ON sm.[object_id] = o.[object_id]
JOIN sys.schemas s WITH (NOLOCK) ON o.[schema_id] = s.[schema_id]
WHERE o.[type] = 'P '
AND s.name + '.' + o.name = 'dbo.Sample1'
) sm2
WHERE sm2.tokens LIKE '%=%'
) sm3
JOIN sys.parameters p WITH (NOLOCK) ON sm3.[object_id] = p.[object_id]
OUTER APPLY (
SELECT p2.name
FROM sys.parameters p2 WITH (NOLOCK)
WHERE p2.is_output = 0
AND sm3.[object_id] = p2.[object_id]
AND p.parameter_id + 1 = p2.parameter_id
) p2
WHERE p.is_output = 0
) data
) data2
WHERE data2.ptoken LIKE '%=%'
) data3
然而,对于一个期望可以从系统视图中轻松查询的任务来说,它确实很难看。
我同意默认存储过程参数值应该通过 SQL 服务器目录视图公开。
T-SQL 解析方法在很多情况下可能有效,但很脆弱。考虑使用 TransactSQL ScriptDOM。下面是一个混合使用 PowerShell 和 C# 的示例。并不是说这对所有情况都是完美的,但它似乎处理了我迄今为止抛给它的所有参数。
我在此示例中使用了我的 SSMS 安装中的 Microsoft.SqlServer.TransactSql.ScriptDom.dll
程序集,但它可以从 NuGet Gallery 下载。
try
{
Add-type -LiteralPath @("C:\Program Files (x86)\Microsoft SQL Server0\Tools\Binn\ManagementStudio\Extensions\Application\Microsoft.SqlServer.TransactSql.ScriptDom.dll");
Add-type `
-ReferencedAssemblies @("C:\Program Files (x86)\Microsoft SQL Server0\Tools\Binn\ManagementStudio\Extensions\Application\Microsoft.SqlServer.TransactSql.ScriptDom.dll") `
-TypeDefinition @"
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SqlServer.TransactSql.ScriptDom;
using System.IO;
public static class ProcParser
{
public static List<StoredProcedureParameter> GetStoredProcedureParameters(string storedProcedureDefinition)
{
StringReader reader = new StringReader(storedProcedureDefinition);
var parser = new TSql140Parser(true);
IList<ParseError> errors;
TSqlFragment sqlFragment = parser.Parse(reader, out errors);
if (errors.Count > 0)
{
throw new Exception(`"Error parsing stored procedure definition`");
}
SQLVisitor sqlVisitor = new SQLVisitor();
sqlFragment.Accept(sqlVisitor);
return sqlVisitor.StoredProcedureParameters;
}
}
internal class SQLVisitor : TSqlFragmentVisitor
{
public List<StoredProcedureParameter> StoredProcedureParameters = new List<StoredProcedureParameter>();
public override void ExplicitVisit(ProcedureParameter node)
{
var p = StoredProcedureParameter.CreateProcedureParameter(node);
StoredProcedureParameters.Add(p);
}
}
public class StoredProcedureParameter
{
public string ParameterName;
public string ParameterType;
public string ParameterDirection = null;
public string DefaultParameterValue = null;
public static StoredProcedureParameter CreateProcedureParameter(ProcedureParameter node)
{
var param = new StoredProcedureParameter();
//parameter name
param.ParameterName = node.VariableName.Value;
//data type
switch (((ParameterizedDataTypeReference)node.DataType).Parameters.Count)
{
case 0:
if (node.DataType.Name.Identifiers.Count == 1)
{
param.ParameterType = node.DataType.Name.Identifiers[0].Value;
}
else
{
//schema-qualified type name
param.ParameterType = node.DataType.Name.Identifiers[0].Value + `".`" + node.DataType.Name.Identifiers[1].Value;
}
break;
case 1:
param.ParameterType = node.DataType.Name.Identifiers[0].Value + "(" + ((ParameterizedDataTypeReference)node.DataType).Parameters[0].Value + ")";
break;
case 2:
param.ParameterType = node.DataType.Name.Identifiers[0].Value + "(" + ((ParameterizedDataTypeReference)node.DataType).Parameters[0].Value + "," + ((ParameterizedDataTypeReference)node.DataType).Parameters[1].Value + ")";
break;
}
//default value
if (node.Value != null)
{
param.DefaultParameterValue = node.ScriptTokenStream[node.LastTokenIndex].Text;
}
//direction
if (node.Modifier == ParameterModifier.Output)
{
param.ParameterDirection = `"OUTPUT`";
}
else if (node.Modifier == ParameterModifier.ReadOnly)
{
param.ParameterDirection = `"READONLY`";
}
else
{
param.ParameterDirection = `"INPUT`";
}
return param;
}
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(ParameterName);
sb.Append(`" `");
sb.Append(ParameterType);
if (DefaultParameterValue != null)
{
sb.Append(`" `");
sb.Append(DefaultParameterValue);
}
sb.Append(`" `");
sb.Append(ParameterDirection);
return sb.ToString();
}
}
"@
}
catch [System.Reflection.ReflectionTypeLoadException]
{
Write-Host "Message: $($_.Exception.Message)"
Write-Host "StackTrace: $($_.Exception.StackTrace)"
Write-Host "LoaderExceptions: $($_.Exception.LoaderExceptions)"
throw;
}
Function Get-ProcText($connectionString, $procName)
{
$connection = New-Object System.Data.SqlClient.SqlConnection($connectionString);
$connection.Open();
$command = New-Object System.Data.SqlClient.SqlCommand("SELECT definition FROM sys.sql_modules WHERE object_id = OBJECT_ID(@ProcName);", $connection);
$procNameParameter = $command.Parameters.Add((New-Object System.Data.SqlClient.SqlParameter("@ProcName", [System.Data.SqlDbType]::NVarChar, 261)));
$procNameParameter.Value = $procName;
$procText = $command.ExecuteScalar();
$connection.Close();
return $procText;
}
############
### main ###
############
try {
# get proc text definition from database
$procText = Get-ProcText `
-connectionString "Data Source=.;Initial Catalog=tempdb;Integrated Security=SSPI" `
-procName "dbo.testproc";
# parse parameters from proc text
$procParameters = [ProcParser]::GetStoredProcedureParameters($procText);
# display parameter values
foreach($procParameter in $procParameters)
{
Write-Host "ParameterName=$($procParameter.ParameterName)";
Write-Host "`tParameterType=$($procParameter.ParameterType)";
Write-Host "`tDefaultParameterValue=$($procParameter.DefaultParameterValue)";
Write-Host "`tParameterDirection=$($procParameter.ParameterDirection)";
}
}
catch {
throw;
}
我创建了以下具有默认值的存储过程:
CREATE PROCEDURE [dbo].[Sample1]
@OrderID INT = 10285
AS
SELECT ProductName, OrderID
FROM Products P, [Order Details] Od
WHERE Od.ProductID = P.ProductID
AND Od.OrderID = @OrderID
尝试使用 sys.parameters
.
Select a.object_id, a.default_value
from sys.parameters a
inner join sys.types b on b.system_type_id = a.system_type_id
where Object_id = object_id('[dbo].[Sample1]')
但是我得到 NULL 作为 default_value
,而我期望 10285
作为 default_value
。
有没有办法获取默认值?
看起来 Microsoft has neglected this topic 并且没有找到参数默认值的简单方法,即使特定参数存在或不存在默认值也是如此:
As we all know, T-SQL stored procedure parameter defaults are not stored in sys.parameters, all_parameters, and system_parameters. They are also not exposed through sp_sproc_columns, sys.columns, or sp_procedure_params_rowset.
来自微软的反馈:
As posted by Tibor Karaszi, BOL document that "SQL Server only maintains default values for CLR objects in this catalog view; therefore, this column has a value of 0 for Transact-SQL objects. To view the default value of a parameter in a Transact-SQL object, query the definition column of the sys.sql_modules catalog view, or use the OBJECT_DEFINITION system function."
We dont store even the bit that indicating parameter is of default value in Yukon.
我已经测试了第一个代码片段 in this answer,它似乎适用于您的简单示例:
SELECT
data3.name
, [default_value] = REVERSE(RTRIM(SUBSTRING(
data3.rtoken
, CASE
WHEN CHARINDEX(N',', data3.rtoken) > 0
THEN CHARINDEX(N',', data3.rtoken) + 1
WHEN CHARINDEX(N')', data3.rtoken) > 0
THEN CHARINDEX(N')', data3.rtoken) + 1
ELSE 1
END
, LEN(data3.rtoken)
)))
FROM (
SELECT
data2.name
, rtoken = REVERSE(
SUBSTRING(ptoken
, CHARINDEX('=', ptoken, 1) + 1
, LEN(data2.ptoken))
)
FROM (
SELECT
data.name
, ptoken = SUBSTRING(
data.tokens
, token_pos + name_length + 1
, ISNULL(ABS(next_token_pos - token_pos - name_length - 1), LEN(data.tokens))
)
FROM (
SELECT
sm3.tokens
, p.name
, name_length = LEN(p.name)
, token_pos = CHARINDEX(p.name, sm3.tokens)
, next_token_pos = CHARINDEX(p2.name, sm3.tokens)
FROM (
SELECT
sm2.[object_id]
, sm2.[type]
, tokens = REVERSE(SUBSTRING(sm2.tokens, ISNULL(CHARINDEX('SA', sm2.tokens) + 2, 0), LEN(sm2.tokens)))
FROM (
SELECT
sm.[object_id]
, o.[type]
, tokens = REVERSE(SUBSTRING(
sm.[definition]
, CHARINDEX(o.name, sm.[definition]) + LEN(o.name) + 1
, ABS(CHARINDEX(N'AS', sm.[definition]))
)
)
FROM sys.sql_modules sm WITH (NOLOCK)
JOIN sys.objects o WITH (NOLOCK) ON sm.[object_id] = o.[object_id]
JOIN sys.schemas s WITH (NOLOCK) ON o.[schema_id] = s.[schema_id]
WHERE o.[type] = 'P '
AND s.name + '.' + o.name = 'dbo.Sample1'
) sm2
WHERE sm2.tokens LIKE '%=%'
) sm3
JOIN sys.parameters p WITH (NOLOCK) ON sm3.[object_id] = p.[object_id]
OUTER APPLY (
SELECT p2.name
FROM sys.parameters p2 WITH (NOLOCK)
WHERE p2.is_output = 0
AND sm3.[object_id] = p2.[object_id]
AND p.parameter_id + 1 = p2.parameter_id
) p2
WHERE p.is_output = 0
) data
) data2
WHERE data2.ptoken LIKE '%=%'
) data3
然而,对于一个期望可以从系统视图中轻松查询的任务来说,它确实很难看。
我同意默认存储过程参数值应该通过 SQL 服务器目录视图公开。
T-SQL 解析方法在很多情况下可能有效,但很脆弱。考虑使用 TransactSQL ScriptDOM。下面是一个混合使用 PowerShell 和 C# 的示例。并不是说这对所有情况都是完美的,但它似乎处理了我迄今为止抛给它的所有参数。
我在此示例中使用了我的 SSMS 安装中的 Microsoft.SqlServer.TransactSql.ScriptDom.dll
程序集,但它可以从 NuGet Gallery 下载。
try
{
Add-type -LiteralPath @("C:\Program Files (x86)\Microsoft SQL Server0\Tools\Binn\ManagementStudio\Extensions\Application\Microsoft.SqlServer.TransactSql.ScriptDom.dll");
Add-type `
-ReferencedAssemblies @("C:\Program Files (x86)\Microsoft SQL Server0\Tools\Binn\ManagementStudio\Extensions\Application\Microsoft.SqlServer.TransactSql.ScriptDom.dll") `
-TypeDefinition @"
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SqlServer.TransactSql.ScriptDom;
using System.IO;
public static class ProcParser
{
public static List<StoredProcedureParameter> GetStoredProcedureParameters(string storedProcedureDefinition)
{
StringReader reader = new StringReader(storedProcedureDefinition);
var parser = new TSql140Parser(true);
IList<ParseError> errors;
TSqlFragment sqlFragment = parser.Parse(reader, out errors);
if (errors.Count > 0)
{
throw new Exception(`"Error parsing stored procedure definition`");
}
SQLVisitor sqlVisitor = new SQLVisitor();
sqlFragment.Accept(sqlVisitor);
return sqlVisitor.StoredProcedureParameters;
}
}
internal class SQLVisitor : TSqlFragmentVisitor
{
public List<StoredProcedureParameter> StoredProcedureParameters = new List<StoredProcedureParameter>();
public override void ExplicitVisit(ProcedureParameter node)
{
var p = StoredProcedureParameter.CreateProcedureParameter(node);
StoredProcedureParameters.Add(p);
}
}
public class StoredProcedureParameter
{
public string ParameterName;
public string ParameterType;
public string ParameterDirection = null;
public string DefaultParameterValue = null;
public static StoredProcedureParameter CreateProcedureParameter(ProcedureParameter node)
{
var param = new StoredProcedureParameter();
//parameter name
param.ParameterName = node.VariableName.Value;
//data type
switch (((ParameterizedDataTypeReference)node.DataType).Parameters.Count)
{
case 0:
if (node.DataType.Name.Identifiers.Count == 1)
{
param.ParameterType = node.DataType.Name.Identifiers[0].Value;
}
else
{
//schema-qualified type name
param.ParameterType = node.DataType.Name.Identifiers[0].Value + `".`" + node.DataType.Name.Identifiers[1].Value;
}
break;
case 1:
param.ParameterType = node.DataType.Name.Identifiers[0].Value + "(" + ((ParameterizedDataTypeReference)node.DataType).Parameters[0].Value + ")";
break;
case 2:
param.ParameterType = node.DataType.Name.Identifiers[0].Value + "(" + ((ParameterizedDataTypeReference)node.DataType).Parameters[0].Value + "," + ((ParameterizedDataTypeReference)node.DataType).Parameters[1].Value + ")";
break;
}
//default value
if (node.Value != null)
{
param.DefaultParameterValue = node.ScriptTokenStream[node.LastTokenIndex].Text;
}
//direction
if (node.Modifier == ParameterModifier.Output)
{
param.ParameterDirection = `"OUTPUT`";
}
else if (node.Modifier == ParameterModifier.ReadOnly)
{
param.ParameterDirection = `"READONLY`";
}
else
{
param.ParameterDirection = `"INPUT`";
}
return param;
}
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(ParameterName);
sb.Append(`" `");
sb.Append(ParameterType);
if (DefaultParameterValue != null)
{
sb.Append(`" `");
sb.Append(DefaultParameterValue);
}
sb.Append(`" `");
sb.Append(ParameterDirection);
return sb.ToString();
}
}
"@
}
catch [System.Reflection.ReflectionTypeLoadException]
{
Write-Host "Message: $($_.Exception.Message)"
Write-Host "StackTrace: $($_.Exception.StackTrace)"
Write-Host "LoaderExceptions: $($_.Exception.LoaderExceptions)"
throw;
}
Function Get-ProcText($connectionString, $procName)
{
$connection = New-Object System.Data.SqlClient.SqlConnection($connectionString);
$connection.Open();
$command = New-Object System.Data.SqlClient.SqlCommand("SELECT definition FROM sys.sql_modules WHERE object_id = OBJECT_ID(@ProcName);", $connection);
$procNameParameter = $command.Parameters.Add((New-Object System.Data.SqlClient.SqlParameter("@ProcName", [System.Data.SqlDbType]::NVarChar, 261)));
$procNameParameter.Value = $procName;
$procText = $command.ExecuteScalar();
$connection.Close();
return $procText;
}
############
### main ###
############
try {
# get proc text definition from database
$procText = Get-ProcText `
-connectionString "Data Source=.;Initial Catalog=tempdb;Integrated Security=SSPI" `
-procName "dbo.testproc";
# parse parameters from proc text
$procParameters = [ProcParser]::GetStoredProcedureParameters($procText);
# display parameter values
foreach($procParameter in $procParameters)
{
Write-Host "ParameterName=$($procParameter.ParameterName)";
Write-Host "`tParameterType=$($procParameter.ParameterType)";
Write-Host "`tDefaultParameterValue=$($procParameter.DefaultParameterValue)";
Write-Host "`tParameterDirection=$($procParameter.ParameterDirection)";
}
}
catch {
throw;
}