Split string and number columns - sql

Let's say I have a table such as
ItemID ClassID
------------------------
1 10, 13, 12
2 5, 7
and would like to copy the data to another table like so
ItemID Numbering ClassID
----------------------------------
1 1 10
1 2 13
1 3 12
2 1 5
2 2 7
Separating the comma-delimited ClassID field into individual rows, retaining the order they had in the first table
Populating the Numbering row on insert. The Numbering column has sequential integers for each batch of ClassID and is why ClassID needs to be kept in order.
I have attempted this with the following function:
CREATE FUNCTION dbo.Split
(
#String NVARCHAR(MAX)
)
RETURNS #SplittedValues TABLE(
Value INT
)
AS
BEGIN
DECLARE #SplitLength INT
DECLARE #Delimiter VARCHAR(10)
SET #Delimiter = ','
WHILE len(#String) > 0
BEGIN
SELECT #SplitLength = (CASE charindex(#Delimiter, #String)
WHEN 0 THEN
datalength(#String) / 2
ELSE
charindex(#Delimiter, #String) - 1
END)
INSERT INTO #SplittedValues
SELECT cast(substring(#String, 1, #SplitLength) AS INTEGER)
WHERE
ltrim(rtrim(isnull(substring(#String, 1, #SplitLength), ''))) <> '';
SELECT #String = (CASE ((datalength(#String) / 2) - #SplitLength)
WHEN 0 THEN
''
ELSE
right(#String, (datalength(#String) / 2) - #SplitLength - 1)
END)
END
RETURN
END
but it only partly works. It copies the rows the correct amount of times (i.e. three times for ItemID=1, and twice for ItemID=2 in the above example), but they are exact copies of the row (all saying '10, 13, 12') and the comma-delimited parts are not split up. There is also nothing in the function to add to the Numbering column.
So, I have two questions: How do I modify the above function to split up the ClassID string, and what do I add to correctly increment the Numbering column?
Thanks!

I'd use a recursive CTE to do it.
WITH SplitCTE AS
(
SELECT
itemid,
LEFT(ClassID,CHARINDEX(',',ClassID)-1) AS ClassID
,RIGHT(ClassID,LEN(ClassID)-CHARINDEX(',',ClassID)) AS remaining
FROM table1
WHERE ClassID IS NOT NULL AND CHARINDEX(',',ClassID)>0
UNION ALL
SELECT
itemid,
LEFT(remaining,CHARINDEX(',',remaining)-1)
,RIGHT(remaining,LEN(remaining)-CHARINDEX(',',remaining))
FROM SplitCTE
WHERE remaining IS NOT NULL AND CHARINDEX(',',remaining)>0
UNION ALL
SELECT
itemid,remaining,null
FROM SplitCTE
WHERE remaining IS NOT NULL AND CHARINDEX(',',remaining)=0
)
SELECT
itemid,
row_number() over (partition by itemid order by cast(classid as int) asc) as Numbering,
cast (ClassID as int) as ClassID
FROM
SplitCTE
UNION ALL
select
ItemId,
1,
cast(classid as int)
FROM table1
WHERE ClassID IS NOT NULL AND CHARINDEX(',',ClassID) = 0
SQL Fiddle

DECLARE #t TABLE( ID INT IDENTITY, data VARCHAR(50))
INSERT INTO #t(data) SELECT '10, 13, 12'
INSERT INTO #t(data) SELECT '5, 7'
select F1.id,O.splitdata, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT 1))
from (
select *,cast(''+replace(F.data,',','')+'' as XML) as xmlfilter from #t F
)F1
cross apply
(
select fdata.D.value('.','varchar(50)') as splitdata from f1.xmlfilter.nodes('X') as fdata(D)
) O

Related

Add rows to result set from comma delimited data

I have a select statement returning data in the following format- (from a bad database design)
ID Numbers
3 6,7,8
and I need to tweak it to return
ID Number
3 6
3 7
3 8
to fix it. What's the best way to do this? I do not need a permanent function, I just need the result set from a select query to import into a new database.
thanks!
Since you are using SQL Server 2015, your best option is to write a UDF like so:
CREATE FUNCTION [dbo].[udfSplit]
(
#sInputList VARCHAR(8000) -- List of delimited items
,
#sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
)
RETURNS #List TABLE ( item VARCHAR(8000) )
BEGIN
DECLARE #sItem VARCHAR(8000);
WHILE CHARINDEX(#sDelimiter, #sInputList, 0) <> 0
BEGIN
SELECT #sItem = RTRIM(LTRIM(SUBSTRING(#sInputList, 1,
CHARINDEX(#sDelimiter,
#sInputList, 0)
- 1))) ,
#sInputList = RTRIM(LTRIM(SUBSTRING(#sInputList,
CHARINDEX(#sDelimiter,
#sInputList, 0)
+ LEN(#sDelimiter),
LEN(#sInputList))));
IF LEN(#sItem) > 0
INSERT INTO #List
SELECT #sItem;
END;
IF LEN(#sInputList) > 0
INSERT INTO #List
SELECT #sInputList; -- Put the last item in
RETURN;
END;
And use it like so:
SELECT ID ,
item
FROM ( SELECT 3 AS ID ,
'6,7,8' AS Numbers
) x
CROSS APPLY udfSplit(x.Numbers, ',');
Try this:
DECLARE #YourTable Table (
ID INT IDENTITY(1,1)
, Number VARCHAR(10)
)
INSERT INTO #YourTable
VALUES
('1,2,3')
, ('4,5,6,7')
, ('8,9')
, ('10')
;WITH CTE
AS
(
SELECT 1 AS ID
UNION ALL
SELECT ID + 1 FROM CTE
WHERE ID < 100
)
SELECT
x.ID
, SUBSTRING(x.Number, t.ID, CHARINDEX(',', x.Number + ',', t.ID) - t.ID) AS Number
FROM #YourTable x
JOIN CTE t
ON t.ID <= DATALENGTH(x.Number)+1
AND SUBSTRING(',' + x.Number, t.ID, 1) = ','
ORDER BY ID, Number
More information can be found here: http://www.sqlservercentral.com/articles/Tally+Table/72993/

Stored Procedure to Insert comma seperated values as multiple records

Please help me in creating a stored procedure which accepts comma separated values and inserts as multiple rows.
So one parameter #Name will contain values A,B,C and the other parameter #Id will contain values as 1,2,3
The table values after insertion should be as below:
Name Id
------------
A 1
A 2
A 3
B 1
B 2
B 3
C 1
C 2
C 3
How can I write a stored procedure that can insert the comma-separated values as shown above. Also, If the table already consists of a Name,id pair for example, if A,2 is already there in the table, then it should not insert.
I am using SQL Server 2005. Thanks in advance.
Something like this?
DECLARE #var1 VARCHAR(100)='A,B,C';
DECLARE #var2 VARCHAR(100)='1,2,3';
WITH rep1(name, delim) AS
(
SELECT #var1 name, ',' delim
UNION ALL
SELECT LEFT(name, CHARINDEX(delim, name, 1) - 1) name, delim
FROM rep1
WHERE (CHARINDEX(delim, name, 1) > 0)
UNION ALL
SELECT RIGHT(name, LEN(name) - CHARINDEX(delim, name, 1)) name, delim
FROM rep1
WHERE (CHARINDEX(delim, name, 1) > 0)
)
,rep2(id, delim) AS
(
SELECT #var2 id, ',' delim
UNION ALL
SELECT LEFT(id, CHARINDEX(delim, id, 1) - 1) id, delim
FROM rep2
WHERE (CHARINDEX(delim, id, 1) > 0)
UNION ALL
SELECT RIGHT(id, LEN(id) - CHARINDEX(delim, id, 1)) id, delim
FROM rep2
WHERE (CHARINDEX(delim, id, 1) > 0)
)
INSERT #table
(Name
,ID)
SELECT
r1.name
,r2.id
FROM rep1 r1
CROSS JOIN rep2 r2
LEFT JOIN #table t
ON r2.id=t.id
AND t.name=r1.name
WHERE (CHARINDEX(r1.delim, r1.name, 1) = 0)
AND (CHARINDEX(r2.delim, r2.id, 1) = 0)
AND t.name IS NULL
ORDER BY r1.name
,r2.id
OPTION (MAXRECURSION 0);
Here we are sepearting Comma Seperated into rows
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
IF OBJECT_ID('tempdb..#NewTemp') IS NOT NULL
DROP TABLE #NewTemp
Declare #Testdata table ( name Varchar(max), Data varchar(max))
insert #Testdata select 'A', '1,2,3'
insert #Testdata select 'B', '1,2,3'
insert #Testdata select 'C', '1,2'
insert #Testdata select 'A', '1,2,3,4'
insert #Testdata select 'C', '1,2,3,4,5'
;with tmp(name, DataItem, Data) as (
select name, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from #Testdata
union all
select name, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from tmp
where Data > ''
)
Then Inserting into Temp Table
select DISTINCT name, DataItem INTO #Temp
from tmp WHERE EXISTS (Select DISTINCT name,DataItem from tmp)
order by name
Here we are controlling entry of Duplicates we can observe combination won't repeat like (A,1),(B,1)Even though they are multiple
CREATE TABLE #NewTemp(name Varchar(max), Data varchar(max))
INSERT INTO #NewTemp (name,Data)
Select name,DataItem from #Temp
Select * FROM #NewTemp
You can go and create one user defined functions for splitting the comma separated values into rows as below
How this function will work and more on it can be found here
CREATE FUNCTION dbo.Split
(
#RowData nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
DECLARE #index INT
SET #index = Charindex(#SplitOn,#RowData)
While (#index>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,#index-1)))
Set #RowData = Substring(#RowData,#index+1,len(#RowData))
Set #Cnt = #Cnt + 1
SET #index = Charindex(#SplitOn,#RowData)
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END
Once this function is created, you can use it for your requirement as below
declare #Name VARCHAR(30)
declare #Id VARCHAR(30)
SET #Name = 'A,B,C'
SET #Id = '1,2,3'
select A.Data,B.Data FROM dbo.Split(#name,',') A ,dbo.Split(#id,',') B

SQL Query for Min and Max Values [duplicate]

This question already has answers here:
How to split a comma-separated value to columns
(38 answers)
Closed 8 years ago.
I have the following data in a table. The number of values in each row can vary and the number of rows could also vary.
The table has 1 column with csv formatted values. The values will always be numeric
Data
1,2
4
5,12, 10
6,7,8,9,10
15,17
I would like to end up with a temp table with the following
Data Lowest Highest
1,2 1 2
4 4 4
5,12, 10 5 12
6,7,8,9,10 6 10
15,17 15 17
Can anyone help with writing a sql query or function to achieve this
Instead of function, you can achieve by this
;WITH tmp
AS (SELECT A.rn,split.a.value('.', 'VARCHAR(100)') AS String
FROM (SELECT Row_number() OVER(ORDER BY (SELECT NULL)) AS RN,
Cast ('<M>' + Replace([data], ',', '</M><M>') + '</M>' AS XML) AS String
FROM table1) AS A
CROSS apply string.nodes ('/M') AS Split(a))
SELECT X.data,Tmp.lower,Tmp.higher
FROM (SELECT rn,Min(Cast(string AS INT)) AS Lower,Max(Cast(string AS INT)) AS Higher
FROM tmp
GROUP BY rn) Tmp
JOIN (SELECT Row_number() OVER(ORDER BY (SELECT NULL)) AS RN1,data
FROM table1) X
ON X.rn1 = Tmp.rn
FIDDLE DEMO
Output would be:
Data Lower Higher
1,2 1 2
4 4 4
5,12, 10 5 12
6,7,8,9,10 6 10
15,17 15 17
First create a user defined function to convert each row of 'DATA' column to a intermediate table as:
/****** Object: UserDefinedFunction [dbo].[CSVToTable]******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[CSVToTable] (#InStr VARCHAR(MAX))
RETURNS #TempTab TABLE
(id int not null)
AS
BEGIN
;-- Ensure input ends with comma
SET #InStr = REPLACE(#InStr + ',', ',,', ',')
DECLARE #SP INT
DECLARE #VALUE VARCHAR(1000)
WHILE PATINDEX('%,%', #INSTR ) <> 0
BEGIN
SELECT #SP = PATINDEX('%,%',#INSTR)
SELECT #VALUE = LEFT(#INSTR , #SP - 1)
SELECT #INSTR = STUFF(#INSTR, 1, #SP, '')
INSERT INTO #TempTab(id) VALUES (#VALUE)
END
RETURN
END
GO
Function is explained further here.
Then Using Cross Apply we can get the desired output as:
With CTE as
(
select
T.Data, Min(udf.Id) as [Lowest],Max(udf.Id) as [Highest]
from
Test T
CROSS APPLY dbo.CSVToTable(T.Data) udf
Group By Data
)
Select * from CTE
Sample Code here...
What a Cross Apply does is : it applies the right table expression to each row from the left table and produces a result table with the unified result sets.
Create table #temp1 (name varchar(100),value int )
Declare #len int
Select #len=(select max(LEN(name)-LEN(replace(name,',',''))) from table)
Declare #i int = 1
while (#i<=#len+1)
begin
insert into #temp1
select name,PARSENAME(REPLACE(name,',','.'),#i) from table t
set #i = #i+1
end
Select name,MIN(value) MINV,MAX(value) MAXV from #temp1 group by name
declare #Testdata table ( Data varchar(max))
insert #Testdata select '1,2'
insert #Testdata select '4'
insert #Testdata select '5,12, 10'
insert #Testdata select '6,7,8,9,10'
;with tmp( DataItem, Data, RN1) as (
select LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), ''),
ROW_NUMBER()OVER(ORDER BY (SELECT NULL))AS RN1
from #Testdata
union all
select LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), ''),RN1
from tmp
where Data > ''
)
Select x.data,t.Low,t.Up FROM
(Select RN1,MIN(Cast(DataItem AS INT)) As Low,
MAX(Cast(DataItem AS INT)) As Up
FROM tmp t GROUP BY t.RN1)t
JOIN (Select ROW_NUMBER()OVER(ORDER BY (SELECT NULL))AS RN,data from #Testdata)X
ON X.RN = t.RN1

Tricky SQL query requiring search for contains

I have data such as this:
Inventors column in my table
Hundley; Edward; Ana
Isler; Hunsberger
Hunsberger;Hundley
Names are separated by ;. I want to write a SQL query which sums up the count.
Eg. The result should be:
Hundley 2
Isler 1
Hunsberger 2
Edward 1
Ana 1
I could do a group by but this is not a simple group by as you can see. Any ideas/thoughts on how to get this output?
Edit: Changed results so it doesn't create any confusion that a row only contains 2 names.
You can take a look at this. I certainly do not recommend this way if you have lots of data, BUT you can do some modifications and use it and it works like a charm!
This is the new code for supporting unlimited splits:
Declare #Table Table (
Name Nvarchar(50)
);
Insert #Table (
Name
) Select 'Hundley; Edward; Anna'
Union Select 'Isler; Hunsberger'
Union Select 'Hunsberger; Hundley'
Union Select 'Anna'
;
With Result (
Part
, Remained
, [Index]
, Level
) As (
Select Case When CharIndex(';', Name, 1) = 0
Then Name
Else Left(Name, CharIndex(';', Name, 1) - 1)
End
, Right(Name, Len(Name) - CharIndex(';', Name, 1))
, CharIndex(';', Name, 1)
, 1
From #Table
Union All
Select LTrim(
Case When CharIndex(';', Remained, 1) = 0
Then Remained
Else Left(Remained, CharIndex(';', Remained, 1) - 1)
End
)
, Right(Remained, Len(Remained) - CharIndex(';', Remained, 1))
, CharIndex(';', Remained, 1)
, Level
+ 1
From Result
Where [Index] <> 0
) Select Part
, Count(*)
From Result
Group By Part
Cheers
;with cte as
(
select 1 as Item, 1 as Start, CHARINDEX(';',inventors, 1) as Split, Inventors from YourInventorsTable
union all
select cte.Item+1, cte.Split+1, nullif(CHARINDEX(';',inventors, cte.Split+1),0), inventors as Split
from cte
where cte.Split<>0
)
select rTRIM(lTRIM(SUBSTRING(inventors, start,isnull(split,len(inventors)+1)-start))), count(*)
from cte
group by rTRIM(lTRIM(SUBSTRING(inventors, start,isnull(split,len(inventors)+1)-start)))
You can create a split function to split the col values
select splittedValues.items,count(splittedValues) from table1
cross apply dbo.split(col1,';') splittedValues
group by splittedValues.items
DEMO in Sql fiddle
first make one function who take your comma or any other operator(;) separated string into one table and by using that temp table, apply GROUP function on that table.
So you will get count for separate value.
"select d.number,count(*) from (select number from dbo.CommaseparedListToTable('Hundley;Edward;Ana;Isler;Hunsberger;Hunsberger;Hundley',';'))d
group by d.number"
declare #text nvarchar(255) = 'Edward; Hundley; AnaIsler; Hunsberger; Hunsberger; Hundley ';
declare #table table(id int identity,name varchar(50));
while #text like '%;%'
Begin
insert into #table (name)
select SUBSTRING(#text,1,charindex(';',#text)-1)
set #text = SUBSTRING(#text, charindex(';',#text)+1,LEN(#text))
end
insert into #table (name)
select #text
select name , count(name ) counts from #table group by name
Output
name count
AnaIsler 1
Hundley 2
Hunsberger 2
Edward 1

SQL Server - sum comma separated value from a column

There is a column in database which contains comma separated values like: 0.00,12.45,14.33 and so on.
I need to sum this inside a stored procedure. One way which I can think of is to split and convert it into a table using a function and then sum it.
Any other ideas?
Using Sql Server 2005+ CTE you can create a recursive select, something like
DECLARE #Table TABLE(
ID INT,
Vals VARCHAR(100)
)
INSERT INTO #Table SELECT 1, '0.00,12.45,14.33'
INSERT INTO #Table SELECT 2, '1,2,3,4'
;WITH ValList AS(
SELECT ID,
CAST(LEFT(Vals,PATINDEX('%,%', Vals) - 1) AS FLOAT) Val,
RIGHT(Vals,LEN(Vals) - PATINDEX('%,%', Vals)) Remainder
FROM #Table
UNION ALL
SELECT ID,
CAST(LEFT(Remainder,CASE WHEN PATINDEX('%,%', Remainder) = 0 THEN LEN(Remainder) ELSE PATINDEX('%,%', Remainder) - 1 END) AS FLOAT) Val,
RIGHT(Remainder,CASE WHEN PATINDEX('%,%', Remainder) = 0 THEN 0 ELSE LEN(Remainder) - PATINDEX('%,%', Remainder) END) Remainder
FROM ValList
WHERE LEN(Remainder) > 0
)
SELECT ID,
SUM(Val)
FROM ValList
GROUP BY ID
OUTPUT
ID Total
----------- ----------------------
1 26.78
2 10
within a function you could try something like this, totally unsure if it will work tho!
CREATE FUNCTION ufn_sum_csv(#string varchar(100))
RETURNS #result int
AS BEGIN
EXEC 'SELECT #result = ' + REPLACE(#string,',','+')
RETURN
Can't try it out on this comp.