|
|
Comments and Discussions
|
|
 |

|
Article is very good. Objective of this note is just to make solution more accurate, So please take it positively.
It's having couple of issues identified as follows.
#1) e.g. Consider SP parameters sequence is as follows and try to find default value of '@P' parameter.
.....
@P11 varchar(100) = 'p3',
@P1 varchar(100) = 'p2',
@P varchar(100) = 'p1',....
Here it returns the result = 'p3' which is incorrect.
#2) e.g. Consider SP parameter statement written in worst forms as explained below.
...
@P
varchar(100) = 'p1'
...
OR
...
@P varchar(100)
=
'p1'
...
----------------------------------------------------
I have corrected both issues and redesigned it with our coding conventions and readable remarks.
----------------------------------------------------
CREATE PROCEDURE DU_CG_spGetSPParameterDefault_Private
(
@SPName varchar(100),
@SPParameterName varchar(100),
@ProcessingStatus int Out, @DefaultValue varchar(100) Out
)
As
Begin
Set @SPName = LTrim(RTrim(@SPName))
Declare @Version int
Declare @StartPosition int
Set @StartPosition = CharIndex(';', @SPName)
If @StartPosition > 0 Begin
Set @Version = Substring(@SPName, @StartPosition + 1, 1)
Set @SPName = Left(@SPName, Len(@SPName) - 2)
End
Else
Set @Version = 1
Declare @ObjectID int
Set @ObjectID = Object_ID(@SPName)
If @ObjectID Is Null
Begin
Set @ProcessingStatus = 0 Print 'Requested SP ''' + @SPName + ''' doesn''t exist.'
Return
End
Set @DefaultValue = Null
Declare @SQL nvarchar(4000)
Declare @Text varchar(8000)
Declare @ParameterDefinition nvarchar(500)
Set @SQL = N'Select @Text_OUT = Text ' +
'From SysComments ' +
'Where ID = @Object_ID And ColID = 1 And Number = @Vers'
Set @ParameterDefinition = N'@Object_ID int,
@Vers int,
@Text_OUT varchar(4000) Out'
Exec sp_executesql @SQL, @ParameterDefinition,
@Object_ID = @ObjectID,
@Vers = @Version,
@Text_OUT = @Text Out
Declare @IndexOfParamWithSpace int
Set @IndexOfParamWithSpace = PatIndex('%' + @SPParameterName + Char(32) + '%', @Text)
Declare @IndexOfParamWithTab int
Set @IndexOfParamWithTab = PatIndex('%' + @SPParameterName + Char(9) + '%', @Text)
Declare @IndexOfParamWithCRLF int
Set @IndexOfParamWithCRLF = PatIndex('%' + @SPParameterName + Char(13) + Char(10) + '%', @Text)
Declare @NearestPositionIndex int
Select @NearestPositionIndex = Min(PositionIndex)
From
(
Select @IndexOfParamWithSpace As PositionIndex
Union
Select @IndexOfParamWithTab As PositionIndex
Union
Select @IndexOfParamWithCRLF As PositionIndex
) As tblUnion
Where IsNull(tblUnion.PositionIndex, 0) <> 0
Set @NearestPositionIndex = IsNull(@NearestPositionIndex, 0)
Set @StartPosition = @NearestPositionIndex
If @StartPosition <= 0
Begin
Set @ProcessingStatus = -1 Set @DefaultValue = Null
Return
End
Set @Text = Right(@Text, Len(@Text) - (@StartPosition + 1))
Set @StartPosition = PatIndex('%=%', @Text) If @StartPosition <= 0 Begin
Set @ProcessingStatus = -2 Set @DefaultValue = Null
Return
End
Set @ProcessingStatus = 1 Set @DefaultValue = dbo.DU_CG_fnLTrimNewLineTabs(LTrim(RTrim(Right(@Text, Len(@Text) - (@StartPosition)))))
Declare @EndPosition int
Set @EndPosition = CharIndex('--', @DefaultValue)
If @EndPosition > 0 Set @DefaultValue = RTrim(Left(@DefaultValue, @EndPosition - 1))
Set @EndPosition= CharIndex(',', @DefaultValue) If @EndPosition > 0 Set @DefaultValue = RTrim(Left(@DefaultValue, @EndPosition - 1))
Set @DefaultValue = dbo.DU_CG_fnRTrimNewLineTabs(@DefaultValue)
End
GO
----------------------------------------------------
|
|
|
|

|
This article saved our valuable time.
Thanks.
|
|
|
|

|
This is what I did to get it. grab the section of the stored procedure starting with the first parameter up to the AS statement. Created a temporary stored procedure with the declare statements and returning union all of all the parameter ids, names, column types, if they have default, and their value. And then executed the stored procedure with assumption of if there is a equal sign between parameters they have default, and if they don't have default I passed null to the parameter during execution, and either read the resultset or if exists the stored procedure populated a temporary table so that I can query it later. I checked if there is any equal signs between parameters and if yes initially I assumed they have defaults. If there is a comment etc with equal sign the procedure that means they did not have default and during execution I did not pass any parameter, the execution failed, I caught the error message, read the parameter name and executed the procedure this time I passed null to the parameter. In the procedure I used a CLR string concat function, for that reason it won't compile if you execute directly, but you can probably replace with XML path or so, or email me back I can guide you through the clr if you want to. Since I did union all the parameters I casted them as varchar(max)<pre lang="SQL">
USE Util
GO
CREATE AGGREGATE [dbo].[StringConcat]
(@Value nvarchar(MAX), @Delimiter nvarchar(100))
RETURNS nvarchar(MAX)
EXTERNAL NAME [UtilClr].[UtilClr.Concat]
GO
CREATE FUNCTION dbo.GetColumnType (@TypeName SYSNAME,
@MaxLength SMALLINT,
@Precision TINYINT,
@Scale TINYINT,
@Collation SYSNAME,
@DBCollation SYSNAME)
RETURNS TABLE
AS
RETURN
SELECT CAST(CASE WHEN @TypeName IN ('char', 'varchar')
THEN @TypeName + '(' + CASE WHEN @MaxLength = -1 THEN 'MAX'
ELSE CAST(@MaxLength AS VARCHAR)
END + ')' + CASE WHEN @Collation <> @DBCollation THEN ' COLLATE ' + @Collation
ELSE ''
END
WHEN @TypeName IN ('nchar', 'nvarchar')
THEN @TypeName + '(' + CASE WHEN @MaxLength = -1 THEN 'MAX'
ELSE CAST(@MaxLength / 2 AS VARCHAR)
END + ')' + CASE WHEN @Collation <> @DBCollation THEN ' COLLATE ' + @Collation
ELSE ''
END
WHEN @TypeName IN ('binary', 'varbinary') THEN @TypeName + '(' + CASE WHEN @MaxLength = -1 THEN 'MAX'
ELSE CAST(@MaxLength AS VARCHAR)
END + ')'
WHEN @TypeName IN ('bigint', 'int', 'smallint', 'tinyint') THEN @TypeName
WHEN @TypeName IN ('datetime2', 'time', 'datetimeoffset') THEN @TypeName + '(' + CAST (@Scale AS VARCHAR) + ')'
WHEN @TypeName IN ('numeric', 'decimal') THEN @TypeName + '(' + CAST(@Precision AS VARCHAR) + ', ' + CAST(@Scale AS VARCHAR) + ')'
ELSE @TypeName
END AS VARCHAR(256)) AS ColumnType
GO
go
USE [master]
GO
IF OBJECT_ID('dbo.sp_ParamDefault') IS NULL
EXEC('CREATE PROCEDURE dbo.sp_ParamDefault AS SELECT 1 AS ID')
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE dbo.sp_ParamDefault
@ProcName SYSNAME = NULL OUTPUT
AS
SET NOCOUNT ON
SET ANSI_WARNINGS OFF
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
DECLARE @SQL VARCHAR(MAX),
@ObjectId INT = OBJECT_ID(LTRIM(RTRIM(@ProcName))),
@FirstParam VARCHAR(256),
@LastParam VARCHAR(256),
@SelValues VARCHAR(MAX),
@ExecString VARCHAR(MAX),
@WhiteSpace VARCHAR(10) = '[' + CHAR(10) + CHAR(13) + CHAR(9) + CHAR(32) + ']',
@TableExists BIT = ABS(SIGN(ISNULL(OBJECT_ID('tempdb..#sp_ParamDefault'), 0))),
@DeclareSQL VARCHAR(MAX),
@ErrorId INT,
@ErrorStr VARCHAR(MAX)
IF @ObjectId IS NULL
BEGIN
SET @ProcName = NULL
PRINT '/* -- SILENCE OPERATION --
IF OBJECT_ID(''tempdb..#sp_ParamDefault'') IS NOT NULL DROP TABLE #sp_ParamDefault
CREATE TABLE #sp_ParamDefault (Id INT, NAME VARCHAR(256), TYPE VARCHAR(256), HasDefault BIT, IsOutput BIT, VALUE VARCHAR(MAX))
*/
EXEC dbo.sp_ParamDefault
@ProcName = NULL
'
RETURN
END
SELECT @SQL = definition,
@ProcName = QUOTENAME(OBJECT_SCHEMA_NAME(@ObjectId)) + '.' + QUOTENAME(OBJECT_NAME(@ObjectId)),
@FirstParam = FirstParam,
@LastParam = LastParam
FROM sys.all_sql_modules m (NOLOCK)
CROSS APPLY (SELECT MAX(CASE WHEN p.parameter_id = 1 THEN p.name
END) AS FirstParam,
Util.dbo.StringConcat(p.name, '%') AS Params
FROM sys.parameters p (NOLOCK)
WHERE p.object_id = m.OBJECT_ID) p
CROSS APPLY (SELECT TOP 1
p.NAME AS LastParam
FROM sys.parameters p (NOLOCK)
WHERE p.object_id = m.OBJECT_ID
ORDER BY parameter_id DESC) l
WHERE m.object_id = @ObjectId
IF @FirstParam IS NULL
BEGIN
IF @TableExists = 0
SELECT CAST(NULL AS INT) AS Id,
CAST(NULL AS VARCHAR(256)) AS Name,
CAST(NULL AS VARCHAR(256)) AS Type,
CAST(NULL AS BIT) AS HasDefault,
CAST(NULL AS VARCHAR(MAX)) AS VALUE
WHERE 1 = 2
RETURN
END
SELECT @DeclareSQL = SUBSTRING(@SQL, 1, lst + AsFnd + 2) + '
'
FROM (SELECT PATINDEX ('%' + @WhiteSpace + @LastParam + @WhiteSpace + '%', @SQL) AS Lst) l
CROSS APPLY (SELECT SUBSTRING (@SQL, lst, LEN (@SQL)) AS SQL2) s2
CROSS APPLY (SELECT PATINDEX ('%' + @WhiteSpace + 'AS' + @WhiteSpace + '%', SQL2) AS AsFnd) af
DECLARE @ParamTable TABLE (Id INT NOT NULL,
NAME SYSNAME NULL,
TYPE VARCHAR(256) NULL,
HasDefault BIGINT NULL,
IsOutput BIT NOT NULL,
TypeName SYSNAME NOT NULL) ;
WITH pr
AS (SELECT p.NAME COLLATE SQL_Latin1_General_CP1_CI_AS AS ParameterName,
p.Parameter_id,
t.NAME COLLATE SQL_Latin1_General_CP1_CI_AS AS TypeName,
ct.ColumnType,
MAX(Parameter_id) OVER (PARTITION BY (SELECT 0)) AS MaxParam,
p.is_output
FROM sys.parameters p (NOLOCK)
INNER JOIN sys.types t (NOLOCK) ON t.user_type_id = p.user_type_id
INNER JOIN sys.databases AS db (NOLOCK) ON db.database_id = DB_ID()
CROSS APPLY Util.dbo.GetColumnType(t.name, p.max_length, p.precision, p.scale, db.collation_name, db.collation_name) ct
WHERE OBJECT_ID = @ObjectId)
INSERT @ParamTable
(Id,
NAME,
TYPE,
HasDefault,
IsOutput,
TypeName)
SELECT Parameter_id AS Id,
ParameterName AS NAME,
ColumnType AS TYPE,
HasDefault,
is_output AS IsOutput,
TypeName
FROM pr a
CROSS APPLY (SELECT ISNULL('%' + (SELECT Util.dbo.StringConcat (ParameterName, '%') FROM pr b WHERE b.parameter_id < a.parameter_id), '') + '%'
+ ParameterName + '%=' + '%' + CASE WHEN parameter_id = MaxParam THEN @WhiteSpace + 'AS' + @WhiteSpace + '%'
ELSE (SELECT Util.dbo.StringConcat (ParameterName, '%') FROM pr b
WHERE b.parameter_id > a.parameter_id) + '%'
END AS ptt) b
CROSS APPLY (SELECT SIGN (PATINDEX (ptt, @DeclareSQL)) AS HasDefault) hd
AGAIN:
SELECT @SelValues = CASE WHEN @TableExists = 1 THEN 'INSERT #sp_ParamDefault(Id, Name, Type, HasDefault, IsOutput, Value)
' ELSE ''
END + 'SELECT * FROM (VALUES' + Util.dbo.StringConcat('(' + CAST(Id AS VARCHAR) + ', ''' + Name + ''', ''' + Type + ''', '
+ CAST(HasDefault AS VARCHAR) + ', ' + CAST(IsOutput AS VARCHAR) + ', '
+ CASE WHEN TypeName NOT LIKE '%char%' THEN 'CAST(' + name + ' AS VARCHAR(MAX))'
ELSE name
END + ')', ',
') + '
) d(Id, Name, Type, HasDefault, IsOutput, Value)',
@ExecString = 'EXEC #sp_ParamDefaultProc
' + ISNULL(Util.dbo.StringConcat(CASE WHEN HasDefault = 0 THEN Name + ' = NULL'
END, ',
'), '')
FROM @ParamTable
SET @SQL = 'CREATE PROCEDURE #sp_ParamDefaultProc
' + SUBSTRING(@DeclareSQL, CHARINDEX(@FirstParam, @DeclareSQL), LEN(@DeclareSQL)) + '
' + @SelValues
IF OBJECT_ID('TEMPDB..#sp_ParamDefaultProc') IS NOT NULL
DROP PROCEDURE #sp_ParamDefaultProc
EXEC(@SQL)
BEGIN TRY
EXEC(@ExecString)
END TRY
BEGIN CATCH
SELECT @ErrorStr = ERROR_MESSAGE(),
@ErrorId = ERROR_NUMBER()
UPDATE p
SET HasDefault = 0
FROM (SELECT PATINDEX ('%expects parameter ''@%', @ErrorStr) AS ii) i
CROSS APPLY (SELECT CHARINDEX ('''', @ErrorStr, ii + 20) AS uu) u
INNER JOIN @ParamTable p ON p.name = SUBSTRING(@ErrorStr, ii + 19, uu - ii - 19)
WHERE ii > 0
IF @@ROWCOUNT > 0
GOTO AGAIN
RAISERROR(@ErrorStr, 16, 1)
RETURN 30
END CATCH
GO
EXEC sys.sp_MS_marksystemobject
sp_ParamDefault
GO
|
|
|
|

