Aliasing column names dynamically in SQL Server - sql

Providing separate aliasing values to each column dynamically. The number of values in #column_name1 and #aliasing_val can change as per requirements, so please provide a solution that can work for any number of values in the #column_name1 and #aliasing_val.
Declare #table_name1 NVARCHAR(250) = 'table1',
#column_name1 NVARCHAR(250) = 't1col1,t1col2,t1col3',
#aliasing_val NVARCHAR(250) = 'c1,c2,c3',
#SQLString nvarchar(max),
#SQLString2 nvarchar(max)
If ((#table_name1 IS NOT NULL AND LEN(#table_name1) !=0)
AND (#column_name1 IS NOT NULL AND LEN(#column_name1) !=0))
BEGIN
set #SQLString2 = 'SELECT '
Select #SQLString2 = #SQLString2 +
QUOTENAME(split.a.value('.', 'VARCHAR(100)')) + ' As '+aliasing_val+',
'
FROM (SELECT Cast ('<M>' + Replace(#column_name1, ',', '</M><M>')+ '</M>' AS XML) AS Data) AS A
CROSS apply data.nodes ('/M') AS Split(a);
Set #SQLString2 = LEFT(#SQLString2, LEN(#SQLString2) - 3)
END
print #SQLString2
Current output:
SELECT [t1col1] As c1,c2,c3,
[t1col2] As c1,c2,c3,
[t1col3] As c1,c2,c3
Expected output
SELECT [t1col1] As c1,
[t1col2] As c2,
[t1col3] As c3

This should do the job. You need to separate the comma-separated elements of each string out one at a time. This script loops through the items and maintains a pointer to the next item in the list. Then at each loop it extracts the item and adds it to the SQL:
Declare #table_name1 NVARCHAR(250) = 'table1',
#column_name1 NVARCHAR(250) = 't1col1,t1col2,t1col3',
#aliasing_val NVARCHAR(250) = 'c1,c2,c3',
#SQLString nvarchar(max),
#SQLString2 nvarchar(MAX),
#count [int],
#pos int,
#pos2 [int],
#delimiter varchar(1);
SET #delimiter = ',';
SET #pos = CHARINDEX(#delimiter, #aliasing_val, 1)
SET #pos2 = CHARINDEX(#delimiter, #column_name1, 1)
SET #aliasing_val = LTRIM(RTRIM(#aliasing_val)) + #delimiter
SET #column_name1 = LTRIM(RTRIM(#column_name1)) + #delimiter
SET #count = 0
IF ((#table_name1 IS NOT NULL AND LEN(#table_name1) !=0) AND (#column_name1 IS NOT NULL AND LEN(#column_name1) !=0))
BEGIN
SET #SQLString2 = 'SELECT '
IF REPLACE(#aliasing_val, #delimiter, '') <> '' -- make sure there are actually any delimited items in the list
BEGIN
WHILE #pos > 0
BEGIN
IF #count > 0 SET #SQLString2 = #SQLString2 + ','
SET #SQLString2 = #SQLString2 + LTRIM(RTRIM(LEFT(#column_name1, #pos2 - 1))) + ' As ' + LTRIM(RTRIM(LEFT(#aliasing_val, #pos - 1)))
SET #aliasing_val = RIGHT(#aliasing_val, LEN(#aliasing_val) - #pos) -- remove the item we just extracted from the list
SET #column_name1 = RIGHT(#column_name1, LEN(#column_name1) - #pos2) -- remove the item we just extracted from the list
SET #pos = CHARINDEX(#delimiter, #aliasing_val, 1) -- reset the position to point to the next delimiter
SET #pos2 = CHARINDEX(#delimiter, #column_name1, 1) -- reset the position to point to the next delimiter
SET #count = #count + 1
END
END
END
SELECT #SQLString2

You are at the right approach. Only thing you need to do is make two separate select statement for #column_name and #aliasing_val and join it using ROW_NUMBER() function and you got what you expect - Try this
IF ((#table_name1 IS NOT NULL AND LEN(#table_name1) !=0) AND (#column_name1 IS NOT NULL AND LEN(#column_name1) !=0))
BEGIN
SET #SQLString2 = 'SELECT '
SELECT #SQLString2 += tab.ColName + ' As '+ res.AliasName +','
FROM
(
SELECT QUOTENAME(split.a.value('.', 'VARCHAR(100)')) AS ColName, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowNum
FROM
(
SELECT Cast ('<M>' + Replace(#column_name1, ',', '</M><M>')+ '</M>' AS XML) AS Data
) AS A
CROSS apply data.nodes ('/M') AS Split(a)
) tab
INNER JOIN
(
Select AliasSplit.c.value('.', 'varchar(100)') AS AliasName, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowNum
FROM
(
SELECT Cast ('<A>' + Replace(#aliasing_val, ',', '</A><A>')+ '</A>' AS XML) AS AliasData
) AS A
CROSS apply AliasData.nodes ('/A') AS AliasSplit(c)
) res
ON tab.RowNum = res.RowNum
Set #SQLString2 = LEFT(#SQLString2, LEN(#SQLString2) - 1)
END
PRINT #SQLString2
Output
SELECT [t1col1] As c1,[t1col2] As c2,[t1col3] As c3

Related

Replace the alternate occurances of a substring

My input strings are like:
A or B OR C or D OR E or F
A OR B OR C OR D OR E OR F
Expected Output: 'A or B' OR 'C or D' OR 'E or F'
outputString = '''' + REPLACE(#inputValue COLLATE Latin1_General_CS_AS, ' OR ' COLLATE Latin1_General_CS_AS, ''' OR ''') + ''''
I tried using SQL Replace function and the above statement works properly for the first string and I get the desired output but for the second string since we have all the ORs in the uppercase it fails and returns 'A' OR 'B' OR 'C' OR 'D' OR 'E' OR 'F'
I'm using SSMS 15.0.
How can I solve this problem? Any help will be appreciated.
Here's a solution that uses a UDF.
The function splits a string on a pattern as a resultset.
(similar as the STRING_SPLIT function, but with a pattern)
The FOR XML trick is then used to construct a string from the splitted parts, and to add the quotes.
DECLARE #vchNewValue VARCHAR(100), #result VARCHAR(100);
SET #vchNewValue = 'A OR B or C OR D or E OR F';
SET #result = LTRIM(RTRIM((
SELECT
CASE WHEN match = 1
THEN ' '+quotename(ltrim(rtrim(replace(value,' OR ',' or ') )),'''')+' '
ELSE UPPER(value)
END
FROM dbo.fnPattern_Split(' '+#vchNewValue+' ', ' % OR % ') AS spl
ORDER BY ordinal
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)')
));
SELECT #result AS result;
result
'A or B' OR 'C or D' OR 'E or F'
Test db<>fiddle here
The UDF
Uses PATINDEX to find each next start position of the given pattern in the string.
Then finds the nearest end position where the pattern is still valid.
So it's kinda like a lazy search in regex.
The positions are then used to insert the parts into the returned table.
CREATE FUNCTION dbo.fnPattern_Split
(
#str VARCHAR(MAX),
#pattern VARCHAR(100)
)
RETURNS #tbl TABLE (
ordinal INT,
value VARCHAR(MAX),
match BIT
)
WITH SCHEMABINDING
AS
BEGIN
DECLARE #value NVARCHAR(MAX)
, #splitvalue NVARCHAR(MAX)
, #startpos INT = 0
, #endpos INT = 0
, #ordinal INT = 0
, #foundend BIT = 0
, #patminlen INT = ISNULL(NULLIF(LEN(REPLACE(#pattern,'%','')),0),1);
WHILE (LEN(#str) > 0)
BEGIN
SET #startpos = ISNULL(NULLIF(PATINDEX('%'+#pattern+'%', #str),0), LEN(#str)+1);
IF #startpos < LEN(#str)
BEGIN
SET #foundend = 0;
SET #endpos = #startpos+#patminlen-1;
WHILE #endpos < LEN(#str) AND #foundend = 0
BEGIN
IF SUBSTRING(#str, #startpos, 1+#endpos-#startpos) LIKE #pattern
SET #foundend = 1;
ELSE
SET #endpos += 1;
END
END
ELSE SET #endpos = LEN(#str);
IF #startpos > 1
BEGIN
SET #ordinal += 1;
SET #value = LEFT(#str, #startpos-1);
INSERT INTO #tbl (ordinal, value, match)
VALUES (#ordinal, #value, 0);
END
IF #endpos >= #startpos
BEGIN
SET #ordinal += 1;
SET #splitvalue = SUBSTRING(#str, #startpos, 1+#endpos-#startpos);
INSERT INTO #tbl (ordinal, value, match)
VALUES (#ordinal, #splitvalue, 1);
END
SET #str = SUBSTRING(#str, #endpos+1, LEN(#str));
END;
RETURN;
END;
A recursive solution that stuffs the quotes.
The recursive CTE loops through the string while finding the start positions of the ' or ' patterns.
Since ' or ' has 4 characters, having a start position means you also have the end position.
TheSTUFF function can insert characters in a string on positions.
So the positions are used to stuff the quotes where needed.
Which is every even occurence (modulus 2 of lvl is 0).
declare #input varchar(100)
, #result varchar(100);
set #input = 'A OR B or C OR D or E OR F';
set #result = #input;
with rcte as (
select 1 as lvl
, charindex(' or ', #input) as pos
, len(#input) as max_pos
union all
select lvl+1
, isnull(nullif(charindex(' or ', #input, pos+4), 0), max_pos)
, max_pos
from rcte
where pos < max_pos
)
select #result = stuff(stuff(#result,pos+4,0,''''),pos,0,'''')
from rcte
where lvl%2 = 0 and pos+4 < max_pos
order by lvl desc;
SET #result = ''''+#result+'''';
SET #result = REPLACE(REPLACE(#result,' OR ',' or '),''' or ''',''' OR ''');
select #result as result;
result
'A or B' OR 'C or D' OR 'E or F'
Test on db<>fiddle here
This solution comes with a high cost on server load since while is used.
declare #input varchar(100)
set #input = 'A or B or C or D or E or F or G or'
declare #inc int = 1, #end int = 1
,#final varchar(100) = '', #part varchar(100)
,#nextposition varchar(100), #or varchar(10)= ''
,#last varchar(10), #ifendsOR varchar(10)
select #nextposition = case when #input like '%or' then substring(#input,1,len(#input)-2) else #input end
select #ifendsOR = case when #input like '%or' then ' or' else '' end
select #last = ltrim(rtrim(right(#nextposition,2)))
while #end <> 0
begin
select #part = substring(#nextposition,1,charindex('or',#nextposition)-2)
select #nextposition = replace(#nextposition,concat(#part,' or '),'')
set #end = charindex('or',#nextposition)
select #or = case when #inc%2 = 0 then ' OR ' else ' or ' end
set #inc = #inc+1
set #final = concat(#final,#part,#or)
end
select #ifendsOR = case when #inc%2 = 0 then upper(#ifendsOR) else #ifendsOR end
select concat(#final,#last,#ifendsOR)

How to fix all caps to normal case

I have a sql table that contains an employee's full name in all caps (i.e. SMITH-EASTMAN,JIM M).
I need to be able to separate the full name into two separate columns (Last Name and First Name). This part is going well. Now I could use some assistance with removing the capital letters and putting it in normal case.
How can I take the results of my common table expression and pass them into a function?
WITH CTE AS
(
SELECT FullName = [Employee Name],
LastName = SUBSTRING([Employee Name], 1, CHARINDEX(',',[Employee Name])-1),
FirstNameStartPos = CHARINDEX(',',[Employee Name]) + 1,
MidlleInitialOrFirstNameStartPos = CHARINDEX(' ',[Employee Name]),
MiddleInitialOrSecondFirstName = SUBSTRING([Employee Name], CHARINDEX(' ',[Employee Name]),LEN([Employee Name])),
MiddleInitialOrSecondFirstNameLen = LEN(SUBSTRING([Employee Name], CHARINDEX(' ',[Employee Name]),LEN([Employee Name]))) - 1
FROM ['Med-PS PCN Mapping$']
WHERE [PS Employee ID] IS NOT NULL
),
CTE2 AS
(
SELECT FullName = CTE.FullName,
DerivedFirstName = CASE
WHEN CTE.MiddleInitialOrSecondFirstNameLen = 1
THEN SUBSTRING(CTE.FullName, CTE.FirstNameStartPos, CTE.MidlleInitialOrFirstNameStartPos - CTE.FirstNameStartPos)
ELSE SUBSTRING(CTE.FullName, CTE.FirstNameStartPos, CTE.FirstNameStartPos + CTE.MiddleInitialOrSecondFirstNameLen)
END,
DerivedLastName = CTE.LastName
FROM CTE
)
SELECT *
FROM CTE2
RESULTS
FullName DerivedFirstName DerivedLastName
SMITH-EASTMAN,JIM M JIM SMITH-EASTMAN
O'DAY,MARTIN C MARTIN O'DAY
TROUT,MADISON MARIE MADISON MARI TROUT
CREATE FUNCTION [dbo].[FixCap] ( #InputString varchar(4000) )
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE #Index INT
DECLARE #Char CHAR(1)
DECLARE #PrevChar CHAR(1)
DECLARE #OutputString VARCHAR(255)
SET #OutputString = LOWER(#InputString)
SET #Index = 1
WHILE #Index <= LEN(#InputString)
BEGIN
SET #Char = SUBSTRING(#InputString, #Index, 1)
SET #PrevChar = CASE WHEN #Index = 1 THEN ' '
ELSE SUBSTRING(#InputString, #Index - 1, 1)
END
IF #PrevChar IN (' ', ';', ':', '!', '?', ',', '.', '_', '-', '/', '&', '''', '(')
BEGIN
IF #PrevChar != '''' OR UPPER(#Char) != 'S'
SET #OutputString = STUFF(#OutputString, #Index, 1, UPPER(#Char))
END
SET #Index = #Index + 1
END
RETURN #OutputString
END
GO
select [dbo].[FixCap] (pass in DerivedFirstName from CTE2);
select [dbo].[FixCap] (pass in DerivedLastName from CTE2);
Do you want something like INITCAP ?
CREATE FUNCTION dbo.F_INITCAP (#PHRASE NVARCHAR(max))
RETURNS NVARCHAR(max)
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
IF LEN(#PHRASE) < 1 RETURN #PHRASE;
DECLARE #I INT = 1, #C CHAR(1), #P BIT = 0, #OUT VARCHAR(max) = '';
WHILE #I <= LEN(#PHRASE)
BEGIN
SET #C = SUBSTRING(#PHRASE, #I, 1);
IF #C BETWEEN 'A' AND 'Z' COLLATE Latin1_General_CI_AI
BEGIN
IF #P = 0
SET #OUT = #OUT + UPPER(#C);
ELSE
SET #OUT = #OUT + LOWER(#C);
SET #P = 1
END
ELSE
BEGIN
SET #P = 0;
SET #OUT = #OUT + LOWER(#C);
END
SET #I = #I + 1;
END
RETURN #OUT;
END
GO

need splitting a variable into 6 variables - SQL

need help filling in the blanks i am struggling with splitting a variable into 6 variables.
DECLARE #item VARCHAR(MAX) = 'MG1111.TG2222.MW3333.JG4444.MG5555.MH6666'
DECLARE #item1 VARCHAR(MAX)
DECLARE #item2 VARCHAR(MAX)
DECLARE #item3 VARCHAR(MAX)
DECLARE #item4 VARCHAR(MAX)
DECLARE #item5 VARCHAR(MAX)
DECLARE #item6 VARCHAR(MAX)
set #item1 = (SUBSTRING( #item, 0, CHARINDEX('.', #item)))
set #item2 = (SUBSTRING(SUBSTRING( #item, CHARINDEX('.', #item)+1,LEN(#ITEM)),0,CHARINDEX('.', SUBSTRING( #item, CHARINDEX('.', #item)+1,LEN(#ITEM))) ))
set #item6 = (REVERSE(SUBSTRING( REVERSE(#ITEM), 0, CHARINDEX('.' , REVERSE(#ITEM)))))
print #item1
print #item2
print #item3
print #item4
print #item5
print #item6
Just about any String Parser will do. That said, I have one that will return (currently) up to 9 variables
Select #item1=Pos1
,#item2=Pos2
,#item3=Pos3
,#item4=Pos4
,#item5=Pos5
,#item6=Pos6
From [dbo].[udf-Str-Parse-Row](#Item,'.')
The UDF
CREATE FUNCTION [dbo].[udf-Str-Parse-Row] (#String varchar(max),#Delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse-Row]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse-Row]('John Cappelletti',' ')
-- Select * from [dbo].[udf-Str-Parse-Row]('id26,id46|id658,id967','|')
Returns Table
As
Return (
SELECT Pos1 = xDim.value('/x[1]','varchar(250)')
,Pos2 = xDim.value('/x[2]','varchar(250)')
,Pos3 = xDim.value('/x[3]','varchar(250)')
,Pos4 = xDim.value('/x[4]','varchar(250)')
,Pos5 = xDim.value('/x[5]','varchar(250)')
,Pos6 = xDim.value('/x[6]','varchar(250)')
,Pos7 = xDim.value('/x[7]','varchar(250)')
,Pos8 = xDim.value('/x[8]','varchar(250)')
,Pos9 = xDim.value('/x[9]','varchar(250)')
FROM (Select Cast('<x>' + Replace(#String,#Delimeter,'</x><x>')+'</x>' as XML) as xDim) A
)
Here is a simpler way using XML trick
DECLARE #item VARCHAR(max) = 'MG1111.TG2222.MW3333.JG4444.MG5555.MH6666'
DECLARE #item1 VARCHAR(max)
DECLARE #item2 VARCHAR(max)
DECLARE #item3 VARCHAR(max)
DECLARE #item4 VARCHAR(max)
DECLARE #item5 VARCHAR(max)
DECLARE #item6 VARCHAR(max);
WITH split_names
AS (SELECT CONVERT(XML, '<Names><name>' + Replace(#item, '.', '</name><name>') + '</name></Names>') AS xmlname)
SELECT #item1 = xmlname.value('/Names[1]/name[1]', 'varchar(50)'),
#item2 = xmlname.value('/Names[1]/name[2]', 'varchar(50)'),
#item3 = xmlname.value('/Names[1]/name[3]', 'varchar(50)'),
#item4 = xmlname.value('/Names[1]/name[4]', 'varchar(50)'),
#item5 = xmlname.value('/Names[1]/name[5]', 'varchar(50)'),
#item6 = xmlname.value('/Names[1]/name[6]', 'varchar(50)')
FROM split_names
Why not use this pattern?
set #item1 = SUBSTRING_INDEX( #item, '.', 1)
.
set #item3 = SUBSTRING_INDEX( SUBSTRING_INDEX( #item, '.', 2), '.', 1)
.
.
set #item6 = SUBSTRING_INDEX( #item, '.', -1)
I am assuming by 6 variables you mean each substring between the dots in #item is a separate "variable".
A more generic parser could be as follows. This one returns a row number and is not limited as to depth.
DECLARE #item VARCHAR(MAX) = 'MG1111.TG2222.MW3333.JG4444.MG5555.MH6666'
DECLARE #item1 VARCHAR(MAX)
DECLARE #item2 VARCHAR(MAX)
DECLARE #item3 VARCHAR(MAX)
DECLARE #item4 VARCHAR(MAX)
DECLARE #item5 VARCHAR(MAX)
DECLARE #item6 VARCHAR(MAX)
Select #item1=max(case when Key_PS=1 then Key_Value else '' end)
,#item2=max(case when Key_PS=2 then Key_Value else '' end)
,#item3=max(case when Key_PS=3 then Key_Value else '' end)
,#item4=max(case when Key_PS=4 then Key_Value else '' end)
,#item5=max(case when Key_PS=5 then Key_Value else '' end)
,#item6=max(case when Key_PS=6 then Key_Value else '' end)
From [dbo].[udf-Str-Parse](#Item,'.')
The UDF
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
Returns #ReturnTable Table (Key_PS int IDENTITY(1,1), Key_Value varchar(max))
As
Begin
Declare #XML xml;Set #XML = Cast('<x>' + Replace(#String,#Delimeter,'</x><x>')+'</x>' as XML)
Insert Into #ReturnTable Select ltrim(rtrim(String.value('.', 'varchar(max)'))) FROM #XML.nodes('x') as T(String)
Return
End

Check a chunk of text for tilde and ensure they are 60 characters apart. If not place a tilde inbetween

I currently have a function in SQL that places a tilde every 60 characters, but the original text already has tilde's so , basically I want to change that if there is a tilde and the next tilde is under 60 characters away, then skip to the next tilde. If its over 60 characters, then only then place a extra tilde.
My current function looks like;
function [dbo].[AddTilde] (
#string varchar(max),
#count int
)
returns varchar(max)
as
begin
declare #result varchar(max) = ''
declare #token varchar(max) = ''
while DATALENGTH(#string) > 0
begin
select #token = left(#string, #count)
select #string = REPLACE(#string, #token, '')
select #result +=#token +case when DATALENGTH(#string)=0 then '' else '~' end
end
return #result
end
Any help appreciated
Many Thanks
DECLARE #string1 VARCHAR(max),
#string2 VARCHAR(max) = '',
#i1 INT,
#i2 INT
SET #string1 = '12345678901234567890~1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'
WHILE LEN(#string1) > 0
BEGIN
SET #i1 = CHARINDEX('~',#string1)
SET #i2 = #i1 - (ABS(#i1-60) + (#i1-60)) / 2 -- MINIMUM OF ~ LOCATION AND 60
SET #i1 = LEN(#string1)
IF #i2 = 0 SET #i2 = #i1 - (ABS(#i1-60) + (#i1-60)) / 2 -- MINIMUM OF LENGTH OF #string1 LOCATION AND 60
IF #i2 < 60
BEGIN
SET #string2 = #string2 + LEFT(#string1,#i2)
SET #string1 = RIGHT(#string1,#i1-#i2)
END
ELSE
BEGIN
SET #string2 = #string2 + LEFT(#string1,60) + '~'
SET #string1 = RIGHT(#string1,#i1-60)
END
END
Results: 12345678901234567890~123456789012345678901234567890123456789012345678901234567890~1234567890123456789012345678901234567890
This is based on my String Splitting function, which has a very good performance.
This function should be quite efficient, albeit hard to understand (I have added a few commenets in attempt to make it easier).
You can change internal parameters easily e.g. #Delimitor can be multiple characters
Test cases are included at the bottom.
ALTER FUNCTION [dbo].[AddTilde]
(
#String VARCHAR( MAX ),
#Count INT
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #CurrentPosition BIGINT, #NextDelimiterPosition BIGINT, #NextFixedPosition BIGINT, #NextPosition BIGINT,
#DelimiterLength INT, #Delimiter VARCHAR( 5 ), #Result VARCHAR( MAX )
-- Initialise
SET #Delimiter = '~'
SET #DelimiterLength = LEN( #Delimiter )
SET #Result = ''
-- Ensures we can terminate loop without using an extra IF condition, saves a few = 0 checks
SET #String = #String + #Delimiter
SET #CurrentPosition = 1
-- Check for next Tilde position
SET #NextDelimiterPosition = CHARINDEX( #Delimiter, #String, #CurrentPosition )
-- Initialise fixed increment position
SET #NextFixedPosition = #Count
-- Compare, which one is earlier and use that one. Handle cases where the next token begins with Tilde
SET #NextPosition = CASE WHEN #NextDelimiterPosition - #DelimiterLength > #NextFixedPosition THEN #NextFixedPosition ELSE #NextDelimiterPosition END
WHILE #NextDelimiterPosition > 0
BEGIN
SET #Result = #Result + SUBSTRING( #String, #CurrentPosition, #NextPosition - #CurrentPosition + 1 )
-- Handle cases where the next token begins with Tilde and avoids doubling up Tildes
+ ( CASE WHEN #NextPosition = #NextDelimiterPosition THEN '' ELSE #Delimiter END )
SET #CurrentPosition = #NextPosition + 1
-- Increment fixed position
SET #NextFixedPosition = #CurrentPosition + #Count - 1
-- Check for next Tilde position
SET #NextDelimiterPosition = CHARINDEX( #Delimiter, #String, #CurrentPosition )
SET #NextPosition = CASE WHEN #NextDelimiterPosition - #DelimiterLength > #NextFixedPosition THEN #NextFixedPosition ELSE #NextDelimiterPosition END
END
-- Remove trailing Tilde
SET #Result = SUBSTRING( #Result, 1, LEN( #Result ) - #DelimiterLength )
RETURN #Result
END
/* Test Cases
SELECT dbo.[AddTilde]( 'ab~c~defghijkl~', 3 ) --> 'ab~c~def~ghi~jkl~'
SELECT dbo.[AddTilde]( '~ab~c~defghijkl', 3 ) --> '~ab~c~def~ghi~jkl'
SELECT dbo.[AddTilde]( 'ab~c~~defghijkl', 3 ) --> 'ab~c~~def~ghi~jkl'
SELECT dbo.[AddTilde]( 'abcdefghijkl', 3 ) --> 'abc~def~ghi~jkl'
SELECT dbo.[AddTilde]( 'a', 3 ) --> 'a'
*/

Dynamic SQL for CASE Statements to remove hardcode

I've a Dynamic SQL that is required to be optimized. I need to make CASE Expression Dynamic. I've a list of ATTRIBUTE_LIST & SCENARIO_LIST, that are provided below. I wrote a Function to get them Dynamically. How can I replace Three CASE Expression and make it Dynamic? I'm trying to avoid hard coding.
SET #ATTRIBUTE_LIST = 'symbol_type, currency, performing_status' -- INPUTS
SET #SCENARIO_LIST = 'historicalsimulation_1day_end_10dec2013, historicalsimulation_1day_end_11dec2013'
SELECT CASE
WHEN (GROUPING(Scenario_Name) = 1) THEN ''ALL''
WHEN (Scenario_Name = ''[BaseCase]'') THEN ''BaseCase''
ELSE ISNULL(Scenario_Name, '''')
END AS Scenario_Name,
CASE
WHEN (GROUPING(Symbol_Type) = 1) THEN ''ALL''
ELSE ISNULL(Symbol_Type, '''')
END AS Symbol_Type,
CASE
WHEN (GROUPING(Currency) = 1) THEN ''ALL''
ELSE ISNULL(Currency, '''')
END AS Currency,
CASE
WHEN (GROUPING(Performing_Status) = 1) THEN ''ALL''
ELSE ISNULL(Performing_Status, '''') \
END AS Performing_Status,
SUM(Value) AS ScenarioValue
FROM [20151005_171003_UserName_NT-22_Analysis_Tue] o
LEFT JOIN [20151005_171003_UserName_NT-22_Analysis_Tue_Position_Data] pld
ON o.Position_Unique_Identifier=pld.Position_Unique_Identifier
GROUP BY ' + #ATTRIBUTE_LIST + ' WITH CUBE) AS DATA
PIVOT ( Sum(scenariovalue)
FOR scenario_name IN (' + #SCENARIO_LIST + ')'
It appears you are using SQL Server. Here's a brute-force way of looping over your attributes and building a series of case expressions:
declare #cases nvarchar(max) = '';
declare #l varchar(max) = #ATTRIBUTE_LIST + ',';
declare #ofs int = 0;
declare #pos int = charindex(',', #l, #ofs + 1);
while #pos > 0
begin
if #cases <> '' set #cases = #cases + ',';
set #cases = #cases +
replace('
CASE WHEN (GROUPING(<COL>) = 1) THEN ''ALL''
ELSE ISNULL(<COL>, '''')
END AS <COL>',
'<COL>', substring(#l, #ofs + 1, #pos - #ofs - 1)
);
set #ofs = #pos;
set #pos = charindex(',', #l, #ofs + 1);
end
select #cases;