how to Split And Count cell at Sql Server - sql

i have a table like this
Food |
---------------|
nasi |
nasi goreng |
nasi uduk |
nasi,ikan,lalap|
nasi |
i want result count nasi = 3, nasi goreng = 1 nasi uduk = 1
how to Split And Count cell at Sql Server ?

You could use a string split function and CROSS APPLY
DECLARE #SampleData AS TABLE
(
Food varchar(100)
)
INSERT INTO #SampleData
VALUES
('nasi'),
('nasi goreng'),
('nasi uduk'),
('nasi,ikan,lalap'),
('nasi')
SELECT ca.Value AS Food,
count(*) AS Count
FROM #SampleData sd
CROSS APPLY
(
Select * from [dbo].[SplitString](sd.Food,',')
) ca
GROUP BY ca.Value
String split function
CREATE FUNCTION [dbo].[SplitString] (#Text varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos = Row_Number() over (Order By (Select null))
,Value = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#Text,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Returns
Food Count
---------------------
ikan 1
lalap 1
nasi 3
nasi goreng 1
nasi uduk 1
Hope it helps...

Select food_Name,COUNT(*) from (
SELECT
Tbl.Col.value('./text()[1]','varchar(50)')as food_Name
FROM
(Select cast('<a>'+ replace((SELECT food As [*] FOR XML PATH('')), ',', '</a><a>') + '</a>' as xml)as t
from Your_Table) tl
Cross apply
tl.t.nodes('/a') AS Tbl(Col))t2
group by food_Name

Related

How to Split 2 Strings and insert in to 2 columns

I have 2 strings
Declare #WhenDetails NVarchar(Max) ='07:00:0:0;1:00:1:0;6:00:1:0;10:00:1:0;'
Declare #Dosage NVarchar(Max) ='1.00;2.00;1.00;1.00'
I need to split these 2 string and insert into a table
Example at 07:00:0:0=>1.00 1:00:1:0=>2.00
Declare #TempDosageWhenDetails Table (RowID INT IDENTITY(1,1), PatientMedicationID INT, Dosage NVARCHAR(Max),WhenDetails NVARCHAR(Max))
insert #TempDosageWhenDetails(Dosage)
select x.items
from dbo.Split('07:00:0:0;1:00:1:0;6:00:1:0;10:00:1:0;', ';') x
I have taken a table and split and inserted my when details
How can fill the dosage column as shown in the example?
Note I might have n number of records to split I have given these just an example.
Perhaps with a little JSON (assuming 2016+)
Example
Declare #WhenDetails NVarchar(Max) ='07:00:0:0;1:00:1:0;6:00:1:0;10:00:1:0;'
Declare #Dosage NVarchar(Max) ='1.00;2.00;1.00;1.00'
Select RowID = A.[Key]+1
,PatientID = null
,Dosage = B.[Value]
,WhenDetails = A.[Value]
From (
Select *
From OpenJSON( '["'+replace(#WhenDetails,';','","')+'"]' )
) A
Join (
Select *
From OpenJSON( '["'+replace(#Dosage,';','","')+'"]' )
) B
on A.[Key]=B.[Key]
Returns
RowID PatientID Dosage WhenDetails
1 NULL 1.00 07:00:0:0
2 NULL 2.00 1:00:1:0
3 NULL 1.00 6:00:1:0
4 NULL 1.00 10:00:1:0
If it helps with the Visualization:
We convert the strings into a JSON array, then it is a small matter to join the results based on the KEY
If you were to
Select * From OpenJSON( '["'+replace(#WhenDetails,';','","')+'"]' )
The results would be
key value type
0 07:00:0:0 1
1 1:00:1:0 1
2 6:00:1:0 1
3 10:00:1:0 1
EDIT - XML Approach
Select RowID = A.RetSeq
,PatientID = null
,Dosage = B.RetVal
,WhenDetails = A.RetVal
From (
Select RetSeq = row_number() over (order by 1/0)
,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#WhenDetails,';','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) A
Join (
Select RetSeq = row_number() over (order by 1/0)
,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#Dosage,';','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B
on A.RetSeq=B.RetSeq

MS SQL Server Get value between commas

I have a column in Table1 with string in it separated by commma:
Id Val
1 ,4
2 ,3,1,0
3 NULL
4 ,5,2
Is there a simple way to split and get any value from that column,
for example
SELECT Value(1) FROM Table1 should get
Id Val
1 4
2 3
3 NULL
4 5
SELECT Value(2) FROM Table1 should get
Id Val
1 NULL
2 1
3 NULL
4 2
Thank you!
Storing comma separated values in a column is always a pain, consider changing your table structure
To get this done, create a split string function. Here is one of the best possible approach to split the string to individual rows. Referred from http://www.sqlservercentral.com/articles/Tally+Table/72993/
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
to call the function
SELECT *
FROM yourtable
CROSS apply (SELECT CASE WHEN LEFT(val, 1) = ',' THEN Stuff(val, 1, 1, '') ELSE val END) cs (cleanedval)
CROSS apply [dbo].[Delimitedsplit8k](cs.cleanedval, ',')
WHERE ItemNumber = 1
SELECT *
FROM yourtable
CROSS apply (SELECT CASE WHEN LEFT(val, 1) = ',' THEN Stuff(val, 1, 1, '') ELSE val END) cs (cleanedval)
CROSS apply [dbo].[Delimitedsplit8k](cs.cleanedval, ',')
WHERE ItemNumber = 2
Another option using a Parse/Split Function and an OUTER APPLY
Example
Declare #YourTable Table ([Id] int,[Val] varchar(50))
Insert Into #YourTable Values
(1,',4')
,(2,',3,1,0')
,(3,NULL)
,(4,',5,2')
Select A.ID
,Val = B.RetVal
From #YourTable A
Outer Apply (
Select * From [dbo].[tvf-Str-Parse](A.Val,',')
Where RetSeq = 2
) B
Returns
ID Val
1 4
2 3
3 NULL
4 5
The UDF if Interested
CREATE FUNCTION [dbo].[tvf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Here is an example of using a CTE combined with converting the CSV to XML:
DECLARE #Test TABLE (
CsvData VARCHAR(10)
);
INSERT INTO #Test (CsvData)
VALUES
('1,2,3'),
(',4,5,7'),
(NULL),
(',3,');
WITH XmlData AS (
SELECT CONVERT(XML, '<val>' + REPLACE(CsvData, ',', '</val><val>') + '</val>') [CsvXml]
FROM #Test
)
SELECT xd.CsvXml.value('val[2]', 'VARCHAR(10)')
FROM XmlData xd;
This would output:
2
4
NULL
3
The column to display is controlled by the XPath query. In this case, val[2].
The main advantage here is that no user-defined functions are required.
Try This Logic Using recursive CTE
DECLARE #Pos INT = 2
DECLARE #T TABLE
(
Id INT,
Val VARCHAR(50)
)
INSERT INTO #T
VALUES(1,',4'),(2,',3,1,0'),(3,NULL),(4,',5,2')
;WITH CTE
AS
(
SELECT
Id,
SeqNo = 0,
MyStr = SUBSTRING(Val,CHARINDEX(',',Val)+1,LEN(Val)),
Num = REPLACE(SUBSTRING(Val,1,CHARINDEX(',',Val)),',','')
FROM #T
UNION ALL
SELECT
Id,
SeqNo = SeqNo+1,
MyStr = CASE WHEN CHARINDEX(',',MyStr)>0
THEN SUBSTRING(MyStr,CHARINDEX(',',MyStr)+1,LEN(MyStr))
ELSE NULL END,
Num = CASE WHEN CHARINDEX(',',MyStr)>0
THEN REPLACE(SUBSTRING(MyStr,1,CHARINDEX(',',MyStr)),',','')
ELSE MyStr END
FROM CTE
WHERE ISNULL(REPLACE(MyStr,',',''),'')<>''
)
SELECT
T.Id,
CTE.Num
FROM #T t
LEFT JOIN CTE
ON T.Id = cte.Id
AND SeqNo = #Pos
My Output for the above
Test Data
Declare #t TABLE (Id INT , Val VARCHAR(100))
INSERT INTO #t VALUES
(1 , '4'),
(2 , '3,1,0'),
(3 , NULL),
(4 , '5,2')
Function Definition
CREATE FUNCTION [dbo].[fn_xml_Splitter]
(
#delimited nvarchar(max)
, #delimiter nvarchar(1)
, #Position INT = NULL
)
RETURNS TABLE
AS
RETURN
(
SELECT Item
FROM (
SELECT Split.a.value('.', 'VARCHAR(100)') Item
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ItemNumber
FROM
(SELECT Cast ('<X>' + Replace(#delimited, #delimiter, '</X><X>')
+ '</X>' AS XML) AS Data
) AS t CROSS APPLY Data.nodes ('/X') AS Split(a)
)x
WHERE x.ItemNumber = #Position OR #Position IS NULL
);
GO
Function Call
Now you can call this function in two different ways.
1 . to get return an Item on a specific position, specify the position in the 3rd parameter of the function:
SELECT *
FROM #t t
CROSS APPLY [dbo].[fn_xml_Splitter](t.Val , ',', 1)
2 . to get return all items, specify the key word DEFUALT in the 3rd parameter of the function:
SELECT *
FROM #t t
CROSS APPLY [dbo].[fn_xml_Splitter](t.Val , ',', DEFAULT)

MS Sql order by numeric parts of string

I need to order my query by numeric parts.
I have a lot of rows looks like:
'ABCD.1234.567'
'ABCD.1234-2345'
'ABCD.1234.1213.1'
So, I want firs order by first numeric part, then by second and then by therd.
Like this:
'ABCD.1234.567'
'ABCD.1234.1213.1'
'ABCD.1234-2345'
How can I do this?
UPD: I have tried to use PATINDEX function in order by, but can figure out how to do that for all numbers.
order by s.product, (case when Patindex('% ,.,-%', s.product)=0 then 0
else Cast(SUBSTRING(s.product, Patindex('%[0-9]%', s.product), len(s.product)) as int) end)
You could use a string split function
DECLARE #SampleData AS TABLE
(
Name varchar(100)
)
INSERT INTO #SampleData
VALUES
('ABCD.1234.567'),
('ABCD.1234-2345'),
('ABCD.1234.1213.1')
Your query would be
SELECT *
FROM #SampleData sd
OUTER APPLY
(
SELECT CAST(t.Value AS int) AS Part1
FROM [dbo].[SplitString](replace(sd.Name,'-', '.'),'.') t
WHERE t.Pos = 2
) p1
OUTER APPLY
(
SELECT CAST(t.Value AS int) AS Part2
FROM [dbo].[SplitString](replace(sd.Name,'-', '.'),'.') t
WHERE t.Pos = 3
) p2
OUTER APPLY
(
SELECT CAST(t.Value AS int) AS Part3
FROM [dbo].[SplitString](replace(sd.Name,'-', '.'),'.') t
WHERE t.Pos = 4
) p3
ORDER BY p1.[Part1], p2.[Part2], p3.Part3
And string split function
CREATE FUNCTION [dbo].[SplitString] (#Text varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos = Row_Number() over (Order By (Select null))
,Value = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#Text,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Result:
Name Part1 Part2 Part3
---------------------------------------
ABCD.1234.567 1234 567 NULL
ABCD.1234.1213.1 1234 1213 1
ABCD.1234-2345 1234 2345 NULL
For 4 groups or less, PARSENAME makes a great string split function
DECLARE #foo TABLE (foo varchar(100));
INSERT #foo
VALUES
('ABCD.1234.567'),
('ABCD.1234-2345'),
('ABCD.1234.1213.1')
SELECT
Y.foo
FROM
(
SELECT
Element1 = REVERSE(PARSENAME(X.ProcessedFoo, 1)),
Element2 = REVERSE(PARSENAME(X.ProcessedFoo, 2)),
Element3 = REVERSE(PARSENAME(X.ProcessedFoo, 3)),
Element4 = REVERSE(PARSENAME(X.ProcessedFoo, 4)),
X.foo
FROM
(
SELECT
foo,
ProcessedFoo = REVERSE(REPLACE(foo, '-', '.'))
FROM #foo
) X
) Y
ORDER BY
Y.Element1,
CAST(Y.Element2 AS int),
CAST(Y.Element3 AS int),
CAST(Y.Element4 AS int);

SQL: How to create columns dynamically

I have a table which is created dynamically. So the number of columns is unknown at the time of creation. I want to create copies of each column in the same table with first column holding the first part of comma separated value, second column the second part and so on
For example,
ID Value1 Value2 .... Valuen
1 1;2;3 4;5;6
2 A;B;C D;E;F
I want to get the output like
ID Value1Copy1 Value1Copy2 Value1Copy3 Value2Copy1 Value2Copy2 Value2Copy3 .... ValuenCopy1
1 1 2 3 4 5 6
2 A B C D E F
I am unable to achieve this for variable number of columns
The following will dynamically unpivot your data. You may notice that the only field specified is ID.
The results are dropped into a #Temp table. From there we perform a dynamic pivot
Example
Declare #YourTable table (ID int,Value1 varchar(50),Value2 varchar(50))
Insert Into #YourTable values
( 1, '1;2;3','4;5;6'),
( 2, 'A;B;C','D;E;F')
Select A.ID
,Col = concat(C.Item,'Copy',D.RetSeq)
,Value = D.RetVal
Into #Temp
From #YourTable A --<< Replace with Your actual table
Cross Apply (Select XMLData = cast((Select A.* For XML Raw) as xml ) ) B
Cross Apply (
Select Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('ID','Other2Exclude')
) C
Cross Apply (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(C.Value,';','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) D
Where A.ID is not null -- or any other WHERE statement
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Col) From #Temp Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select *
From #Temp
Pivot (max(Value) For [Col] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns

Split SQL server string that has Decimal points into multiple Columns

I have the following table
Col
=========================
1270.8/847.2/254.16/106.9
And I would like to be split into columns like so:
Col1 Col2 Col3 Col4
============================================
1270.8 847.2 254.16 106.9
I have the code below, but it doesn't take the decimal into consideration.
Declare #Sample Table
(MachineName varchar(max))
Insert into #Sample
values ('1270.8/847.2/254.16');
SELECT
Reverse(ParseName(Replace(Reverse(MachineName), '/', ''), 1)) As [M1]
, Reverse(ParseName(Replace(Reverse(MachineName), '/', ''), 2)) As [M2]
, Reverse(ParseName(Replace(Reverse(MachineName), '/', ''), 3)) As [M3]
FROM #Sample
In SQL Server 2016+ you can use string_split().
In SQL Server pre-2016, using a CSV Splitter table valued function by Jeff Moden and conditional aggregation:
declare #Sample Table (id int not null identity(1,1), MachineName varchar(max));
insert into #Sample values ('1270.8/847.2/254.16'),('1270.8/847.2/254.16/106.9');
select
t.id
, m1 = max(case when s.ItemNumber = 1 then s.Item end)
, m2 = max(case when s.ItemNumber = 2 then s.Item end)
, m3 = max(case when s.ItemNumber = 3 then s.Item end)
, m4 = max(case when s.ItemNumber = 4 then s.Item end)
from #Sample t
cross apply dbo.delimitedsplit8K(MachineName,'/') s
group by id
rextester demo: http://rextester.com/WJVLB77682
returns:
+----+--------+-------+--------+-------+
| id | m1 | m2 | m3 | m4 |
+----+--------+-------+--------+-------+
| 1 | 1270.8 | 847.2 | 254.16 | NULL |
| 2 | 1270.8 | 847.2 | 254.16 | 106.9 |
+----+--------+-------+--------+-------+
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand
Everyone should have a good split/parse function as illustrated by SQLZim (+1), but another option could be as follow:
Declare #YourTable table (ID int,Col varchar(max))
Insert Into #YourTable values
(1,'1270.8/847.2/254.16/106.9')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select Col1 = xDim.value('/x[1]','float')
,Col2 = xDim.value('/x[2]','float')
,Col3 = xDim.value('/x[3]','float')
,Col4 = xDim.value('/x[4]','float')
From (Select Cast('<x>' + replace((Select replace(A.Col,'/','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A
) B
Returns
ID Col1 Col2 Col3 Col4
1 1270.8 847.2 254.16 106.9
EDIT - If 2012+, and just to be super-duper safe
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select Col1 = try_convert(float,xDim.value('/x[1]','varchar(100)'))
,Col2 = try_convert(float,xDim.value('/x[2]','varchar(100)'))
,Col3 = try_convert(float,xDim.value('/x[3]','varchar(100)'))
,Col4 = try_convert(float,xDim.value('/x[4]','varchar(100)'))
From (Select Cast('<x>' + replace((Select replace(A.Col,'/','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A
) B
If you are using SQL Server 2016, you can use String_Split()
;with cte as (
select RowN = row_number() over(order by (SELECT NULL)), * from string_split('1270.8/847.2/254.16/106.9','/')
) select * from cte
pivot (max(value) for RowN in ([1],[2],[3],[4])) p
If you are using less than SQL Server 2016 version then you might require to use custom split functions... Many ways to write custom split function one easier way is to write using xml
CREATE Function dbo.udf_split( #str varchar(max), #delimiter as varchar(5) )
RETURNS #retTable Table
( RowN int,
value varchar(max)
)
AS
BEGIN
DECLARE #xml as xml
SET #xml = cast(('<X>'+replace(#str,#delimiter ,'</X><X>')+'</X>') as xml)
INSERT INTO #retTable
SELECT RowN = Row_Number() over (order by (SELECT NULL)), N.value('.', 'varchar(MAX)') as value FROM #xml.nodes('X') as T(N)
RETURN
END
--Your query
;with cte as (
select * from udf_split('1270.8/847.2/254.16/106.9','/')
) select * from cte
pivot (max(value) for RowN in ([1],[2],[3],[4])) p
But mine is similar to John's solution... Just now only looking at that
If you are using in value in a table then you can use cross apply as below
create table #t (v varchar(50), i int)
insert into #t (v, i) values ('1270.8/847.2/254.16/106.9',1)
,('847.222/254.33/106.44',2)
select * from #t t cross apply string_split(t.v, '/')
create table #t (v varchar(50), i int)
insert into #t (v, i) values ('1270.8/847.2/254.16/106.9',1)
,('847.222/254.33/106.44',2)
--Just to get all the values
select * from #t t cross apply string_split(t.v, '/')
--Inorder to get into same row -pivoting the data
select * from (
select * from #t t cross apply (select RowN=Row_Number() over (Order by (SELECT NULL)), value from string_split(t.v, '/') ) d) src
pivot (max(value) for src.RowN in([1],[2],[3],[4])) p