SQL SubString from Special Kind of ntext column - sql

I have a DB table with the column of type ntext filled with the values like
A<1,?,'attrdisplay'=A<1,?,'1361147_2'='2','1361147_3'='3','1361147_4'='4','1361147_5'='5','1361147_6'='6','1361147_7'='1'>,'ClassificationInheritance'=A<1,?,'DisabledIds'=A<1,?>>,'Confidential'=0,'CreationNotification'=A<1,?,'mail'='Ärende har skapats','recipients'=A<1,?,1414822=-1,1414823=-1,1414824=-1,1414825=-1,1414826=-1,1414827=-1,1415811=-1>>,'IsSubBinder'=1,'name'=A<1,?,'fullname'='Ärendemall5','mlNames'=A<1,?,'sv'='Ärendemall5'>,'name'='Ärendemall5','nameFormat'=':name:','ok'=true,'refnr'='SJCM-2013-00014'>,'showDocumentsFirst'=true,'WorkItem'=A<1,?,'id'=-1,'Name'=''>>
I have to extract only this value SJCM-2013-00014 for each such row in the table.
Any idea how I can do that ?
Thank you in advance!

I guess what you really want is to search for and extract the refnr? If you have the possibility to add CLR-functions to the server, in order to be able to use regular expressions, this is by far the easiest way:
CREATE TABLE #test (data ntext)
INSERT INTO #test VALUES('A<1,?,''attrdisplay''=A<1,?,''1361147_2''=''2'',''1361147_3''=''3'',''1361147_4''=''4'',''1361147_5''=''5'',''1361147_6''=''6'',''1361147_7''=''1''>,''ClassificationInheritance''=A<1,?,''DisabledIds''=A<1,?>>,''Confidential''=0,''CreationNotification''=A<1,?,''mail''=''Ärende har skapats'',''recipients''=A<1,?,1414822=-1,1414823=-1,1414824=-1,1414825=-1,1414826=-1,1414827=-1,1415811=-1>>,''IsSubBinder''=1,''name''=A<1,?,''fullname''=''Ärendemall5'',''mlNames''=A<1,?,''sv''=''Ärendemall5''>,''name''=''Ärendemall5'',''nameFormat''='':name:'',''ok''=true,''refnr''=''SJCM-2013-00014''>,''showDocumentsFirst''=true,''WorkItem''=A<1,?,''id''=-1,''Name''=''''>>')
SELECT
*,
[dbo].[RegexMatchGroupClr](
CAST(data as nvarchar(max)),
'refnr''=''(?<refnr>[^'']+)''',
'refnr'
)
FROM
#test
This is done using regex in a CLR-function that looks like this in C#:
[SqlFunction]
public static SqlChars RegexMatchGroupClr(SqlChars input, string pattern, string groupName)
{
if (input.IsNull) return input;
string inputString = new string(input.Value);
rx.Match match = rx.Regex.Match(inputString, pattern);
if (match.Success && match.Groups[groupName].Success)
{
string resultString = match.Groups[groupName].Value;
SqlChars result = new SqlChars(resultString.ToCharArray());
return result;
}
return null;
}
If you don't have the possibility to add CLR-functions, it's still doable, but way more cumbersome:
CREATE TABLE #test (data ntext)
INSERT INTO #test VALUES('A<1,?,''attrdisplay''=A<1,?,''1361147_2''=''2'',''1361147_3''=''3'',''1361147_4''=''4'',''1361147_5''=''5'',''1361147_6''=''6'',''1361147_7''=''1''>,''ClassificationInheritance''=A<1,?,''DisabledIds''=A<1,?>>,''Confidential''=0,''CreationNotification''=A<1,?,''mail''=''Ärende har skapats'',''recipients''=A<1,?,1414822=-1,1414823=-1,1414824=-1,1414825=-1,1414826=-1,1414827=-1,1415811=-1>>,''IsSubBinder''=1,''name''=A<1,?,''fullname''=''Ärendemall5'',''mlNames''=A<1,?,''sv''=''Ärendemall5''>,''name''=''Ärendemall5'',''nameFormat''='':name:'',''ok''=true,''refnr''=''SJCM-2013-00014''>,''showDocumentsFirst''=true,''WorkItem''=A<1,?,''id''=-1,''Name''=''''>>')
DECLARE #SearchFor varchar(10) = 'refnr''=''';
DECLARE #EndsWith varchar(10) = '''';
WITH converted AS (
SELECT
CAST(data as nvarchar(max)) as data
FROM
#test
), startPos AS (
SELECT
*
,CHARINDEX(#SearchFor, data) + LEN(#SearchFor) as startPos
FROM
converted
), endPos AS (
SELECT
*
,CHARINDEX(#EndsWith, data, startPos) as endPos
FROM
startPos
)
SELECT
*
,SUBSTRING(data, startPos, endPos - startPos) as refnr
FROM
endPos
And if you can't use Common Table Expressions, it gets even more unreadable:
CREATE TABLE #test (data ntext)
INSERT INTO #test VALUES('A<1,?,''attrdisplay''=A<1,?,''1361147_2''=''2'',''1361147_3''=''3'',''1361147_4''=''4'',''1361147_5''=''5'',''1361147_6''=''6'',''1361147_7''=''1''>,''ClassificationInheritance''=A<1,?,''DisabledIds''=A<1,?>>,''Confidential''=0,''CreationNotification''=A<1,?,''mail''=''Ärende har skapats'',''recipients''=A<1,?,1414822=-1,1414823=-1,1414824=-1,1414825=-1,1414826=-1,1414827=-1,1415811=-1>>,''IsSubBinder''=1,''name''=A<1,?,''fullname''=''Ärendemall5'',''mlNames''=A<1,?,''sv''=''Ärendemall5''>,''name''=''Ärendemall5'',''nameFormat''='':name:'',''ok''=true,''refnr''=''SJCM-2013-00014''>,''showDocumentsFirst''=true,''WorkItem''=A<1,?,''id''=-1,''Name''=''''>>')
DECLARE #SearchFor varchar(10) = 'refnr''=''';
DECLARE #EndsWith varchar(10) = '''';
SELECT
*
,SUBSTRING(data, (CHARINDEX(#SearchFor, data) + LEN(#SearchFor)), (CHARINDEX(#EndsWith, data, (CHARINDEX(#SearchFor, data) + LEN(#SearchFor)))) - (CHARINDEX(#SearchFor, data) + LEN(#SearchFor))) as refnr
FROM
#test

Related

Converting comma-separated value to in subquery

I have following query :
Set #OrderStatuses = 'Cancelled,Complete'
Select *
From TableName
Where Status in (#OrderStatuses)
The Status is what I am receiving externally and it fails to fetch any result as, what we actually need to process is:
Select *
From TableName
Where Status in ('Cancelled', 'Complete');
#OrderStatuses can contain any number of comma-separated values.
One method is dynamic SQL:
declare #sql nvarchar(max);
set #sql = 'Select * from TableName Where Status in (#list)';
set #sql = replace(#sql, '#list', '#OrderStatuses');
exec sp_executesql #sql;
Note: You cannot pass a list in as a parameter.
You can also use like:
Select *
from TableName
Where ',' + #OrderStatuses + ',' like '%,' + Status + ',%';
However, this cannot use an index for the the comparison.
You will need to use split string function and process the rest..
;with cte
as
(
select * from split_strings(#orderstatus,',')
)
select * from table where status in (select item from cte)
You must add the list as a Table-Valued Parameter
Here are two methods I use to convert any IEnumerable list of items (in this case, integers) into a Table Valued Parameter. You have to have created/defined a User Defined type (UDT) for the resultant table in your database. In the example below the UDT is named dbo.keyIds and is created (once) with this SQL:
CREATE TYPE [dbo].[KeyIds]
AS TABLE(pkId int NOT NULL, PRIMARY KEY CLUSTERED
( [pkId] ASC) WITH (IGNORE_DUP_KEY = OFF)
)
The c# code is:
public class DbParamList : List<IDbDataParameter>
{
public void AddSQLTableParm<T>(
string parmName, IEnumerable<T> values)
{
var parm = new SqlParameter(parmName, CreateDataTable(values))
{
SqlDbType = SqlDbType.Structured,
TypeName = "dbo.keyIds"
};
Add(parm);
}
internal static DataTable CreateDataTable<T>(IEnumerable<T> values)
{
var dt = new DataTable();
var props = typeof (T).GetProperties();
if (props.Length > 0)
{
foreach (var col in props)
dt.Columns.Add(col.Name, col.PropertyType);
foreach (var id in values)
{
var newRow = dt.NewRow();
foreach (var prop in id.GetType().GetProperties())
newRow[prop.Name] = prop.GetValue(id, null);
dt.Rows.Add(newRow);
}
}
else
{
dt.Columns.Add("ids");
foreach (var id in values) dt.Rows.Add(id);
}
return dt;
}
}

Script to check changes on SQL Server Instance?

Is there any SQL script to check the changes on SQL Server instance made by other DB admin and i get email alert of that changes. if yes, then please provide me the script and all the steps for applying it. I am using SQL Server 2012.
One solution is to use a DDL trigger to catch all schema changes (procedures, functions, table definition etc.). This can work for all non-encrypted objects and of course, other admins must not disable it.
More details about how to write such a trigger and persist changes can be found here.
[Later edit]
I remembered I have created such an audit on the development environment and I can provide a custom version based on the indicated article. Besides usual information, I have also included a "distance" between old object text and new object text to have a basic idea about change magnitude.
1) Table definition:
IF OBJECT_ID('dbo.DDLEvents', 'U') IS NULL
BEGIN
CREATE TABLE dbo.DDLEvents
(
EventId INT IDENTITY(1, 1) NOT NULL,
EventDate DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
EventType NVARCHAR(64),
EventXML XML,
DatabaseName NVARCHAR(255),
SchemaName NVARCHAR(255),
ObjectName NVARCHAR(255),
HostName VARCHAR(128),
IPAddress VARCHAR(32),
ProgramName NVARCHAR(255),
LoginName NVARCHAR(255),
ObjectDefinition NVARCHAR(MAX),
LastObjDefinition NVARCHAR(MAX),
Diff INT -- edit distance between last and current object version (gives an idea of how much was changed in the object)
);
create index IDX_DDLEvents_Object ON DDLEvents (SchemaName, ObjectName)
END
go
2) Initial text for existing objects:
IF NOT EXISTS(SELECT * FROM dbo.DDLEvents)
BEGIN
INSERT INTO dbo.DDLEvents
(EventType, DatabaseName, SchemaName, ObjectName, LoginName, ObjectDefinition)
SELECT 'CREATE_PROCEDURE', DB_NAME(), OBJECT_SCHEMA_NAME([object_id]), OBJECT_NAME([object_id]), 'SYSTEM', OBJECT_DEFINITION([object_id])
FROM sys.procedures;
INSERT INTO dbo.DDLEvents
(EventType, DatabaseName, SchemaName, ObjectName, LoginName, ObjectDefinition)
SELECT 'CREATE_VIEW', DB_NAME(), OBJECT_SCHEMA_NAME([object_id]), OBJECT_NAME([object_id]), 'SYSTEM', OBJECT_DEFINITION([object_id])
FROM sys.views;
INSERT INTO dbo.DDLEvents
(EventType, DatabaseName, SchemaName, ObjectName, LoginName, ObjectDefinition)
SELECT 'CREATE_FUNCTION', DB_NAME(), OBJECT_SCHEMA_NAME([object_id]), OBJECT_NAME([object_id]), 'SYSTEM', OBJECT_DEFINITION([object_id])
FROM sys.objects
-- scalar, inline table-valued, table-valued
WHERE type IN ('FN', 'IF', 'TF')
END
go
3) Distance function (CLR):
[Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = false)]
public static int Levenshtein(SqlString S1, SqlString S2)
{
if (S1.IsNull)
S1 = new SqlString("");
if (S2.IsNull)
S2 = new SqlString("");
int maxLen = 4096;
// keeping only the first part of the string (performance reasons)
String SC1 = S1.Value.ToUpper();
String SC2 = S2.Value.ToUpper();
if (SC1.Length > maxLen)
SC1 = SC1.Remove(maxLen);
if (SC2.Length > maxLen)
SC2 = SC2.Remove(maxLen);
int n = SC1.Length;
int m = SC2.Length;
short[,] d = new short[n + 1, m + 1];
int cost = 0;
if (n + m == 0)
{
return 0;
}
else if (n == 0)
{
return 0;
}
else if (m == 0)
{
return 0;
}
for (short i = 0; i <= n; i++)
d[i, 0] = i;
for (short j = 0; j <= m; j++)
d[0, j] = j;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (SC1[i - 1] == SC2[j - 1])
cost = 0;
else
cost = 1;
d[i, j] = (short) System.Math.Min(System.Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
}
}
// double percentage = System.Math.Round((1.0 - ((double)d[n, m] / (double)System.Math.Max(n, m))) * 100.0, 2);
// return percentage;
return d[n, m];
}
4) The DDL trigger definition
if not exists (select * from sys.triggers where name = 'DDL_Audit_Trigger')
exec ('create trigger DDL_Audit_Trigger ON DATABASE FOR CREATE_PROCEDURE AS BEGIN PRINT 1; END')
GO
ALTER TRIGGER [DDL_Audit_Trigger]
ON DATABASE
FOR
CREATE_ASSEMBLY, ALTER_ASSEMBLY, DROP_ASSEMBLY,
CREATE_PROCEDURE, ALTER_PROCEDURE, DROP_PROCEDURE,
CREATE_FUNCTION, ALTER_FUNCTION, DROP_FUNCTION,
CREATE_INDEX, ALTER_INDEX, DROP_INDEX,
CREATE_VIEW, ALTER_VIEW, DROP_VIEW,
CREATE_ROLE, ALTER_ROLE, DROP_ROLE,
CREATE_SCHEMA, ALTER_SCHEMA, DROP_SCHEMA,
CREATE_TABLE, ALTER_TABLE, DROP_TABLE,
CREATE_TYPE, DROP_TYPE,
CREATE_USER, ALTER_USER, DROP_USER,
CREATE_TRIGGER, ALTER_TRIGGER, DROP_TRIGGER,
RENAME
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
DECLARE #EventData XML = EVENTDATA();
DECLARE #ip VARCHAR(32) = ( SELECT client_net_address FROM sys.dm_exec_connections WHERE session_id = ##SPID);
DECLARE #ObjectSchema NVARCHAR(255) = #EventData.value('(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(255)')
DECLARE #ObjectName NVARCHAR(255) = #EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)')
-- DECLARE #ObjectFullName NVARCHAR(255) = #ObjectSchema + '.' + #ObjectName
DECLARE #CommandText NVARCHAR(MAX) = #EventData.value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)')
DECLARE #LastObjectChange DATETIME = (SELECT TOP 1 EventDate FROM dbo.DDLEvents where SchemaName = #ObjectSchema and ObjectName = #ObjectName ORDER BY EventDate DESC)
DECLARE #LastObjectDefinition NVARCHAR(MAX) = (SELECT TOP 1 ObjectDefinition FROM dbo.DDLEvents where SchemaName = #ObjectSchema and ObjectName = #ObjectName and EventDate = #LastObjectChange ORDER BY EventDate DESC)
INSERT INTO dbo.DDLEvents
(EventType,
EventXML, DatabaseName,
SchemaName, ObjectName,
HostName, IPAddress, ProgramName, LoginName,
ObjectDefinition, LastObjDefinition, Diff
)
SELECT #EventData.value('(/EVENT_INSTANCE/EventType)[1]', 'NVARCHAR(100)'),
#EventData, DB_NAME(),
#ObjectSchema, #ObjectName,
HOST_NAME(), #ip, PROGRAM_NAME(), SUSER_SNAME(),
#CommandText, #LastObjectDefinition, dbo.Levenshtein(#CommandText, #LastObjectDefinition);
END TRY
BEGIN CATCH
INSERT INTO dbo.DDLEventsLog (Error)
SELECT ERROR_MESSAGE()
END CATCH
END
GO

Aggregate replace in SQL Server?

What I'm trying to achieve is to make dynamic a series of replacements that have to be performed on a certain field. (To make things even easier, I want in fact to remove data, so I'll be always comparing with
Say that sometimes I will have to do just one replacement:
... REPLACE(myField, stringToRemove, '')
Sometimes, I will need two replacements:
... REPLACE(REPLACE(myField, stringToRemove, ''), anotherStringToRemove, '')
However, I need to make this dynamic and I do not know in advance how many of those values I'll have, and so, how many replacements (removals) I'll have to do.
I tried searching for aggregate string manipulation functions and, of course, there's none. I also know that this can be achieved through a CLR aggregate function but I don't have the possibility of using it.
Any ideas?
You can setup a table variable with FromValue and ToValue and use a while loop to do the replacements.
-- Table to replace in
declare #T table
(
Value varchar(50)
)
insert into #T values
('first second third'),
('first second third')
-- Table with strings to replace
declare #Rep table
(
ID int identity primary key,
FromValue varchar(50),
ToValue varchar(50)
)
insert into #Rep values
('second', 'fourth'),
('third', 'fifth')
declare #ID int
select #ID = max(ID)
from #Rep
while #ID > 0
begin
update #T
set Value = replace(Value, FromValue, ToValue)
from #Rep
where ID = #ID
set #ID -= 1
end
select *
from #T
Result:
Value
-------------------
first fourth fifth
first fourth fifth
If you only want to query the values you can do something like this.
;with C as
(
select 0 as ID,
Value,
0 as Lvl
from #T
union all
select R.ID,
cast(replace(C.Value, R.FromValue, R.ToValue) as varchar(50)),
Lvl + 1
from #Rep as R
inner join C
on C.ID + 1 = R.ID
)
select top 1 with ties Value
from C
order by Lvl desc
Once you implement the CLR aggregate function below, you can do:
SELECT dbo.ReplaceAgg(t.[text], w.badword, w.goodword) // call CLR aggregate function
FROM [Texts] t CROSS JOIN BadWords w
GROUP BY t.[text]
CLR aggregate function in C#
/// <summary>
/// Allows to apply regex-replace operations to the same string.
/// For example:
/// SELECT dbo.ReplaceAgg(t.[text], w.badpattern, "...")
/// FROM [Texts] t CROSS JOIN BadPatterns w
/// GROUP BY t.[text]
/// </summary>
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.UserDefined,
IsInvariantToDuplicates = true, IsInvariantToOrder = false,
IsInvariantToNulls = true, MaxByteSize = -1)]
public class RegexReplaceAgg : IBinarySerialize
{
private string str;
private string needle;
private string replacement;
public void Init()
{
str = null;
needle = null;
replacement = null;
}
public void Accumulate(SqlString haystack, SqlString needle, SqlString replacement)
{
// Null values are excluded from aggregate.
if (needle.IsNull) return;
if (replacement.IsNull) return;
if (haystack.IsNull) return;
str = str ?? haystack.Value;
this.needle = needle.Value;
this.replacement = replacement.Value;
str = Regex.Replace(str, this.needle, this.replacement, RegexOptions.Compiled | RegexOptions.CultureInvariant);
}
public void Merge(RegexReplaceAgg group)
{
Accumulate(group.Terminate(), new SqlString(needle), new SqlString(replacement));
}
public SqlString Terminate() => new SqlString(str);
public void Read(BinaryReader r)
{
str = r.ReadString();
needle = r.ReadString();
replacement = r.ReadString();
}
public void Write(BinaryWriter w)
{
w.Write(str);
w.Write(needle);
w.Write(replacement);
}
}
You might have to write a scalar function to which you pass the original string, and enough information for it to know which strings to remove, and have it loop through them and return the result of the set of replacements.

