Related
I have a table A which contain column "ColumnName" (it will contain the comma separated integer values ) and I have store procedure which take parameter which is also comma separated integer values.
For example I have values in table "101,102,103" and "103,104,105" and User input is "101,104" it should return 2 record. how can I achieve this?
Need SQL statements
This should do it:
CREATE PROCEDURE GetMatches(#input varchar (100))
AS
BEGIN
WITH CTE AS
(
SELECT value AS number
FROM STRING_SPLIT(#input, ',')
)
SELECT CTE.number, A.ColumnName
FROM A
INNER JOIN CTE
ON ',' + A.ColumnName + ',' LIKE '%,' + CTE.number + ',%';
END
You can test the stored procedure like this:
EXEC dbo.GetMatches #input = '101,104';
If you need help, explanations, let me know :)
DECLARE
#input VARCHAR(MAX) = '101,104',
#separator CHAR(1) =',',
#separatorPosition INT,
#value VARCHAR(MAX),
#start INT = 1
DECLARE #split_input TABLE(Value VARCHAR(MAX))
DECLARE #tmp TABLE (st VARCHAR(50))
INSERT INTO #tmp VALUES ('101,102,103')
INSERT INTO #tmp VALUES ('103,104,105')
INSERT INTO #tmp VALUES ('106,107,108')
SET #separatorPosition = CHARINDEX(#separator, #input)
IF #separatorPosition = 0
BEGIN
INSERT INTO #split_input
VALUES
(
#input
)
RETURN
END
SET #input = #input + #separator
WHILE #separatorPosition > 0
BEGIN
SET #value = SUBSTRING(#input, #start, #separatorPosition - #start)
IF (#value <> '')
INSERT INTO #split_input
VALUES
(
#value
)
SET #start = #separatorPosition + 1
SET #separatorPosition = CHARINDEX(#separator, #input, #start)
END
SELECT tmp.st FROM #tmp AS tmp INNER JOIN #split_input AS split ON tmp.st LIKE '%' + split.Value + '%'
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[fnParseArray] (#Array VARCHAR(MAX),#separator CHAR(1))
RETURNS #T Table (col1 varchar(50))
AS
BEGIN
-- #Array is the array we wish to parse
-- #Separator is the separator charactor such as a comma
DECLARE #separator_position INT -- This is used to locate each separator character
DECLARE #array_value VARCHAR(MAX) -- this holds each array value as it is returned
-- For my loop to work I need an extra separator at the end. I always look to the
-- left of the separator character for each array value
SET #array = #array + #separator
-- Loop through the string searching for separtor characters
WHILE PATINDEX('%' + #separator + '%', #array) <> 0
BEGIN
-- patindex matches the a pattern against a string
SELECT #separator_position = PATINDEX('%' + #separator + '%',#array)
SELECT #array_value = LEFT(#array, #separator_position - 1)
-- This is where you process the values passed.
INSERT into #T VALUES (#array_value)
-- Replace this select statement with your processing
-- #array_value holds the value of this element of the array
-- This replaces what we just processed with and empty string
SELECT #array = STUFF(#array, 1, #separator_position, '')
END
RETURN
END
select * from [dbo].[fnParseArray]('a,b,c,d',',')
SQL Query to retrieve 2 digit numbers from the below string
String --> 'Partial:[64][95]'
The output should be in the below format.
64,95
You can use the replace() function several times to replace parts of the string.
In the example below I've set a declared variable to your example string:
DECLARE #mystring varchar(100) = 'Partial:[64][95]'
SELECT REPLACE(REPLACE(REPLACE(#mystring, 'Partial:[', '')
, '][', ',')
, ']', '') AS [answer]
Produces output:
answer
------
64,95
Use replace() :
select *, replace(replace(replace(Partial, '][', ','), '[', ''), ']', '')
from table t;
However, this would reduce your nested replace via TRANSLATE() but available from SQL Server 2017.
I was able to get the result with the below query.
DECLARE #string VARCHAR(100);
SET #string = 'Partial:[64][95]';
WHILE PATINDEX('%[^0-9]%', #string) <> 0
SET #string = STUFF(#string, PATINDEX('%[^0-9]%', #string), 1, '');
DECLARE #splitstring NVARCHAR(20) = #string;
DECLARE #i INT = 3;
WHILE #i < LEN(#splitstring)
BEGIN
SELECT #splitstring = STUFF(#splitstring, #i, 0, ',');
SET #i = #i +3;
END;
SELECT #splitstring;
Thanks everyone for the help.
I have a list of ids separated by comma like:
1,17,25,44,46,67,88
I want to convert them to a table records ( into a temporary table ) like
#tempTable
number_
--------
1
17
25
44
46
67
88
It is possible with a function, a table-valued one ?
Why I want this ? I want to use for INNER JOIN clause (into stored procedure) with another table(s) like as:
SELECT a,b,c FROM T1
INNER JOIN functionNameWhichReturnsTable
ON functionNameWhichReturnsTable.number_ = T1.a
I cannot use IN because I will use stored procedure which accepts a parameter of type NVARCHAR. That parameter will provide the list of ids.
Thank you
Possible duplicate of separate comma separated values and store in table in sql server.
Please try a precise one from Comma-Delimited Value to Table:
CREATE FUNCTION [dbo].[ufn_CSVToTable] ( #StringInput VARCHAR(8000), #Delimiter nvarchar(1))
RETURNS #OutputTable TABLE ( [String] VARCHAR(10) )
AS
BEGIN
DECLARE #String VARCHAR(10)
WHILE LEN(#StringInput) > 0
BEGIN
SET #String = LEFT(#StringInput,
ISNULL(NULLIF(CHARINDEX(#Delimiter, #StringInput) - 1, -1),
LEN(#StringInput)))
SET #StringInput = SUBSTRING(#StringInput,
ISNULL(NULLIF(CHARINDEX(#Delimiter, #StringInput), 0),
LEN(#StringInput)) + 1, LEN(#StringInput))
INSERT INTO #OutputTable ( [String] )
VALUES ( #String )
END
RETURN
END
GO
Check the requirement in other way using XML:
DECLARE #param NVARCHAR(MAX)
SET #param = '1:0,2:1,3:1,4:0'
SELECT
Split.a.value('.', 'VARCHAR(100)') AS CVS
FROM
(
SELECT CAST ('<M>' + REPLACE(#param, ',', '</M><M>') + '</M>' AS XML) AS CVS
) AS A CROSS APPLY CVS.nodes ('/M') AS Split(a)
Here's a trick that doesn't need a function or XML.
Basically the string gets transformed into a single insert statement for a temporary table.
The temp table can then be used for further processing.
IF OBJECT_ID('tempdb..#tmpNum') IS NOT NULL
DROP TABLE #tmpNum;
CREATE TABLE #tmpNum (num int);
DECLARE #TEXT varchar(max) = '1,17,25,44,46,67,88';
DECLARE #InsertStatement varchar(max);
SET #InsertStatement = 'insert into #tmpNum (num) values ('+REPLACE(#TEXT,',','),(')+');';
EXEC (#InsertStatement);
-- use the temp table
SELECT *
FROM YourTable t
WHERE t.id IN (SELECT DISTINCT num FROM #tmpNum);
This method is usable for up to 1000 values.
Because 1000 is the max limit of a row value expression.
Also, as Stuart Ainsworth pointed out.
Since this method uses Dynamic Sql, be wary of code injection and don't use it for strings based on user input.
Side-note
Starting from MS Sql Server 2016, one could simply use the STRING_SPLIT function.
DECLARE #TEXT varchar(max);
SET #TEXT = '1,17,25,44,46,67,88';
SELECT t.*
FROM YourTable t
JOIN (SELECT DISTINCT CAST(value AS INT) num FROM STRING_SPLIT(#TEXT, ',')) nums
ON t.id = nums.num;
Completing the answers, you could also use the CSV string to store multiple values in multiple columns:
--input sql text
declare #text_IN varchar(max) ='text1, text1.2, text1.3, 1, 2010-01-01\r\n text2, text2.2, text2.3, 2, 2016-01-01'
Split the csv file into rows:
declare #temptable table (csvRow varchar(max))
declare #DelimiterInit varchar(4) = '\r\n'
declare #Delimiter varchar(1) = '|'
declare #idx int
declare #slice varchar(max)
set #text_IN = REPLACE(#text_IN,#DelimiterInit,#Delimiter)
select #idx = 1
if len(#text_IN)<1 or #text_IN is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#text_IN)
if #idx!=0
set #slice = left(#text_IN,#idx - 1)
else
set #slice = #text_IN
if(len(#slice)>0)
insert into #temptable(csvRow) values(#slice)
set #text_IN = right(#text_IN,len(#text_IN) - #idx)
if len(#text_IN) = 0 break
end
Split rows into columns:
;WITH XMLTable (xmlTag)
AS
(
SELECT CONVERT(XML,'<CSV><champ>' + REPLACE(csvRow,',', '</champ><champ>') + '</champ></CSV>') AS xmlTag
FROM #temptable
)
SELECT RTRIM(LTRIM(xmlTag.value('/CSV[1]/champ[1]','varchar(max)'))) AS Column1,
RTRIM(LTRIM(xmlTag.value('/CSV[1]/champ[2]','varchar(max)'))) AS Column2,
RTRIM(LTRIM(xmlTag.value('/CSV[1]/champ[3]','varchar(max)'))) AS Column3,
RTRIM(LTRIM(xmlTag.value('/CSV[1]/champ[4]','int'))) AS Column4,
RTRIM(LTRIM(xmlTag.value('/CSV[1]/champ[5]','datetime'))) AS Column5
FROM XMLTable
The following works:
declare #parStoreNo As varchar(8000) = '1,2,3,4'
CREATE TABLE #parStoreNo (StoreNo INT)-- drop #parStoreNo
declare #temptable VARCHAR(1000) = #parStoreNo
declare #SQL VARCHAR(1000)
SELECT #SQL = CONVERT(VARCHAR(1000),' select ' + REPLACE(ISNULL(#temptable,' NULL '),',', ' AS Col UNION ALL SELECT '))
INSERT #parStoreNo (StoreNo)
EXEC (#SQL)
I am using XML Function as below...
DECLARE #str VARCHAR(4000) = '6,7,7,8,10,12,13,14,16,44,46,47,394,396,417,488,714,717,718,719,722,725,811,818,832,833,836,837,846,913,914,919,922,923,924,925,926,927,927,928,929,929,930,931,932,934,935,1029,1072,1187,1188,1192,1196,1197,1199,1199,1199,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1366,1367,1387,1388,1666,1759,1870,2042,2045,2163,2261,2374,2445,2550,2676,2879,2880,2881,2892,2893,2894'
Declare #x XML
select #x = cast('<A>'+ replace(#str,',','</A><A>')+ '</A>' as xml)
select t.value('.', 'int') as inVal
from #x.nodes('/A') as x(t)
I prefer this because not need to create any separate function and proc. Also I don't have to opt dynamic SQL query which I prefer most.
Convert Comma Separated String to Table
DECLARE #str VARCHAR(4000) = '6,7,7,8,10,12,13,14,16,44,46,47,394,396,417,488,714,717,718,719,722,725,811,818,832'
DECLARE #x XML
select #x = cast('<A>'+ replace(#str,',','</A><A>')+ '</A>' as xml)
select t.value('.', 'int') as inVal
from #x.nodes('/A') as x(t)
Try this code
SELECT RTRIM(part) as part
INTO Table_Name
FROM dbo.splitstring(#Your_Comma_string,',')
splitstring Function is as follows
CREATE FUNCTION dbo.splitstring ( #stringToSplit VARCHAR(MAX) )
RETURNS
#returnList TABLE ([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
My table has one column that contain strings like: ” HRM_APPLICATION_DELAY_IN”
I want to perform bellow operations on each row on this column
convert to lower case
remove underscore “_”
change case (convert to upper case) of the character after the underscore like: ” hrm_Application_Delay_In”
Need help for conversion. Thanks for advance
Here is a function to achieve it:
create function f_test
(
#a varchar(max)
)
returns varchar(max)
as
begin
set #a = lower(#a)
while #a LIKE '%\_%' ESCAPE '\'
begin
select #a = stuff(#a, v, 2, upper(substring(#a, v+1,1)))
from (select charindex('_', #a) v) a
end
return #a
end
Example:
select dbo.f_test( HRM_APPLICATION_DELAY_IN')
Result:
hrmApplicationDelayIn
To update your table here is an example how to write the syntax with the function:
UPDATE <yourtable>
SET <yourcolumn> = dbo.f_test(col)
WHERE <yourcolumn> LIKE '%\_%' ESCAPE '\'
For a variable this is overkill, but I'm using this to demonstrate a pattern
declare #str varchar(100) = 'HRM_APPLICATION_DELAY_IN';
;with c(one,last,rest) as (
select cast(lower(left(#str,1)) as varchar(max)),
left(#str,1), stuff(lower(#str),1,1,'')
union all
select one+case when last='_'
then upper(left(rest,1))
else left(rest,1) end,
left(rest,1), stuff(rest,1,1,'')
from c
where rest > ''
)
select max(one)
from c;
That can be extended to a column in a table
-- Sample table
declare #tbl table (
id int identity not null primary key clustered,
str varchar(100)
);
insert #tbl values
('HRM_APPLICATION_DELAY_IN'),
('HRM_APPLICATION_DELAY_OUT'),
('_HRM_APPLICATION_DELAY_OUT'),
(''),
(null),
('abc<de_fg>hi');
-- the query
;with c(id,one,last,rest) as (
select id,cast(lower(left(str,1)) as varchar(max)),
left(str,1), stuff(lower(str),1,1,'')
from #tbl
union all
select id,one+case when last='_'
then upper(left(rest,1))
else left(rest,1) end,
left(rest,1), stuff(rest,1,1,'')
from c
where rest > ''
)
select id,max(one)
from c
group by id
option (maxrecursion 0);
-- result
ID COLUMN_1
1 hrm_Application_Delay_In
2 hrm_Application_Delay_Out
3 _Hrm_Application_Delay_Out
4
5 (null)
6 abc<de_Fg>hi
select
replace(replace(replace(replace(replace(replace(replace(
replace(replace(replace(replace(replace(replace(replace(
replace(replace(replace(replace(replace(replace(replace(
replace(replace(replace(replace(replace(replace(lower('HRM_APPLICATION_DELAY_IN'),'_a','A'),'_b','B'),'_c','C'),'_d','D'),'_e','E'),'_f','F'),
'_g','G'),'_h','H'),'_i','I'),'_j','J'),'_k','K'),'_l','L'),
'_m','M'),'_n','N'),'_o','O'),'_p','P'),'_q','Q'),'_r','R'),
'_s','S'),'_t','T'),'_u','U'),'_v','V'),'_w','W'),'_x','X'),
'_y','Y'),'_z','Z'),'_','')
Bellow two steps can solve problem,as example i use sys.table.user can use any one
declare #Ret varchar(8000), #RetVal varchar(8000), #i int, #count int = 1;
declare #c varchar(10), #Text varchar(8000), #PrevCase varchar, #ModPrefix varchar(10);
DECLARE #FileDataTable TABLE(TableName varchar(200))
INSERT INTO #FileDataTable
select name FROM sys.tables where object_name(object_id) not like 'sys%' order by name
SET #ModPrefix = 'Pur'
DECLARE crsTablesTruncIns CURSOR
FOR select TableName FROM #FileDataTable
OPEN crsTablesTruncIns
FETCH NEXT FROM crsTablesTruncIns INTO #Text
WHILE ##FETCH_STATUS = 0
BEGIN
SET #RetVal = '';
select #i=1, #Ret = '';
while (#i <= len(#Text))
begin
SET #c = substring(#Text,#i,1)
--SET #Ret = #Ret + case when #Reset=1 then UPPER(#c) else LOWER(#c)
IF(#PrevCase = '_' OR #i = 1)
SET #Ret = UPPER(#c)
ELSE
SET #Ret = LOWER(#c)
--#Reset = case when #c like '[a-zA-Z]' then 0 else 1 end,
if(#c like '[a-zA-Z]')
SET #RetVal = #RetVal + #Ret
if(#c = '_')
SET #PrevCase = '_'
else
SET #PrevCase = ''
SET #i = #i +1
end
SET #RetVal = #ModPrefix + #RetVal
print cast(#count as varchar) + ' ' + #RetVal
SET #count = #count + 1
EXEC sp_RENAME #Text , #RetVal
SET #RetVal = ''
FETCH NEXT FROM crsTablesTruncIns INTO #Text
END
CLOSE crsTablesTruncIns
DEALLOCATE crsTablesTruncIns
I'd like to show you my nice and simple solution. It uses Tally function to split the string by pattern, in our case by underscope. For understanding Tally functions, read this article.
So, this is how my tally function looks like:
CREATE FUNCTION [dbo].[tvf_xt_tally_split](
#String NVARCHAR(max)
,#Delim CHAR(1))
RETURNS TABLE
as
return
(
WITH Tally AS (SELECT top (select isnull(LEN(#String),100)) n = ROW_NUMBER() OVER(ORDER BY [name]) from master.dbo.syscolumns)
(
SELECT LTRIM(RTRIM(SUBSTRING(#Delim + #String + #Delim,N+1,CHARINDEX(#Delim,#Delim + #String + #Delim,N+1)-N-1))) Value, N as Ix
FROM Tally
WHERE N < LEN(#Delim + #String + #Delim)
AND SUBSTRING(#Delim + #String + #Delim,N,1) = #Delim
)
)
This function returns a table, where each row represents part of string between #Delim (in our case between underscopes). Rest of the work is simple, just cobination of LEFT, RIGHT, LEN, UPPER and LOWER functions.
declare #string varchar(max)
set #string = ' HRM_APPLICATION_DELAY_IN'
-- convert to lower case
set #string = LOWER(#string)
declare #output varchar(max)
-- build string
select #output = coalesce(#output + '_','') +
UPPER(left(Value,1)) + RIGHT(Value, LEN(Value) - 1)
from dbo.tvf_xt_tally_split(#string, '_')
-- lower first char
select left(lower(#output),1) + RIGHT(#output, LEN(#output) - 1)
how can i get SQL to take a sting and return the first letter of each word passed into it.
I want to use this UDF for generating initials for peoples names I have in the DB.
names can be 2 (fname, lname)or 3(...mname) words
i am using sql2005
This should work for both "Firstname Lastname" and "Firstname Middlename Lastname" combinations.
DECLARE #name AS NVARCHAR(50)
SET #name = 'Firstname Middle Lastname'
SELECT SUBSTRING(#name, 1, 1) + --First initial
SUBSTRING(#name, CHARINDEX(' ', #name) + 1, 1) + --Middle/Last initial
CASE WHEN 0 <> CHARINDEX(' ', #name, CHARINDEX(' ', #name) + 1) -- More than two words
THEN SUBSTRING(#name, CHARINDEX(' ', #name, CHARINDEX(' ', #name) + 1) + 1, 1) --Last initial
ELSE '' --Have to add empty string to avoid NULLing entire result
END
Of course, if users have a space in one of their names for some reason you will have an issue parsing this out, but I suspect that would be the case anyways when not storing your names in separate fields.
For SQL Server 2017 and newer:
CREATE FUNCTION dbo.fnGetFirstChars (#string NVARCHAR(max), #seperator NVARCHAR(MAX))
RETURNS NVARCHAR(max)
AS BEGIN
DECLARE #result NVARCHAR(MAX)
SELECT #result = STRING_AGG(SUBSTRING(value, 1, 1), '')
FROM STRING_SPLIT(#string, #seperator)
RETURN #result
END;
CREATE FUNCTION dbo.GetFirstLetter ( #Array VARCHAR(1000), #separator VARCHAR(10))
RETURNS #resultTable TABLE
(parseValue VARCHAR(100))
AS
BEGIN
DECLARE #separator_position INT
DECLARE #array_value VARCHAR(1000)
SET #array = #array + #separator
WHILE patindex('%' + #separator + '%' , #array) <> 0
BEGIN
SELECT #separator_position = patindex('%' + #separator + '%', #array)
SELECT #array_value = left(#array, #separator_position - 1)
INSERT #resultTable
VALUES (SUBSTRING(Cast(#array_value AS varchar), 1, 1))
SELECT #array = stuff(#array, 1, #separator_position, '')
END
RETURN
END
Here's my solution, and it has these features/peculiarities:
It can process however many names there are in the string. (That is, both less than two and more than three.)
All spaces between the names are preserved.
I know the OP has specified that there can only be 2 or 3 names in his case. I don't mind. I'm just sharing a solution that works, and if it's not best for the particular problem, it's fine.
So, here's the function:
CREATE FUNCTION dbo.fnGetInitials (#name varchar(max))
RETURNS varchar(max)
AS BEGIN
DECLARE #cutpos int, #spacepos int, #result varchar(max);
DECLARE #cutlist TABLE (CutPos int, SpacePos int);
SET #result = LTRIM(RTRIM(#name));
SET #cutpos = 2;
SET #spacepos = CHARINDEX(' ', #result);
WHILE #spacepos > 0 BEGIN
INSERT INTO #cutlist VALUES (#cutpos, #spacepos);
SET #spacepos = #spacepos + 1;
SET #cutpos = #spacepos + 1;
SET #spacepos = CHARINDEX(' ', #result, #spacepos);
END;
DELETE FROM #cutlist WHERE CutPos >= SpacePos;
SELECT #result = STUFF(#result, CutPos, SpacePos - CutPos, '')
FROM #cutlist
ORDER BY CutPos DESC;
RETURN #result;
END;
And here's a test call:
SELECT dbo.fnGetInitials(' John Ronald Reuel Tolkien ');
and the result:
----------------------------------------------------------------------------------------------------
J R R Tolkien
You can achieve it via xquery as well.
Declare #Xml XML
Declare #String Varchar(Max)
Declare #firstletter Varchar(Max)
Declare #delimiter Varchar(5)
SET #delimiter=' '
SET #String= 'THIS IS SQL'
SET #Xml = cast(('<a>'+replace(#String,#delimiter,'</a><a>')+'</a>') AS XML)
;WITH CTE AS
(SELECT A.value('.', 'varchar(max)') as [Column]FROM #Xml.nodes('a') AS FN(a) )
SELECT Stuff((SELECT '' + LEFT([Column],1)from CTE
FOR XML PATH ('') ),1,0,'')
Here is the complete solution.
http://raresql.com/2013/04/12/sql-server-get-the-first-letter-of-each-word-in-a-string-column/
A Picture is 100 times better than description. Here is an example of UDF declaration:
CREATE FUNCTION dbo.GetOnlyFirstLetters(#str NVARCHAR(4000),#sep NVARCHAR(10) )
RETURNS NVARCHAR(100)
AS
BEGIN
DECLARE #textXML XML
SELECT #textXML = CAST('<d>' + replace(#str, #sep, '</d><d>') + '</d>' AS XML)
DECLARE #result VARCHAR(8000)
SET #result = ''
SELECT #result = #result + LEFT(T.split.value ('.', 'nvarchar(max)'), 1)
FROM #textXML.nodes ('/d') T (split)
RETURN #result
END
GO
Here is how to call:
SELECT dbo.GetOnlyFirstLetters('Humayoun Kabir Sohel',' ');
Result will be:
HKS