|
The stored procedure for parsing the parameters assumes (I think) a certain standard format of the stored procedure "declaration." Each parameter needs to be listed on a separate line, as is done for the procedure itself in the article.
I happen to think that this standardized approach to declaring a stored procedure is good and, in fact, is slowly being adopted at one of my clients. There are still those old style formats out there, though, and this trips up the parsing logic, I believe.
There are perhaps other issues associated with parsing. For example, if I had a two parameters, one named SubDirectory followed by one named Directory, I'm not so sure the second one would ever be parsed. Granted, it seems a little unlikely that you would use one parameter whose name is a substring of the other, but it can happen in situations where you least expect it.
Perhaps an improvement would be to move the parsing logic into C# where there seems to be a larger text processing toolkit available to you.
|
|
|
|

|
Quoted from above:
"Each parameter needs to be listed on a separate line, "
The parser works for me even when multiple parameters are on the same line.
Thanks Rafael Mizrahi and Laurence Moroney ... great little gadget you wrote!!!
The Corner Analyst
|
|
|
|

|
This is just a warning. We load all of our stored procedures in encrypted mode (yes, ABAPerl would help us manage this so that development environments had unencrypted while production had encrypted versions), so the text of the stored procedure comes back as NULL and this doesn't work. It's a pity SQL Server doesn't store its default parameter values so they can be returned in COLUMN_DEF.
|
|
|
|

|
Eva Zadoyen is my new heroine. I have wanted to be able to do that for so long. And now I can! Thanks Rafael and Laurence for introducing me to this great sproc!
|
|
|
|
 |
|
|
General News Suggestion Question Bug Answer Joke Rant Admin
|
When working with Microsoft SQL JDBC driver, you cannot invoke a stored procedure without specifying all the parameters, including the optional parameters that have default values. Learn how to fix that problem here.
| Type | Article |
| Licence | |
| First Posted | 2 Feb 2006 |
| Views | 45,479 |
| Bookmarked | 22 times |
|
|