Is there a solution for getting the default value of the parameters of a given stored procedure?

I use INFORMATION_SCHEMA.PARAMETERS for getting information about a stored procedure parameter now. I need to know the default values of parameters. Is there a solution for getting the default value of the parameters of a given stored procedure?
Parse the SQL code if you are doing it via SQL commands...
The information isn't stored in a system table. From sys.parameters (where you'd expect it), has_default_value and default_value columns, we're told to parse SQL:
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.
If has_default_value is 1, the value of this column is the value of the default for the parameter; otherwise, NULL.
To prove:
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
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+'%'
#N.Dinesh.Reddy has an amazing answer. I re-worked it a little bit to provide support for new-lines between parameter definitions, as well as supporting parameters whose default values containing XML characters (e.g. #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;
I poked around the dissassembly of SQL Server Management Studio to find out how Microsoft themselves do it, because I wanted to ensure my approach was fully-correct.
As the other posters have surmised, and to my surprise (read: appalled shock) SSMS runs a RegEx against sys.sql_modules.definition to extract parameter information that isn't available in sys.parameters.
As of SQL Server Management Studio 18, the logic for this is in Microsoft.SqlServer.SqlEnum.dll, specifically, the class Microsoft.SqlServer.Management.Smo.PostProcessParam. You'll find other useful regexes in there too.
Here's the patterns:
If your procedure was saved with SET QUOTED_IDENTIFIER then this pattern is used:
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);
If your procedure was not saved with SET QUOTED_IDENTIFIER then this pattern is used:
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);
To get the default values of each parameter, run the appropriate regex against your sql.sql_modules_definition and watch for the val group.
TL;DR:
Here's the full instructions needed to get this to work:
Requirements:
On-Prem SQL Server (tested with SQL Server 2014, 2016 and 207). This does not work with Azure SQL because Azure SQL does not support SQL-CLR.
You do not need Visual Studio - just a command-line access to csc.exe so you can compile the SQL-CLR assembly yourself.
Copy and paste the GetParameterDefaults.cs file described below.
Run this command in a terminal/command-prompt to compile GetParameterDefaults.cs to a SQL-CLR assembly ProcParamDefs.dll:
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
Locate the ProcParamDefs.dll you created in Step 2 and convert it to a hex-bin string (Base 16).
There are websites that will do it for free, or use this PowerShell command:
( Get-Content ".\ProcParamDefs.dll" | Format-Hex | Select-Object -Expand Bytes | ForEach-Object { '{0:x2}' -f $_ }) -join ''
Copy and paste the Install.sql file (at the end of this post) into an SSMS session.
Replace <PASTE BASE-16 (HEX) DLL HERE KEEP THE LEADING 0x PREFIX> with the Base16/hex from step 3. Note the leading 0x needs to be present, as does the trailing ;, so it should look something like this:
CREATE ASSEMBLY [ProcParamDefs]
AUTHORIZATION [dbo]
FROM 0x0x4D5A90000300000004000000FFFF0000B800etc
;
Run it and you should be all-set to use the UDF dbo.GetParameterDefaults, dbo.[GetParameterDefaultsByProcedureObjectId, and dbo.ParseParameterDefaultValues.
For example, see this screenshot:
Note that it only lists parameters with defaults. It will not list parameters without any default defined.
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://learn.microsoft.com/en-us/archive/blogs/sqlprogrammability/server-side-error-handling-part-2-errors-and-error-messages
// https://learn.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
/* You may need to run these statements too:
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'clr strict security', 0;
RECONFIGURE;
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE;
GO
*/
CREATE ASSEMBLY [ProcParamDefs] AUTHORIZATION [dbo]
FROM 0x<PASTE BASE-16 (HEX) DLL HERE KEEP THE LEADING 0x PREFIX>
WITH PERMISSION_SET = SAFE
GO
CREATE FUNCTION [dbo].[GetParameterDefaults] (#procedureName [nvarchar](MAX))
RETURNS TABLE (ParameterName nvarchar(100), DefaultValueExpr nvarchar(4000))
AS EXTERNAL NAME [ProcParamDefs].[UserDefinedFunctions].[GetParameterDefaults];
GO
CREATE FUNCTION [dbo].[GetParameterDefaultsByProcedureObjectId] (#procedureObjectId [int])
RETURNS TABLE (ParameterName nvarchar(100), DefaultValueExpr nvarchar(4000))
AS EXTERNAL NAME [ProcParamDefs].[UserDefinedFunctions].[GetParameterDefaultsByProcedureObjectId];
GO
CREATE FUNCTION [dbo].[ParseParameterDefaultValues] (#procedureDefinition [nvarchar](MAX), #isQuotedId [bit])
RETURNS TABLE (ParameterName nvarchar(100), DefaultValueExpr nvarchar(4000))
AS EXTERNAL NAME [ProcParamDefs].[UserDefinedFunctions].[ParseParameterDefaultValues];
GO
As said before, this is not supported via T-SQL.
You'll have to use some kind of program language to implement this (unless you want to deal with text parsing with T-SQL).
I found it very easy to use SMO for this.
For example, in Powershell:
param
(
[string]$InstanceName = $env:COMPUTERNAME,
[string]$DBName = "MyDB",
[string]$ProcedureSchema = "dbo",
[string]$ProcedureName = "MyProcedure"
)
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | Out-Null
$serverInstance = New-Object ('Microsoft.SqlServer.Management.Smo.Server') $InstanceName
$serverInstance.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.StoredProcedure], $false)
$procedure = $serverInstance.Databases[$DBName].StoredProcedures[$ProcedureName, $ProcedureSchema];
$procedure.Parameters | Select-Object Parent, Name, DataType, DefaultValue, #{Name="Properties";Expression={$_.Properties | Select Name, Value }}
Or, using C#:
Server svr = new Server(new ServerConnection(new SqlConnection(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString)));
svr.SetDefaultInitFields(typeof(StoredProcedure), false);
StoredProcedure sp = svr.Databases["MyDatabase"].StoredProcedures["mySproc", "myScheme"];
Dictionary<string, string> defaultValueLookup = new Dictionary<string, string>();
foreach (StoredProcedureParameter parameter in sp.Parameters)
{
string defaultValue = parameter.DefaultValue;
string parameterName = parameter.Name;
defaultValueLookup.Add(parameterName, defaultValue);
}
(source for the last one: https://stackoverflow.com/a/9977237/3114728)

Remove a particular character from varchar while retrieving data using query

I want to remove a particular character while retrieving a varchar from a table. The query looks like this:
SELECT ServiceName,
case isNumeric(Ramusage)
when 1 then
cast ( ltrim ( rtrim ( ramusage )) as int )
else
0
end as test,
priority
FROM ProcessInfo
Here the problem is ramusage is a varchar and it stores values like 12,500K, 4,321K.
Here I want to remove the "K" from the entry and display the number alone.
You want to use the REPLACE function:
REPLACE(ramusage, 'k', '')
Reference:
SQL Server
Oracle
MySQL
Use REPLACE
DECLARE #ProcessInfo TABLE(
ServiceName VARCHAR(MAX),
Ramusage VARCHAR(MAX),
priority INT
)
INSERT INTO #ProcessInfo (ServiceName,Ramusage,priority)
SELECT 'TEST','12,123K',1
SELECT ServiceName,
case isNumeric(REPLACE(REPLACE(Ramusage,'K',''),',',''))
when 1 then
cast ( ltrim ( rtrim ( REPLACE(REPLACE(Ramusage,'K',''),',',''))) as int )
else
0
end as test,
priority
FROM #ProcessInfo
what we have done is to create a CLR function to take a string amd remove all non numeric items
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString RemoveNonNumeric(SqlString strValue)
{
string strNew = strValue.ToString();
char[] chrArray = strNew.ToCharArray();
strNew = null;
for (int iChar = 0; iChar < chrArray.Length; iChar++)
{
if (Char.IsNumber(chrArray[iChar]))
strNew += chrArray[iChar];
}
// Put your code here
return new SqlString(strNew);
}
substr( ltrim( rtrim( ramusage ) ), length( ramusage ) - 1)
edit: replace, as mentioned in the other answers, is probably better.