I've created a MSSQL Server function which encodes special chars (example: हिन्दीabcde fG#) to the unicode number with "#" and ";" as delimiter. Only very simple chars like "abc" will not be encoded:
declare #position int, #txt nvarchar(max), #output as varchar(max);
set #position=1;
set #txt = N'हिन्दीabcde fG#';
set #output = '';
while #position <= len(#txt)
begin
declare #t int;
select #t=unicode(substring(#txt,#position,1))
--print '&#'+ CONVERT(nvarchar(5),#t)+';'
if ( (#t between 48 and 57) OR (#t between 65 and 90) or (#t between 97 and 122) )
BEGIN
SET #output = #output + CONVERT(nvarchar(5), substring(#txt,#position,1) );
END
else
BEGIN
SET #output = #output + '#'+ CONVERT(nvarchar(5),#t)+';'
END
set #position = #position+1
end
Print #output
The result is:
2361;#2367;#2344;#2381;#2342;#2368;abcde#32;fG#35;
I need it for working with ODBC drivers and to avoid problems with special chars.
But now I need the way back - to decode the encoded chars. Is there any smart solution or will I need at least two loops, the "NCHAR" function ...?
I'll try to build such a function - if it's successfull, I'll post it here :)
You might find this approach a little more appealing. First, create a split function that maintains order:
CREATE FUNCTION dbo.SplitStringsOrdered
(
#List NVARCHAR(MAX),
#delim NVARCHAR(10)
)
RETURNS TABLE
AS
RETURN
(
SELECT rn, v = LTRIM(RTRIM(SUBSTRING(#List, rn,
CHARINDEX(#delim, #List + #delim, rn) - rn)))
FROM
(
SELECT TOP (8000) rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
) AS n
WHERE rn <= LEN(#List)
AND SUBSTRING(#delim + #List, rn, LEN(#delim)) = #delim
);
GO
Usage:
DECLARE #x NVARCHAR(MAX) = N'#2361;#2367;#2344;#2381;'
+ N'#2342;#2368;abcde#32;fG#35;';
-- need one small adjustment to make the string more split-friendly:
SET #x = REPLACE(#x, '#', ';#');
DECLARE #output NVARCHAR(MAX);
SELECT #output = (SELECT
CASE WHEN v LIKE '#%' THEN NCHAR(REPLACE(v, '#', '')) ELSE v END
FROM dbo.SplitStringsOrdered(#x, ';') AS x
ORDER BY rn FOR XML PATH(''),
TYPE).value('./text()[1]','nvarchar(max)');
SELECT #output;
Output:
हिन्दीabcde fG#
I've solved the problem with this query:
declare #position int, #txt nvarchar(max), #output as nvarchar(max), #buffer as varchar(max);
set #position=1;
set #txt = '#2361;#2367;#2344;#2381;#2342;#2368;abcde#32;fG#35;';
set #output = '';
set #buffer = '';
while #position <= len(#txt)
begin
declare #t varchar(max);
select #t=(substring(#txt,#position,1))
if ( len(#buffer) = 0 and #t <> '#' and #t <> ';')
BEGIN
-- Append simple chars, which were not encoded
Print 'Hänge den String ganz normal an den Output: ' + #t
SET #output = #output + #t;
END
ELSE
BEGIN
if ( #t = '#' )
BEGIN
Print 'Raute wurde erkannt: #';
SET #buffer = '#';
END
else if ( #t = ';' )
BEGIN
SET #buffer = REPLACE( #buffer, '#' , '' );
Print 'Umwandeln: ' + #buffer
SET #output = #output + isnull( NCHAR(#buffer) , '');
SET #buffer = '';
END
else
BEGIN
Print 'Ganzzahl an den Buffer anhängen: ' + #t;
SET #buffer = #buffer + #t;
END
END
set #position = #position+1
end
Print #output
Related
I wrote a sql server function which returns substring before the Nth occurence of character.
For example,
SELECT dbo.fn_getFirstNthSentence('.', 'hello world.It.is.raining.today', 3)
returns 'hello world.It.Is.' as a result.
The function I wrote looks dirty and slow so I want to optimize it.
Any advice to make it clean is appreciated.
Thank you.
CREATE FUNCTION fn_getFirstNthSentence
(
#TargetStr VARCHAR(MAX) ,
#SearchedStr VARCHAR(8000) ,
#Occurrence INT
)
RETURNS varchar(MAX)
AS
BEGIN
DECLARE #pos INT ,
#counter INT ,
#ret INT;
SET #pos = CHARINDEX(#TargetStr, #SearchedStr);
IF ( #pos = 0 )
RETURN #SearchedStr
SET #counter = 1;
IF #Occurrence = 1
SET #ret = #pos;
ELSE
BEGIN
WHILE ( #counter < #Occurrence )
BEGIN
IF(LEN(#SearchedStr) < #pos + 1)
RETURN #SearchedStr
SELECT #ret = CHARINDEX(#TargetStr, #SearchedStr,
#pos + 1);
IF(#ret = 0)
RETURN #SearchedStr
SET #counter = #counter + 1;
SET #pos = #ret;
END;
END;
RETURN LEFT(#SearchedStr, #ret)
END;
Here is yet another option using a delimited string splitter. The XML method already posted is a good one but this approach does not require a table variable.
This is created as an inline table valued function which should keep the performance really fast.
create function fn_getFirstNthSentence
(
#SearchedStr varchar(100)
, #Occurrence int
, #Delimiter char(1)
) returns table as return
with ParsedValues as
(
select Item
, ItemNumber
from dbo.DelimitedSplit8K(#SearchedStr, #Delimiter)
where ItemNumber <= #Occurrence
)
select top 1 ResultString = STUFF(
(
select #Delimiter + Item
from ParsedValues
order by ItemNumber
for xml path('')), 1,1, '') + #Delimiter
from ParsedValues
This is also using a splitter created by Jeff Moden. It has one feature that none of the other splitter have...a column to indicate which position the value came from. You can find his article an ensuing discussion here. http://www.sqlservercentral.com/articles/Tally+Table/72993/
Then if you want to execute it you can do this quite simply.
declare #String varchar(100) = 'hello world.It.is.raining.today.'
, #Num int = 3
, #Delimiter char(1) = '.'
;
select *
from fn_getFirstNthSentence(#String, #Num, #Delimiter)
If you don't like Jeff Moden's splitter you can find several other options here. http://sqlperformance.com/2012/07/t-sql-queries/split-strings I don't use Moden's for everything but when you need to keep the parsed values in order it is awesome.
--EDIT--
Here is how you could modify this to become a scalar function instead of an inline table valued function. My preference would be to keep the itvf as they are faster and more flexible.
create function fn_getFirstNthSentenceScalar
(
#SearchedStr varchar(100) = 'hello world.It.is.raining.today.this is after 5'
, #Occurrence int = 5
, #Delimiter char(1) = '.'
) returns varchar(max) as begin
declare #RetVal varchar(max);
with ParsedValues as
(
select Item
, ItemNumber
from dbo.DelimitedSplit8K(#SearchedStr, #Delimiter)
where ItemNumber <= #Occurrence
)
select top 1 #RetVal = STUFF(
(
select #Delimiter + Item
from ParsedValues
order by ItemNumber
for xml path('')), 1,1, '') + #Delimiter
from ParsedValues;
return #RetVal
end
--I find these functions to be a mine-field, and at the risk of stepping on a mine I've tried some simplifications - maybe a microscopic improvement in performance
alter FUNCTION fn_getFirstNthSentence
(
#TargetStr VARCHAR(MAX) ,
#SearchedStr VARCHAR(8000) ,
#Occurrence INT
)
RETURNS varchar(MAX)
AS
BEGIN
DECLARE #pos INT ,
#counter INT ;
IF #Occurrence < 1
RETURN NULL;
SELECT #counter = 0, #POS = 1;
WHILE (#counter < #Occurrence AND #POS > 0)
BEGIN
SELECT #POS = CHARINDEX(#TargetStr, #SearchedStr,
#pos + 1);
IF #POS > 0
SET #counter = #counter + 1;
END;
RETURN CASE WHEN #POS > 0 THEN
LEFT(#SearchedStr, #POS)
ELSE
#SearchedStr
END;
END;
Another option is via XML
I can't see your benchmarks, but it is certainly far less code. An added option could be Find the 3rd through 5th occurrence by adding a parameter and changing the Where Seq<=#FindPos to Where Seq Between range1 and range2.
Declare #FindPos int = 3
Declare #String varchar(max) = 'hello world.It.is.raining.today'
Declare #Delim varchar(10) = '.'
Declare #XML xml,#RetVal varchar(max) = ''
Set #XML = Cast('<x>' + Replace(#String,#Delim,'</x><x>')+'</x>' as XML)
Declare #Table table (Seq int identity(1,1),String varchar(max))
Insert Into #Table Select ltrim(rtrim(String.value('.', 'varchar(max)')))+#Delim as value FROM #XML.nodes('x') as T(String)
Select #RetVal=#RetVal + String from #Table Where Seq<=#FindPos Order By Seq
Select #RetVal
Returns
hello world.It.is.
EDIT: If it helps, below is my generic parsing function which returns a
normalized table...
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',' ')
-- Select * from [dbo].[udf-Str-Parse]('id26,id46|id658,id967','|')
-- Select * from [dbo].[udf-Str-Parse]('hello world. It. is. . raining.today','.')
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 Key_Value = ltrim(rtrim(String.value('.', 'varchar(max)'))) FROM #XML.nodes('x') as T(String)
Return
End
So for example:
Select * from [dbo].[udf-Str-Parse]('hello world.It.is.raining.today','.')
Returns
Key_PS Key_Value
1 hello world
2 It
3 is
4 raining
5 today
How do I replace the characters ~!##$%^&*()_+}{][ in a nvarchar (or varchar) field with a - using TSQL?
you can create user define function for that as given below
CREATE FUNCTION udf_ReplaceSpecialChar
(
#inputString VARCHAR(1000)
)
RETURNS VARCHAR(1000)
AS
BEGIN
DECLARE #outputString VARCHAR(1000),
#LENGTH INT,
#index INT,
#char CHAR(1)
SELECT #LENGTH = LEN(#inputString),
#index = 1
WHILE(#index <= #LENGTH)
BEGIN
SET #char = SUBSTRING(#inputString, #index, 1)
IF((ASCII(#char) NOT BETWEEN 65 AND 90) AND (ASCII(#char) NOT BETWEEN 97 AND 122) AND (ASCII(#char) NOT BETWEEN 48 AND 57))
BEGIN
SELECT #inputString = REPLACE(#inputString, #char, '-')
END
SET #index = #index + 1
END
SET #outputString = #inputString
RETURN #outputString
END
SELECT dbo.udf_ReplaceSpecialChar('This()*& is%%#Sample**.>String')
or you should replace each character with '-'
Like
SELECT REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE('This()*& is%%#Sample**.>String', ' ', '-'), '*', '-'), '#', '-'), '&', '-'), '(', '-'), ')', '-'), '.', '-'), '>', '-'), '%', '-')
You can use REPLACE function. If it doesn't work in some cases, please give us examples.
May be this code is that you are searching for:
-- Author: Christian d'Heureuse, www.source-code.biz
create function dbo.RemoveSpecialChars (#s varchar(256)) returns varchar(256)
with schemabinding
begin
if #s is null
return null
declare #s2 varchar(256)
set #s2 = ''
declare #l int
set #l = len(#s)
declare #p int
set #p = 1
while #p <= #l begin
declare #c int
set #c = ascii(substring(#s, #p, 1))
if #c between 48 and 57 or #c between 65 and 90 or #c between 97 and 122
set #s2 = #s2 + char(#c)
set #p = #p + 1
end
if len(#s2) = 0
return null
return #s2
end
It removes all characters except 0-9, a-z and A-Z. This function uses ASCII codes of characters to determine this ones which must be removed.
--another one variant
----------------------------------------------------------------------------------------
--better to keep such table in server, very usefull table, especially with indexes
DECLARE #Tally TABLE ( N INT )
DECLARE #i AS INT = 1
WHILE #i != 1000
BEGIN
INSERT INTO #Tally
( N )
VALUES ( #i )
SET #i = #i + 1
END
----------------------------------------------------------------------------------------
DECLARE #String AS VARCHAR(1000) = 'This()*& is%%# **.>another one //&^&*$variant'
----------------------------------------------------------------------------------------
--using #tally - split, using like - remove not required, 'for xml ...' - combine into string
SELECT REPLACE(( SELECT LEFT(SUBSTRING(#String, n, 1000), 1)
FROM #Tally AS T
WHERE SUBSTRING(#String, n, 1000) != ''
AND LEFT(SUBSTRING(#String, n, 1000), 1) LIKE '[A-Za-z0-9 ]'
FOR
XML PATH('')
), ' ', ' ')
--another one variant
------------------------------------------------------------------------------------
--better to keep such table in server, very usefull table, especially with indexes
DECLARE #Tally TABLE ( N INT )
DECLARE #i AS INT = 1
WHILE #i != 1000
BEGIN
INSERT INTO #Tally
( N )
VALUES ( #i )
SET #i = #i + 1
END
------------------------------------------------------------------------------------
DECLARE #String VARCHAR(500) ,
#B VARCHAR(500) = ''
SET #String = 'This()*& is%%# **.>another one //&^&*$variant'
SELECT #B = #B + SUBSTRING(#String, t.N, 1)
FROM #Tally t
WHERE t.N <= DATALENGTH(#String)
AND PATINDEX('[A-Za-z0-9 ]', SUBSTRING(#String, t.N, 1)) > 0
SELECT #B
--------------------------------------------------------------------------------
if you wish use this method like a function then:
Create Tally table with one field PRIMARY KEY (1000 rows, starting from 1 with step 1)
Use code below to create function
Table Tally will be very useful for the split sting, clean string etc., currently this is
the best way to use instead fetch, xml and etc.
--------------------------------------------------------------------------------
CREATE FUNCTION [dbo].[StringClean](
#A VARCHAR(500))
RETURNS VARCHAR(500)
AS
BEGIN
DECLARE #B VARCHAR(500)
SET #B = ''
SELECT #B = #B + SUBSTRING(#A, t.N, 1)
FROM dbo.Tally t
WHERE t.N <= DATALENGTH(#A)
AND PATINDEX('[A-Za-z0-9 ]', SUBSTRING(#A, t.N, 1)) > 0
RETURN #B
END
-------------------------------------------------------------------------------
SELECT dbo.StringClean('This()*& is%%# **.>another one //&^&*$variant')
-------------------------------------------------------------------------------
DECLARE #Tally TABLE ( N INT )
DECLARE #i AS INT = 1
WHILE #i != 1000
BEGIN
INSERT INTO #Tally (N) VALUES (#i)
SET #i = #i + 1
END
--------------------------------------------------------------
DECLARE #String VARCHAR(500)
DECLARE #B VARCHAR(500) = ''
DECLARE #ReplacedChars VARCHAR(50) = '~!##$%^&*()_+}{][<>/.'
SET #String = 'This()*& is%%# **.>another one //&^&*$variant'
SELECT #B = #B + CASE WHEN CHARINDEX(SUBSTRING(#String, t.N, 1), #ReplacedChars) > 0 THEN '-'
ELSE SUBSTRING(#String, t.N, 1) END
FROM #Tally t
WHERE t.N <= DATALENGTH(#String)
SELECT #B
I have the following three different strings which needs to split into three different columns.
Example:
String 1:
Declare #str1 varchar(max) = 'A1,A2,A3'
String 2:
Declare #str2 varchar(max) = 'B1,B2,B3'
String 3:
Declare #str2 varchar(max) = 'C1,C2,C3'
NoteI want to store the above three strings into three different columns.
Expected Output:
colA colB colC
------------------
A1 B1 C1
A2 B2 C2
A3 B3 C3
Attempt:
SQL Fiddle: http://sqlfiddle.com/#!3/d41d8/41345
I know its a bit heavy but it will work
Declare #str1 varchar(max) = 'A1,A2,A3'
Declare #str2 varchar(max) = 'B1,B2,B3'
Declare #str3 varchar(max) = 'C1,C2,C3'
DECLARE #RowCount TINYINT
DECLARE #i TINYINT = 0
DECLARE #Table AS TABLE
(
colA varchar(MAX)
,ColB varchar(MAX)
,ColC varchar(MAX)
)
SET #RowCount = len(#str1) - len(replace(#str1, ',', ''))
WHILE(#i<=#RowCount)
BEGIN
INSERT INTO #Table
SELECT LEFT(#str1,CHARINDEX(',',#str1+',',0)-1) AS colA
,LEFT(#str2,CHARINDEX(',',#str2+',',0)-1) AS colB
,LEFT(#str3,CHARINDEX(',',#str3+',',0)-1) AS colC
SET #str1 = STUFF(#str1,1,CHARINDEX(',',#str1,0),'')
SET #str2 = STUFF(#str2,1,CHARINDEX(',',#str2,0),'')
SET #str3 = STUFF(#str3,1,CHARINDEX(',',#str3,0),'')
SET #i = #i + 1
END
SELECT * FROM #Table
If you have atmost three values then try this.
DECLARE #str1 VARCHAR(max) = 'A1,A2,A3'
SELECT Parsename(Replace(#str1, ',', '.'), 3) 'FST_COL',
Parsename(Replace(#str1, ',', '.'), 2) 'SCD_COL',
Parsename(Replace(#str1, ',', '.'), 1) 'TRD_COL' into #temp
Or
DECLARE #str1 VARCHAR(max) = 'A1,A2,A3',
#sql NVARCHAR(max),
#loop INT,
#cnt INT=1
SELECT #loop = Len(#str1) - Len(Replace(#str1, ',', '')) + 1
SET #sql=' WITH Split_cols ( xmlcol)
AS (SELECT CONVERT(XML, ''<cols><col>''
+ Replace('''
+ #str1 + ''', '','', ''</col><col>'') + ''</col></cols>'') as xmlcol)
SELECT '
WHILE #cnt <= #loop
BEGIN
SET #sql+=' xmlcol.value(''/cols[1]/col['
+ CONVERT(VARCHAR(30), #cnt)
+ ']'', ''varchar(100)'') AS col'
+ CONVERT(VARCHAR(30), #cnt) + ','
SET #cnt=#cnt + 1
END
SET #sql=LEFT(#sql, Len(#sql) - 1)
SET #sql +=' FROM Split_cols '
--PRINT #sql
EXEC Sp_executesql #sql
THIS WILL WORK WITH ANY NUMBER OF STRINGS AND VALUES NOT HARDCODED
CREATE FUNCTION dbo.splitstring (#stringToSplit VARCHAR(MAX) )
RETURNS
#returnList TABLE ([ID] INT IDENTITY(1,1),[Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX(',', #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(',', #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
-- USE THIS PARAMETER TO PASS VALUE
DECLARE #STRING VARCHAR(MAX)
DECLARE #COUNT INT
DECLARE #I INT
DECLARE #COLUMNNAME VARCHAR(MAX)
DECLARE #CREATETABLE VARCHAR(MAX)
DECLARE #INSERT VARCHAR(MAX)
IF OBJECT_ID('TEMPDB..##TEMPTABLE') IS NOT NULL
DROP TABLE ##TEMPTABLE
IF OBJECT_ID('TEMPDB..##RETURNLIST') IS NOT NULL
DROP TABLE ##RETURNLIST
SELECT * INTO ##RETURNLIST FROM dbo.splitstring(#STRING)
select #COUNT = COUNT(*) from ##RETURNLIST
SET #I=0
SET #CREATETABLE = 'CREATE TABLE ##TEMPTABLE ('
WHILE (#COUNT>0)
BEGIN
SET #COLUMNNAME = 'COLUMN'+ CONVERT(varchar(10), #I) + ' VARCHAR(MAX)'
SET #CREATETABLE = #CREATETABLE + #COLUMNNAME
IF(#COUNT<>1)
SET #CREATETABLE = #CREATETABLE + ', '
SET #I = #I+1
SET #COUNT = #COUNT -1;
END
SET #CREATETABLE = #CREATETABLE + ' )'
EXECUTE(#CREATETABLE)
SET #INSERT = 'INSERT INTO ##TEMPTABLE VALUES( '
WHILE (#I>0)
BEGIN
SET #INSERT = #INSERT +''''+ (SELECT NAME FROM ##RETURNLIST WHERE ID = #COUNT+1) +''''
IF(#I<>1)
SET #INSERT = #INSERT + ', '
SET #I = #I-1
SET #COUNT = #COUNT +1;
ENDenter code here
SET #INSERT = #INSERT + ' )'
EXECUTE(#INSERT)
EXECUTE('SELECT * FROM ##TEMPTABLE')
You haven't given any criteria for joining the values together for each of the rows of columns so I've just joined them in descending order. This solution can handle any number of items in the lists but if the number gets too high you may need to use the MAX RECURSION query hint.
DECLARE #strA NVARCHAR(MAX) = 'A1,A2,A3';
DECLARE #strB NVARCHAR(MAX) = 'B1,B2,B3,B4';
DECLARE #strC NVARCHAR(MAX) = 'C1,C2,C3';
WITH stringData AS (
--each group of comma separate values with a group identifier
SELECT 'a' AS grp, #strA AS strng
UNION ALL
SELECT 'b' AS grp, #strB
UNION ALL
SELECT 'c' AS grp, #strC
),
splitStrings AS (
--a recursive CTE to split the comma separated values
SELECT grp, CAST('' AS NVARCHAR(MAX)) AS item,
strng AS cText
FROM stringData
UNION ALL
SELECT grp,
CASE
WHEN CHARINDEX(N',',cText,0)>0 THEN LEFT(cText,CHARINDEX(N',',cText,0)-1) --SUBSTRING(cText,0,CHARINDEX(N',',cText,0))
ELSE cText
END,
RIGHT(cText,LEN(cText)-CHARINDEX(N',',cText,0))
FROM splitStrings
WHERE cText!=item
)
SELECT grp,
item,
ROW_NUMBER() OVER(PARTITION BY grp ORDER BY item) AS rnum
INTO #stringValues --put the results in a temp table so we don't need to execute the recursive CTE more than once
FROM splitStrings
WHERE len(item)>0;
DECLARE #maxNum INT = (SELECT MAX(rnum) FROM #stringValues);
--join the values together
WITH allNums AS (
SELECT 1 AS num
UNION ALL
SELECT num+1
FROM allNums
WHERE num<#maxNum
)
SELECT sa.item AS colA,
sb.item AS colB,
sc.item AS colC
FROM allNums
LEFT JOIN #stringValues AS sa ON sa.rnum=allNums.num AND sa.grp='A'
LEFT JOIN #stringValues AS sb ON sb.rnum=allNums.num AND sb.grp='B'
LEFT JOIN #stringValues AS sc ON sc.rnum=allNums.num AND sc.grp='C'
DROP TABLE #stringValues;
I am trying to create a dynamic query in SQL Server.
Input: #value= abc,def,en,
Output: MAX(abc) as abc, MAX(def) as def, MAX(en) as en
My efforts so far took me no where.
With CONVERT() and REPLACE() I achieved a bit but finding it difficult. Need help!
Try this:
declare #value varchar(50) = 'abc,def,en'
declare #result varchar(100) = ''
select #result = replace(#value,'abc', 'MAX(''abc'') as abc')
select #result = replace(#result,'def', 'MAX(''def'') as def')
select #result = replace(#result,'en', 'MAX(''en'') as en')
select #result
You can also do the replacements in one line by nesting the expressions.
EDIT: If you have variable values in #value, you can take the below approach:
Use a splitter function to get the individual values in the string as a list. You can take a look at this article for implementations.
Insert this list to a temp table.
Update the temp table as shown above.
Concatenate the values into a single string using STUFF like so:
select stuff((select ',' + val from #temp for xml path('')),1,1,'')
Try this:
DECLARE #Value VARCHAR(200) = 'abc,def,en'
DECLARE #Template VARCHAR(100) = 'MAX(''##'') as ##'
DECLARE #Result VARCHAR(1000) = ''
DECLARE #Data VARCHAR(100) = ''
WHILE LEN(#Value) > 0
BEGIN
SET #Data = REPLACE(LEFT(#Value, ISNULL(NULLIF(CHARINDEX(',', #Value),0), LEN(#Value))),',','')
SET #Result = #Result + REPLACE(#Template, '##', #Data)
IF CHARINDEX(',', #Value) > 0
BEGIN
SET #Result = #Result + ','
SET #Value = REPLACE(#Value,#Data + ',','')
END
ELSE
SET #Value = REPLACE(#Value,#Data,'')
END
SELECT #Result
Have a look at SQL User Defined Function to Parse a Delimited String
So you can do like
Declare #Value varchar(200) = 'abc,def,en'
Declare #Item varchar(20) = null
declare #Str varchar(1000)=''
WHILE LEN(#Value) > 0
BEGIN
IF PATINDEX('%,%',#Value) > 0
BEGIN
SET #Item = SUBSTRING(#Value, 0, PATINDEX('%,%',#Value))
-- SELECT #Item
IF(LEN(#Str)>0)
SET #Str = #Str + ', SELECT MAX('+#Item+') as ' +#Item
ELSE
SET#Str = #Str + ' SELECT MAX('+#Item+') as ' +#Item
SET #Value = SUBSTRING(#Value, LEN(#Item + ',') + 1, LEN(#Value))
END
ELSE
BEGIN
SET #Item = #Value
SET #Value = NULL
SET #Str = #Str + 'SELECT MAX('+#Item+') as ' + #Item
END
END
select #Str
See the fiddle sample here
I have a string:
#TempCol = sigma_x1,sigma_x2,...,sigma_xd,XX,YY,ZZ
I want to get a substring to get the string sigma_x1,sigma_x2,...,sigma_xd.
d is a variable, so it can be 1, 3, 20, ..., etc.
I know the value of d, but what I don't know is how to get the substring of the original string with d terms.
I tried this:
#L = ''
SET #ColumnNo = 0
WHILE #ColumnNo <= #d
BEGIN
SET #L = #L + ' ' + SUBSTRING(#TempCol, 1, CHARINDEX(',',#TempCol)-1 )
SET #TempCol = REPLACE ( #TempCol, LTRIM(RTRIM(#L) ) ,'')
Set #ColumnNo = #ColumnNo + 1
PRINT #L
END
but I do not know how to get the expected result.
DECLARE #TempCol varchar(max), #d int, #p int, #Result varchar(max);
SET #TempCol = 'item1,item2,itemA,itemB,item#,item$';
SET #d = 3;
SET #p = 1;
WHILE #d > 0 AND #p > 0 BEGIN
SET #p = CHARINDEX(',', #TempCol, #p);
IF #p > 0 SET #p = #p + 1;
SET #d = #d - 1;
END;
IF #p = 0
SET #Result = #TempCol
ELSE
SET #Result = SUBSTRING(#TempCol, 1, #p - 2);
SELECT #Result;
Basically, the loop just searches for the final position to cut at. The substring is extracted after the loop.
If you specify too large #d then the result will simply be all of #TempCol, otherwise you get the desired number of items.
What you need is a split function (shown at the bottom).
With SplitItems As
(
Select Position, Value
, Row_Number() Over ( Order By Position ) As ItemNum
From dbo.udf_Split( #TempCol, ',' )
)
Select Value
From SplitItems
Where ItemNum <= #d
If you want the assembled string up to a given point you would simply do:
With SplitItems As
(
Select Position, Value
, Row_Number() Over ( Order By Position ) As ItemNum
From dbo.udf_Split( #TempCol, ',' )
)
Select ',' + Value
From SplitItems
Where ItemNum <= #d
Order By ItemNum
For Xml Path('')
Split function:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
Create FUNCTION [dbo].[udf_Split]
(
#DelimitedList nvarchar(max)
, #Delimiter nvarchar(2) = ','
)
RETURNS TABLE
AS
RETURN
(
With CorrectedList As
(
Select Case When Left(#DelimitedList, Len(#Delimiter)) <> #Delimiter Then #Delimiter Else '' End
+ #DelimitedList
+ Case When Right(#DelimitedList, Len(#Delimiter)) <> #Delimiter Then #Delimiter Else '' End
As List
, Len(#Delimiter) As DelimiterLen
)
, Numbers As
(
Select TOP( Coalesce(DataLength(#DelimitedList)/2,0) ) Row_Number() Over ( Order By c1.object_id ) As Value
From sys.columns As c1
Cross Join sys.columns As c2
)
Select CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen As Position
, Substring (
CL.List
, CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen
, CharIndex(#Delimiter, CL.list, N.Value + 1)
- ( CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen )
) As Value
From CorrectedList As CL
Cross Join Numbers As N
Where N.Value <= DataLength(CL.List) / 2
And Substring(CL.List, N.Value, CL.DelimiterLen) = #Delimiter
)