I need to parse directory to 5 Level in SQL server with string function.
\ABC\BC\ADS\ADS\123\456\123\ABD
\ABC\BC\ADS\ADS\156\456\123\ABD\123\565
\ABC\BC\ADS\ADS\179\456\123\ABD\123\565\ZYX
I need a below result.
\ABC\BC\ADS\ADS\123\
\ABC\BC\ADS\ADS\156\
\ABC\BC\ADS\ADS\179\
If you are allowed to create a database function, you can achieve your required data.
Create Function:
USE [your_database_name];
GO
ALTER FUNCTION [dbo].[find_nth_char_position]
(#TargetStr VARCHAR(8000),
#SearchedStr VARCHAR(8000),
#Occurrence INT
)
RETURNS INT
AS
BEGIN
DECLARE #pos INT, #counter INT, #ret INT;
SET #pos = CHARINDEX(#TargetStr, #SearchedStr);
SET #counter = 1;
IF #Occurrence = 1
SET #ret = #pos;
ELSE
BEGIN
WHILE(#counter < #Occurrence)
BEGIN
IF CHARINDEX(#TargetStr, #SearchedStr, #pos + 1) = 0
BEGIN
RETURN(0)
END
ELSE
BEGIN
SELECT #ret = CHARINDEX(#TargetStr, #SearchedStr, #pos + 1);
SET #counter = #counter + 1;
SET #pos = #ret;
END
END;
END;
RETURN(#ret);
END;
GO
This above function will return the Nth position of a character in the provided string. As you required the PATH till level 5, I have find the 6th position of character "\" and keep string up to that position using SUBSTRING method.
Select Statement:
SELECT
SUBSTRING
(
<db column name>,
1,
CASE
WHEN [dbo].[find_nth_char_position]('\',<db column name>,6) = 0 THEN LEN(<db column name>)
ELSE [dbo].[find_nth_char_position]('\',<db column name>,6)
END
)
FROM <your_table>
DEMO HERE
You may try with the following approach:
Table:
CREATE TABLE #Data (
TextData varchar(1000)
)
INSERT INTO #Data
(TextData)
VALUES
('\ABC\BC\ADS\ADS\123\456\123\ABD'),
('\ABC\BC\ADS\ADS\156\456\123\ABD\123\565'),
('\ABC\BC\ADS\ADS'),
('\ABC\BC\ADS\ADS\179\456\123\ABD\123\565\ZYX')
Statement:
SELECT CONCAT(Part1, Part2, Part3, Part4, Part5, Part6) AS TextData
FROM #Data d
CROSS APPLY (SELECT LEFT(CONCAT(d.TextData, '\'), CHARINDEX('\', CONCAT(d.TextData, '\'))) AS Part1, STUFF(CONCAT(d.TextData, '\'), 1, CHARINDEX('\', CONCAT(d.TextData, '\')), '') AS TextData) c1
CROSS APPLY (SELECT LEFT(c1.TextData, CHARINDEX('\', c1.TextData)) AS Part2, STUFF(c1.TextData, 1, CHARINDEX('\', c1.TextData), '') AS TextData) c2
CROSS APPLY (SELECT LEFT(c2.TextData, CHARINDEX('\', c2.TextData)) AS Part3, STUFF(c2.TextData, 1, CHARINDEX('\', c2.TextData), '') AS TextData) c3
CROSS APPLY (SELECT LEFT(c3.TextData, CHARINDEX('\', c3.TextData)) AS Part4, STUFF(c3.TextData, 1, CHARINDEX('\', c3.TextData), '') AS TextData) c4
CROSS APPLY (SELECT LEFT(c4.TextData, CHARINDEX('\', c4.TextData)) AS Part5, STUFF(c4.TextData, 1, CHARINDEX('\', c4.TextData), '') AS TextData) c5
CROSS APPLY (SELECT LEFT(c5.TextData, CHARINDEX('\', c5.TextData)) AS Part6, STUFF(c5.TextData, 1, CHARINDEX('\', c5.TextData), '') AS TextData) c6
Output:
--------------------
TextData
--------------------
\ABC\BC\ADS\ADS\123\
\ABC\BC\ADS\ADS\156\
\ABC\BC\ADS\ADS\
\ABC\BC\ADS\ADS\179\
You may try this. By using string_split you can convert your string into rows, then find out the top records as you needed in your code to get the desired result.
After that use stuff to again concatenate into single string and get your output.
declare #tab table ( col varchar(max) )
insert into #tab ( col)
values ( '\ABC\BC\ADS\ADS\123\456\123\ABD' )
,('\ABC\BC\ADS\ADS\156\456\123\ABD\123\565')
,('\ABC\BC\ADS\ADS\179\456\123\ABD\123\565\ZYX')
select stuff((select top 6 '\' + value from string_split(col, '\') for xml path('')),1,1,'') as Result from #tab
--- by using our own created split function, if you are allowed to do so.
select stuff((select top 6 '\' + items from Split(col, '\') for xml path('')),1,1,'') as Result from #tab
Result
----------------
\ABC\BC\ADS\ADS\123
\ABC\BC\ADS\ADS\156
\ABC\BC\ADS\ADS\179
You may create your own split function to make sure that order will not change in parsing.
GO
CREATE FUNCTION [dbo].[Split]
(#String nvarchar(max), #Delimiter char(1))
RETURNS #Results TABLE (Items varchar(200))
AS
BEGIN
DECLARE #INDEX INT
DECLARE #SLICE nvarchar(max)
-- HAVE TO SET TO 1 SO IT DOESNT EQUAL Z
-- ERO FIRST TIME IN LOOP
SELECT #INDEX = 1
WHILE #INDEX !=0
BEGIN
-- GET THE INDEX OF THE FIRST OCCURENCE OF THE SPLIT CHARACTER
SELECT #INDEX = CHARINDEX(#Delimiter,#STRING)
-- NOW PUSH EVERYTHING TO THE LEFT OF IT INTO THE SLICE VARIABLE
IF #INDEX !=0
SELECT #SLICE = LEFT(#STRING,#INDEX - 1)
ELSE
SELECT #SLICE = #STRING
-- PUT THE ITEM INTO THE RESULTS SET
INSERT INTO #Results(Items) VALUES(#SLICE)
-- CHOP THE ITEM REMOVED OFF THE MAIN STRING
SELECT #STRING = RIGHT(#STRING,LEN(#STRING) - #INDEX)
-- BREAK OUT IF WE ARE DONE
IF LEN(#STRING) = 0 BREAK
END
RETURN
END
GO
I have an SP which accepts the Inputtable as parameter, My Inputtable is as shown in the code
`create table inputTable ( id int,ItemQty varchar(100))
insert into inputTable(id, ItemQty) values(1,'a,b,c')
insert into inputTable(id, ItemQty) values(2,'x,y')
insert into inputTable(id, ItemQty) values(3,'l,m,n,o,p')
insert into inputTable(id, ItemQty) values(4,'a,b')
insert into inputTable(id, ItemQty) values(5,'m')`
and SP i have written is like below
`ALTER PROCEDURE [dbo].[Column_Dynamics] (#tablename varchar(50))
AS
BEGIN
-----
declare #maxcount as int
set #maxcount='select MAX(len(ITEMQTY) - len(replace(ITEMQTY, '','', '''')) +1) from '+#tablename
exec('select MAX(len(ITEMQTY) - len(replace(ITEMQTY, '','', '''')) +1) from '+#tablename)
print #maxcount
exec #maxcount
print #maxcount
declare #var varchar(100)
IF EXISTS(SELECT * FROM sys.columns WHERE object_id = Object_id(#tablename))
set #var='alter table '+ #tablename +' ADD column QTY1'
exec(#var)
select * from #tablename
select max(len(ItemQty))-max(len(replace(ItemQty, ',', ''))-1) from inputtable
END`
My table is :
step 1 ) I want to add the columns dynamically to inputtable like QTY1,QTY2,QTY3,QTY4,QTY5 because maximum count of ItemQty column is 5, by considering comma as delimiter as shown in figure1
**step 2) ** Parse values in the respective columns(by considering the delimiter comma (,).as shown in figure2
Later SP: I got till here, But Not getting the second step, that is update Parse values in the respective columns.
ALTER PROCEDURE dynamic_tbl (#tablename varchar(50))
AS
BEGIN
DECLARE #ColumnCount int
DECLARE #rowcount TABLE (Value int);
INSERT INTO #rowcount
EXEC('select MAX(len(ITEMQTY) - len(replace(ITEMQTY, '','', '''')) +1) from '+#tablename);
SELECT #ColumnCount = Value FROM #rowcount;
Declare #ColumnName nvarchar(10)='qty_'
Declare #count int =0
IF(#ColumnCount>0)
BEGIN
IF (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'dyn_tbl'))
BEGIN
DROP TABLE dyn_tbl
END
select * into dyn_tbl from inputtable
SET #count=#count +1;
WHile(#ColumnCount>=#count)
BEGIN
SET #ColumnName='qty_'+CONVERT(varchar(2),#count)
EXEC ('ALTER TABLE dyn_tbl ADD ['+#ColumnName +'] varchar(20)')
declare #myvar as varchar(max)
set #myvar='update '+#tablename+' set '+#ColumnName +' =itemQty'
--exec dynamic_tbl 'dyn_tbl'
--select * from dyn_tbl
--CAST('<A>'+REPLACE(ITEMQTY, ',', '</A><A>')+'</A>' AS XML)
print #myvar
exec(#myvar)
SET #count=#count +1;
END
END
----
END
Procedure to alter given table dynamically based on column length as you have asked
Alter PROCEDURE [dbo].[Column_Dynamics] (#tablename varchar(50))
AS
BEGIN
drop table ##temp
declare #query1 varchar(max)
exec ( '
create table ##temp (id int identity,columnsl varchar(100))
declare #maxcount as int
set #maxcount = (select MAX(len(ITEMQTY) - len(replace(ITEMQTY, '','', '''')) +1) from '+#tablename+')
declare #count int = 1
while (#count <= #maxcount)
begin
declare #colvar nvarchar(100)= ''QTY''
set #colvar = concat(#colvar,#count)
set #count = #count + 1
insert into ##temp select #colvar
end
')
declare #tempstart int = 1
declare #templast int = (select count(*) from ##temp)
declare #updatecol varchar(100) = ''
while (#tempstart <= #templast)
Begin
set #updatecol = (select columnsl from ##temp where id = #tempstart)
exec ('alter table '+#tablename+' Add '+#updatecol+' varchar(100) ')
set #tempstart = #tempstart + 1
end
End
output for inputTable:
id ItemQty QTY1 QTY2 QTY3 QTY4 QTY5
1 a,b,c NULL NULL NULL NULL NULL
2 x,y NULL NULL NULL NULL NULL
3 l,m,n,o,p NULL NULL NULL NULL NULL
4 a,b NULL NULL NULL NULL NULL
5 m NULL NULL NULL NULL NULL
may not be the best way but works.
edit
Altered above procedure to perform both actions, Please use below procedure
Alter PROCEDURE [dbo].[Column_Dynamics] (#tablename varchar(50))
AS
BEGIN
-- declare #tablename varchar(100) = 'inputTable'
drop table #temp if object_id('temp..#temp') is not null drop table #temp
declare #query1 varchar(max)
create table #temp (id int identity,columnsl varchar(100))
exec ( '
declare #maxcount as int
set #maxcount = (select MAX(len(ITEMQTY) - len(replace(ITEMQTY, '','', '''')) +1) from '+#tablename+')
declare #count int = 1
while (#count <= #maxcount)
begin
declare #colvar nvarchar(100)= ''QTY''
set #colvar = concat(#colvar,#count)
set #count = #count + 1
insert into #temp
select #colvar
end
')
declare #tempstart int = 1
declare #templast int = (select count(*) from #temp)
declare #updatecol varchar(100) = ''
declare #itemqty varchar(100)
while (#tempstart <= #templast)
Begin
set #updatecol = (select columnsl from #temp where id = #tempstart)
exec ('alter table '+#tablename+' Add '+#updatecol+' varchar(100) ')
set #tempstart = #tempstart + 1
end
declare #sysvar table (id int identity,cols varchar(100))
insert into #sysvar select sys.columns.name AS ColumnName FROM sys.columns JOIN sys.tables ON sys.columns.object_id = sys.tables.object_id WHERE sys.tables.name = 'inputTable'
declare #finvar table (id int identity,cols varchar(100))
insert into #finvar select cols from #sysvar where id not in (1,2)
declare #cat int = 1 declare #dog int = (select max(id) from inputTable)
while (#cat <= #dog)
begin
drop table #tab2
if object_id('temp..#tab2') is not null drop table #tab2
create table #tab2 (id int identity,fnvalues varchar(100))
set #itemqty = (select itemqty from inputTable where id = #cat)
insert into #tab2 select item from [dbo].[fnSplit](#itemQty,',')
declare #cn int = 1
declare #max int = (select max(id) from #tab2)
declare #sql nvarchar (1000);
while (#cn <= #max)
begin
declare #upcol varchar(100) = (select fnvalues from #tab2 where id = #cn)
declare #plscol varchar(100) = (select cols from #finvar where id = #cn)
set #sql = N'update '+#tablename+' set ' + #plscol + '= '''+#upcol+''' where id = '''+cast(#cat as varchar(10))+''' ';
select #sql
exec sp_executesql #sql;
set #cn = #cn + 1
end
set #cat = #cat + 1
End
End
output:
id ItemQty QTY1 QTY2 QTY3 QTY4 QTY5
1 a,b,c a b c NULL NULL
2 x,y x y NULL NULL NULL
3 l,m,n,o,p l m n o p
4 a,b a b NULL NULL NULL
5 m m NULL NULL NULL NULL
did not optimize the query but works fine.
However, if you have maximum Qty's are known which are in comma separated format then you could use xml node method to separate them into columns.QTY1...QTY5
SELECT DISTINCT ID, ITEMQTY,
a.value('/A[1]', 'VARCHAR(MAX)') as QTY1,
a.value('/A[2]', 'VARCHAR(MAX)') as QTY2,
a.value('/A[3]', 'VARCHAR(MAX)') as QTY3,
a.value('/A[4]', 'VARCHAR(MAX)') as QTY4,
a.value('/A[5]', 'VARCHAR(MAX)') as QTY5
FROM
(
SELECT ID, ITEMQTY,
CAST('<A>'+REPLACE(ITEMQTY, ',', '</A><A>')+'</A>' AS XML) AS ITEMQTY1
FROM inputTable
) A
CROSS APPLY ITEMQTY1.nodes('/A') AS split(a);
Result :
ID ITEMQTY QTY1 QTY2 QTY3 QTY4 QTY5
1 a,b,c a b c NULL NULL
2 x,y x y NULL NULL NULL
3 l,m,n,o,p l m n o p
4 a,b a b NULL NULL NULL
5 m m NULL NULL NULL NULL
Later, you could replace null by using coalesce() or isnull() function with ''
Use This
First Create a function
CREATE FUNCTION [dbo].[fn_split](
#str VARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #returnTable TABLE (idx INT PRIMARY KEY IDENTITY, item VARCHAR(8000))
AS
BEGIN
DECLARE #pos INT
SELECT #str = #str + #delimiter
WHILE LEN(#str) > 0
BEGIN
SELECT #pos = CHARINDEX(#delimiter,#str)
IF #pos = 1
INSERT #returnTable (item)
VALUES (NULL)
ELSE
INSERT #returnTable (item)
VALUES (SUBSTRING(#str, 1, #pos-1))
SELECT #str = SUBSTRING(#str, #pos+1, LEN(#str)-#pos)
END
RETURN
END
GO
and use function like this
Declare #test TABLE (
ID VARCHAR(200),
Data VARCHAR(200)
)
insert into #test
(ID, Data)
Values
(1,'a,b,c')
insert into #test
(ID, Data )
values(2,'x,y')
insert into #test
(ID, Data )
values(3,'l,m,n,o,p')
insert into #test
(ID, Data )
values(4,'a,b')
insert into #test
(ID, Data )
values(5,'m')
select ID,data AS ItemQty,
ISNULL((select item from fn_split(Data,',') where idx in (1)),'') as QTY1 ,
ISNULL((select item from fn_split(Data,',') where idx in (2)),'') as QTY2,
ISNULL((select item from fn_split(Data,',') where idx in (3)),'') as QTY3,
ISNULL((select item from fn_split(Data,',') where idx in (4)),'') as QTY5 ,
ISNULL((select item from fn_split(Data,',') where idx in (5)),'') as QTY5
from #test
Output Same as your Image
instead of using
insert into #test
(ID, Data)
Values
(1,'a,b,c')
you can also assgin it like this
insert into #test
(ID, Data)
Values
(Select Column1, Column2 From YourTable)
I’m looking to split '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15...' (comma delimited) into a table or table variable.
Does anyone have a function that returns each one in a row?
Try this
DECLARE #xml xml, #str varchar(100), #delimiter varchar(10)
SET #str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
SET #delimiter = ','
SET #xml = cast(('<X>'+replace(#str, #delimiter, '</X><X>')+'</X>') as xml)
SELECT C.value('.', 'varchar(10)') as value FROM #xml.nodes('X') as X(C)
OR
DECLARE #str varchar(100), #delimiter varchar(10)
SET #str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
SET #delimiter = ','
;WITH cte AS
(
SELECT 0 a, 1 b
UNION ALL
SELECT b, CHARINDEX(#delimiter, #str, b) + LEN(#delimiter)
FROM CTE
WHERE b > a
)
SELECT SUBSTRING(#str, a,
CASE WHEN b > LEN(#delimiter)
THEN b - a - LEN(#delimiter)
ELSE LEN(#str) - a + 1 END) value
FROM cte WHERE a > 0
Many more ways of doing the same is here How to split comma delimited string?
Here is somewhat old-fashioned solution:
/*
Splits string into parts delimitered with specified character.
*/
CREATE FUNCTION [dbo].[SDF_SplitString]
(
#sString nvarchar(2048),
#cDelimiter nchar(1)
)
RETURNS #tParts TABLE ( part nvarchar(2048) )
AS
BEGIN
if #sString is null return
declare #iStart int,
#iPos int
if substring( #sString, 1, 1 ) = #cDelimiter
begin
set #iStart = 2
insert into #tParts
values( null )
end
else
set #iStart = 1
while 1=1
begin
set #iPos = charindex( #cDelimiter, #sString, #iStart )
if #iPos = 0
set #iPos = len( #sString )+1
if #iPos - #iStart > 0
insert into #tParts
values ( substring( #sString, #iStart, #iPos-#iStart ))
else
insert into #tParts
values( null )
set #iStart = #iPos+1
if #iStart > len( #sString )
break
end
RETURN
END
In SQL Server 2008 you can achieve the same with .NET code. Maybe it would work faster, but definitely this approach is easier to manage.
You've tagged this SQL Server 2008 but future visitors to this question (using SQL Server 2016+) will likely want to know about STRING_SPLIT.
With this new builtin function you can now just use
SELECT TRY_CAST(value AS INT)
FROM STRING_SPLIT ('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15', ',')
Some restrictions of this function and some promising results of performance testing are in this blog post by Aaron Bertrand.
This is most like .NET, for those of you who are familiar with that function:
CREATE FUNCTION dbo.[String.Split]
(
#Text VARCHAR(MAX),
#Delimiter VARCHAR(100),
#Index INT
)
RETURNS VARCHAR(MAX)
AS BEGIN
DECLARE #A TABLE (ID INT IDENTITY, V VARCHAR(MAX));
DECLARE #R VARCHAR(MAX);
WITH CTE AS
(
SELECT 0 A, 1 B
UNION ALL
SELECT B, CONVERT(INT,CHARINDEX(#Delimiter, #Text, B) + LEN(#Delimiter))
FROM CTE
WHERE B > A
)
INSERT #A(V)
SELECT SUBSTRING(#Text,A,CASE WHEN B > LEN(#Delimiter) THEN B-A-LEN(#Delimiter) ELSE LEN(#Text) - A + 1 END) VALUE
FROM CTE WHERE A >0
SELECT #R
= V
FROM #A
WHERE ID = #Index + 1
RETURN #R
END
SELECT dbo.[String.Split]('121,2,3,0',',',1) -- gives '2'
here is the split function that u asked
CREATE FUNCTION [dbo].[split](
#delimited NVARCHAR(MAX),
#delimiter NVARCHAR(100)
) RETURNS #t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
AS
BEGIN
DECLARE #xml XML
SET #xml = N'<t>' + REPLACE(#delimited,#delimiter,'</t><t>') + '</t>'
INSERT INTO #t(val)
SELECT r.value('.','varchar(MAX)') as item
FROM #xml.nodes('/t') as records(r)
RETURN
END
execute the function like this
select * from dbo.split('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15',',')
DECLARE
#InputString NVARCHAR(MAX) = 'token1,token2,token3,token4,token5'
, #delimiter varchar(10) = ','
DECLARE #xml AS XML = CAST(('<X>'+REPLACE(#InputString,#delimiter ,'</X><X>')+'</X>') AS XML)
SELECT C.value('.', 'varchar(10)') AS value
FROM #xml.nodes('X') as X(C)
Source of this response:
http://sqlhint.com/sqlserver/how-to/best-split-function-tsql-delimited
I am tempted to squeeze in my favourite solution. The resulting table will consist of 2 columns: PosIdx for position of the found integer; and Value in integer.
create function FnSplitToTableInt
(
#param nvarchar(4000)
)
returns table as
return
with Numbers(Number) as
(
select 1
union all
select Number + 1 from Numbers where Number < 4000
),
Found as
(
select
Number as PosIdx,
convert(int, ltrim(rtrim(convert(nvarchar(4000),
substring(#param, Number,
charindex(N',' collate Latin1_General_BIN,
#param + N',', Number) - Number))))) as Value
from
Numbers
where
Number <= len(#param)
and substring(N',' + #param, Number, 1) = N',' collate Latin1_General_BIN
)
select
PosIdx,
case when isnumeric(Value) = 1
then convert(int, Value)
else convert(int, null) end as Value
from
Found
It works by using recursive CTE as the list of positions, from 1 to 100 by default. If you need to work with string longer than 100, simply call this function using 'option (maxrecursion 4000)' like the following:
select * from FnSplitToTableInt
(
'9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
'9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
'9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
'9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
'9, 8, 7, 6, 5, 4, 3, 2, 1, 0'
)
option (maxrecursion 4000)
CREATE FUNCTION Split
(
#delimited nvarchar(max),
#delimiter nvarchar(100)
) RETURNS #t TABLE
(
-- Id column can be commented out, not required for sql splitting string
id int identity(1,1), -- I use this column for numbering splitted parts
val nvarchar(max)
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#delimited,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(max)') as item
from #xml.nodes('//root/r') as records(r)
RETURN
END
GO
usage
Select * from dbo.Split(N'1,2,3,4,6',',')
This simple CTE will give what's needed:
DECLARE #csv varchar(max) = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15';
--append comma to the list for CTE to work correctly
SET #csv = #csv + ',';
--remove double commas (empty entries)
SET #csv = replace(#csv, ',,', ',');
WITH CteCsv AS (
SELECT CHARINDEX(',', #csv) idx, SUBSTRING(#csv, 1, CHARINDEX(',', #csv) - 1) [Value]
UNION ALL
SELECT CHARINDEX(',', #csv, idx + 1), SUBSTRING(#csv, idx + 1, CHARINDEX(',', #csv, idx + 1) - idx - 1) FROM CteCsv
WHERE CHARINDEX(',', #csv, idx + 1) > 0
)
SELECT [Value] FROM CteCsv
This is another version which really does not have any restrictions (e.g.: special chars when using xml approach, number of records in CTE approach) and it runs much faster based on a test on 10M+ records with source string average length of 4000. Hope this could help.
Create function [dbo].[udf_split] (
#ListString nvarchar(max),
#Delimiter nvarchar(1000),
#IncludeEmpty bit)
Returns #ListTable TABLE (ID int, ListValue nvarchar(1000))
AS
BEGIN
Declare #CurrentPosition int, #NextPosition int, #Item nvarchar(max), #ID int, #L int
Select #ID = 1,
#L = len(replace(#Delimiter,' ','^')),
#ListString = #ListString + #Delimiter,
#CurrentPosition = 1
Select #NextPosition = Charindex(#Delimiter, #ListString, #CurrentPosition)
While #NextPosition > 0 Begin
Set #Item = LTRIM(RTRIM(SUBSTRING(#ListString, #CurrentPosition, #NextPosition-#CurrentPosition)))
If #IncludeEmpty=1 or LEN(#Item)>0 Begin
Insert Into #ListTable (ID, ListValue) Values (#ID, #Item)
Set #ID = #ID+1
End
Set #CurrentPosition = #NextPosition+#L
Set #NextPosition = Charindex(#Delimiter, #ListString, #CurrentPosition)
End
RETURN
END
/* *Object: UserDefinedFunction [dbo].[Split] Script Date: 10/04/2013 18:18:38* */
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[Split]
(#List varchar(8000),#SplitOn Nvarchar(5))
RETURNS #RtnValue table
(Id int identity(1,1),Value nvarchar(100))
AS
BEGIN
Set #List = Replace(#List,'''','')
While (Charindex(#SplitOn,#List)>0)
Begin
Insert Into #RtnValue (value)
Select
Value = ltrim(rtrim(Substring(#List,1,Charindex(#SplitOn,#List)-1)))
Set #List = Substring(#List,Charindex(#SplitOn,#List)+len(#SplitOn),len(#List))
End
Insert Into #RtnValue (Value)
Select Value = ltrim(rtrim(#List))
Return
END
go
Select *
From [Clv].[Split] ('1,2,3,3,3,3,',',')
GO
Using tally table here is one split string function(best possible approach) by Jeff Moden
CREATE FUNCTION [dbo].[DelimitedSplit8K]
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover NVARCHAR(4000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
Referred from Tally OH! An Improved SQL 8K “CSV Splitter” Function
This blog came with a pretty good solution using XML in T-SQL.
This is the function I came up with based on that blog (change function name and result type cast per need):
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[SplitIntoBigints]
(#List varchar(MAX), #Splitter char)
RETURNS TABLE
AS
RETURN
(
WITH SplittedXML AS(
SELECT CAST('<v>' + REPLACE(#List, #Splitter, '</v><v>') + '</v>' AS XML) AS Splitted
)
SELECT x.v.value('.', 'bigint') AS Value
FROM SplittedXML
CROSS APPLY Splitted.nodes('//v') x(v)
)
GO
CREATE Function [dbo].[CsvToInt] ( #Array varchar(4000))
returns #IntTable table
(IntValue int)
AS
begin
declare #separator char(1)
set #separator = ','
declare #separator_position int
declare #array_value varchar(4000)
set #array = #array + ','
while patindex('%,%' , #array) <> 0
begin
select #separator_position = patindex('%,%' , #array)
select #array_value = left(#array, #separator_position - 1)
Insert #IntTable
Values (Cast(#array_value as int))
select #array = stuff(#array, 1, #separator_position, '')
end
This works great for me https://www.sqlshack.com/the-string-split-function-in-sql-server/
After two hours of resarching this topic this is the simplest solution (without using XML ect.).
You should only remember to use string_split after from.
DROP TABLE IF EXISTS #Countries
GO
DROP TABLE IF EXISTS #CityList
GO
CREATE TABLE #Countries
(Continent VARCHAR(100),
Country VARCHAR(100))
GO
CREATE TABLE #CityList
(Country VARCHAR(100),
City VARCHAR(5000))
GO
INSERT INTO #Countries
VALUES('Europe','France'),('Europe','Germany')
INSERT INTO #CityList
VALUES('France','Paris,Marsilya,Lyon,Lille,Nice'), ('Germany','Berlin,Hamburg,Munih,Frankfurt,Koln')
SELECT
CN.Continent,CN.Country,value
FROM #CityList CL CROSS APPLY string_split(CL.City,',') INNER JOIN
#Countries CN ON CL.Country = CN.Country
DROP TABLE IF EXISTS #Countries
GO
DROP TABLE IF EXISTS #CityList
You write this function in sql server after that problem will be solved.
http://csharpdotnetsol.blogspot.in/2013/12/csv-function-in-sql-server-for-divide.html
I have comma separated column which represents the ids of cities like:
ID | Name
1 | 1,2,3
2 | 2,3,4
I want to make query to get name of the this value field. There is City Table which has two columns: id and name of cities
EXPECTED OUTPUT
ID | VALUES
1 | mumbai,delhi,pune
2 | delhi,pune,chennai
I can make a query if there is only one id in a column like:
select data.id,city.name from data,city where data.values=city.cityid
but I am not getting how to retrieve the data if there are multiple comma-separated values in one field.
The easy way is to convert CSV values to rows for each Id, join that with CITY table and convert back to CSV values. I have written the logic inside the query.
;WITH CTE1 AS
(
-- Convert CSV to rows
SELECT Id,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'NAME'
FROM
(
-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
SELECT Id,CAST ('<M>' + REPLACE(Name, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM #TEMP
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
,CTE2 AS
(
-- Now join the values in rows with Id in CITY table
SELECT T.ID,T.NAME,C.CITYNAME
FROM CTE1 T
JOIN #CITY C ON T.NAME=C.ID
)
-- Now convert back to CSV format
SELECT DISTINCT ID,
SUBSTRING(
(SELECT ', ' + CITYNAME
FROM CTE2 I
WHERE I.Id=O.Id
FOR XML PATH('')),2,200000) [VALUES]
FROM CTE2 O
Click here to view result
to do this please do following sections:
1-Create Function to get table of comma separate value in each row
CREATE FUNCTION [dbo].[fn_Split](
#ForigenKey INT,
#String NVARCHAR (4000),
#Delimiter NVARCHAR(10)
)
RETURNS #ValueTable TABLE ([ID] INT IDENTITY NOT NULL,FID int null,[Value] NVARCHAR(4000))
BEGIN
DECLARE #NextString NVARCHAR(4000)
DECLARE #Pos INT
DECLARE #NextPos INT
DECLARE #CommaCheck NVARCHAR(1)
--Initialize
SET #NextString = ''
SET #CommaCheck = RIGHT(#String,1)
--Check for trailing Comma, if not exists, INSERT
--if (#CommaCheck <> #Delimiter )
SET #String = #String + #Delimiter
--Get position of first Comma
SET #Pos = CHARINDEX(#Delimiter,#String)
SET #NextPos = LEN(#Delimiter)
--Loop while there is still a comma in the String of levels
WHILE (#pos <> 0)
BEGIN
SET #NextString = SUBSTRING(#String, 1, #Pos - 1)
INSERT INTO #ValueTable ( FID,[Value]) VALUES (#ForigenKey ,#NextString)
SET #String = SUBSTRING(#String,#pos + LEN(#Delimiter),LEN(#String))
SET #NextPos = #Pos
SET #pos = CHARINDEX(#Delimiter,#String)
END
RETURN
END
GO
2- create Concat Aggregate with the folwing link
Concat Aggregate
3- you can get your data with below select
DECLARE #ID INT,#Name NVARCHAR(4000)
DECLARE #ValueTable table ([ID] int NOT NULL,[Value] INT)
DECLARE mycur CURSOR FOR
SELECT TOP(1000) ID,Name FROM TableA
OPEN mycur
FETCH NEXT FROM mycur INTO #ID,#Name
WHILE(##FETCH_STATUS=0)
BEGIN
INSERT INTO #ValueTable
( ID, Value )
SELECT #ID,Value FROM dbo.fn_Split(#Name,',')
FETCH NEXT FROM mycur INTO #ID,#Name
END
CLOSE mycur
DEALLOCATE mycur
SELECT * FROM #ValueTable
SELECT ID,dbo.ConcatAggregate(CityName) FROM #ValueTable
inner join city on value=cityid GROUP BY ID
Is there an easy way of determining the most frequently occuring word in a column/field using T-SQL or VBA?
I am working on a fuzzy matching system for two given recordsets and would like to produce a matching string where the most frequently occuring words are removed. As the data is from a customer relations management database terms like "limited", "ltd", "plc" and "CORPORATION" would be removed.
Written for sql-server 2005+
Function to split:
create function f_split
(
#a varchar(max),
#delimiter varchar(20)
)
RETURNS #t TABLE(substr varchar(200))
as
begin
set #a = #a + #delimiter
;with a as
(
select cast(1 as bigint) f1, charindex(#delimiter, #a) f2
where len(#a) > 0
union all
select f2 + (len(#delimiter)) + 1, charindex(#delimiter, #a, f2+1)
from a
where f2 > 0
)
insert #t
select substring(#a, f1, f2 - f1) from a
where f1 < f2
return
end
go
Query:
--testdata
declare #table table(name varchar(50))
insert #table values('bla bla bla ltd')
insert #table values('bla plc ltd')
insert #table values('more text CORPORATION')
declare #matchlist table(name varchar(50), replacement varchar(50))
insert #matchlist values('ltd', 'limited')
insert #matchlist values('plc', 'limited')
insert #matchlist values('CORPORATION', 'limited')
--query
select coalesce(m.replacement, a.substr) name, count(*) count from #table p
cross apply
(
select substr from
dbo.f_split(p.name, ' ')
) a
left join
#matchlist m
on a.substr = m.name
group by coalesce(m.replacement, a.substr)
order by 2 desc
Result:
name count
---- -----
bla 4
limited 4
more 1
text 1
Hope this will be useful to you.
create table sometable
( id integer not null primary key identity
, mYWords text not null
);
insert into sometable (mYWords)
values ('a word that appears maximum number of times in a column')
insert into sometable (mYWords)
values ('Is it possible to get words from text columns in a sql server database')
insert into sometable (mYWords)
values ('This could solve my problem if reffered column contain only single word')
insert into sometable (mYWords)
values ('that''s going to require that you split out every word in the column individually')
insert into sometable (mYWords)
values ('the query will definitely not be easy to write')
insert into sometable (mYWords)
values ('Please read the sticky at the top of the board')
insert into sometable (mYWords)
values ('The physical order of data in a database has no meaning')
GO
CREATE TABLE WordList (
Word varchar(256)
, WordId int IDENTITY(1,1)
, Add_Dt datetime DEFAULT (GetDate()))
GO
CREATE UNIQUE INDEX UnqueWords_PK ON WordList(Word)
GO
CREATE PROC isp_INS_WORD_LIST
AS
BEGIN
SET NOCOUNT ON
DECLARE #Words INT, #Pos INT, #x Int, #str varchar(256)
, #word varchar(256), #start int, #end int, #exitstart int
SELECT #Words = 0, #Pos = 1, #x = -1, #Word = '', #start = 1
DECLARE Cur1 CURSOR FOR SELECT mYWords FROM sometable
OPEN Cur1
FETCH NEXT FROM Cur1 INTO #str
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE (#x <> 0)
BEGIN
SET #x = CHARINDEX(' ', #str, #Pos)
IF #x <> 0
BEGIN
SET #end = #x - #start
SET #word = SUBSTRING(#str,#start,#end)
IF NOT EXISTS (SELECT * FROM WordList WHERE Word = #Word)
INSERT INTO WordList(Word) SELECT #word
-- SELECT #Word, ##ROWCOUNT,##ERROR
-- SELECT #x, #Word, #start, #end, #str
SET #exitstart = #start + #end + 1
SET #Pos = #x + 1
SET #start = #x + 1
SET #Words = #Words + 1
END
IF #x = 0
BEGIN
SET #word = SUBSTRING(#str,#exitstart,LEN(#str)-#exitstart+1)
IF NOT EXISTS (SELECT * FROM WordList WHERE Word = #Word)
INSERT INTO WordList(Word) SELECT #word
-- SELECT #Word, ##ROWCOUNT,##ERROR
-- SELECT #x, #Word, #exitstart, LEN(#str)-#exitstart, #str
END
END
FETCH NEXT FROM Cur1 INTO #str
SELECT #Words = 0, #Pos = 1, #x = -1, #Word = '', #start = 1
END
CLOSE Cur1
DEALLOCATE Cur1
SET NOCOUNT OFF
RETURN #Words
END
GO
EXEC isp_INS_WORD_LIST
GO
SELECT * FROM WordList ORDER BY Word
GO
DROP PROC isp_INS_WORD_LIST
DROP TABLE WordList, sometable
GO