Split string and select into new table - sql

I have a table with a structure like this
ID pointCount pointSeries
1 282 35.1079,-111.0151,35.1088,-111.0196...
Obviously the point series is string has pair of lat lon as points. I want to select the pointSeries into a new table
ID Lat Lon
1 35.1079 -111.0151
1 35.1088 -111.0196
What's the best way I can do a split and select into query?

You need to have a function for splitting comma-delimited strings into separate rows. Here is the DelimitedSplit8K function by Jeff Moden.
CREATE FUNCTION [dbo].[DelimitedSplit8K](
#pString NVARCHAR(4000), #pDelimiter NCHAR(1)
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
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
)
,E2(N) AS (SELECT 1 FROM E1 a, E1 b)
,E4(N) AS (SELECT 1 FROM E2 a, E2 b)
,cteTally(N) AS(
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
,cteStart(N1) AS(
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(
SELECT
s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
Then you need to pivot the result of the split to achieve the desired result:
;WITH CteSplitted AS(
SELECT
t.ID,
x.ItemNumber,
Item = CAST(x.Item AS NUMERIC(16,4)),
RN = (ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ItemNumber) + 1) / 2
FROM Test t
CROSS APPLY dbo.DelimitedSplit8K(t.PointSeries, ',') x
)
SELECT
ID,
Lat = MAX(CASE WHEN ItemNumber % 2 = 1 THEN Item END),
Lon = MAX(CASE WHEN ItemNumber % 2 = 0 THEN Item END)
FROM CteSplitted
GROUP BY ID, RN

Related

Split column value to a new row

I have a SQL Server table like this:
id
description
items
123
Women clothing
T-shirt & Bottom & Top
124
sports items
badminton racket
125
gadgets
iphone & airpod & charging
I want to split the column items value to a new row for each one item like this:
id
description
items
123
Women clothing
T-shirt
123
Women clothing
Bottom
123
Women clothing
Top
124
sports items
badminton racket
125
gadgets
iphone
125
gadgets
airpod
125
gadgets
charging
To create function check this screenshot. select your db and execute following:
If you dont have string_split func. (below sqlserver2016) you can use this function , its working in our production enviroment for years :
USE xxx
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[StringSplit]
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
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
),
E2(N) AS (SELECT 1 FROM E1 a, E1 b),
E4(N) AS (SELECT 1 FROM E2 a, E2 b),
cteTally(N) AS (
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
and query :
select * from
(SELECT 123 id, 'T-shirt & Bottom & Top' T UNION ALL
SELECT 124 , 'badminton racket'UNION ALL
SELECT 125 , 'iphone & airpod & charging' ) x
CROSS APPLY dbo.StringSplit(x.T, '&')
WITH CTE(id ,description,items) AS
(
SELECT 123, 'Women clothing' , 'T-shirt & Bottom & Top' UNION ALL
SELECT 124 ,'sports items' ,'badminton racket'UNION ALL
SELECT 125 ,'gadgets' ,'iphone & airpod & charging'
)
SELECT C.ID, C.DESCRIPTION, TRIM(S.VALUE)
FROM CTE AS C
CROSS APPLY STRING_SPLIT(C.items, '&') S;
you can use following SQL server code easily:
select Tbl.RowID,
Tbl.ColMain,
[value] ExtractedValue from
(
SELECT 123 as RowID, 'T-shirt & Bottom & Top' As ColMain UNION ALL
SELECT 124 , 'badminton racket' UNION ALL
SELECT 125 , 'iphone & airpod & charging'
) Tbl
CROSS APPLY String_Split(Tbl.ColMain, '&')

Migrate a Column into Multiple Columns

I have a table with the following columns 'ID, LIST_OF_VALUES'.
Example data is:
ID| LIST_OF_VALUES
--+----------------------------
1 | firstval-secondval-thirdval
2 | val1-val2
3 | val10-val20-val30
4 | singleval
I would like to select the data like this:
ID| VAL1 | VAL2 | VAL3
--+----------+-----------+-------
1 | firstval | secondval | thirdval
2 | val1 | val2 | NULL
3 | val10 | val20 | val30
4 | singlval | NULL | NULL
I am aware of the STRING_SPLIT function. I have tried using it in various ways with Cross Apply, but I can't seem to get the result I want.
I know I can do this using a mess of SUBSTR/INDEX, but I am just curious if STRING_SPLIT offers a more elegant solution.
Just another option
Example of XML Option
Select A.ID
,Val1 = tmpXML.value('/x[1]','varchar(100)')
,Val2 = tmpXML.value('/x[2]','varchar(100)')
,Val3 = tmpXML.value('/x[3]','varchar(100)')
from YourTable A
Cross Apply ( values ( Cast('<x>' + replace([LIST_OF_VALUES],'-','</x><x>')+'</x>' as xml) ) ) B(tmpXML)
Returns
ID Val1 Val2 Val3
1 firstval secondval thirdval
2 val1 val2 NULL
3 val10 val20 val30
4 singleval NULL NULL
Example of JSON Option - as suggested by #PanagiotisKanavos if 2016+
Select A.ID
,Val1 = JSON_VALUE(S,'$[0]')
,Val2 = JSON_VALUE(S,'$[1]')
,Val3 = JSON_VALUE(S,'$[2]')
from #YourTable A
Cross Apply ( values ( '["'+replace(replace([LIST_OF_VALUES],'"','\"'),'-','","')+'"]' ) ) B(S)
Assuming you don't have duplicates, you can use it . . . but it is not trivial:
select t.*, s.*
from t cross apply
(select max(case when seqnum = 1 then value end) as val1,
max(case when seqnum = 2 then value end) as val2,
max(case when seqnum = 3 then value end) as val3
from (select s.value,
row_number() over (order by charindex('-' + value + '-', '-' + t.list_of_values + '-') as seqnum
from string_split(t.list_of_values, '-') s
) s
) s;
Unfortunately, string_split() doesn't provide the ordering. This recreates it using charindex().
One simple and very efficient and scalable way would be to use an ordinal splitter such as dbo.DelimitedSplit8K (or dbo.DelimitedSplitN4K (for nchar/nvarchar). Then the query would be something like this
dbo.DelimitedSplit8K tvf
CREATE FUNCTION dbo.DelimitedSplit8K
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
-- enough to cover VARCHAR(8000)
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;
Query
select t.id,
max(case when ds.ItemNumber=1 then ds.Item end) as val1,
max(case when ds.ItemNumber=2 then ds.Item end) as val2,
max(case when ds.ItemNumber=3 then ds.Item end) as val3
from tTable t
cross apply
dbo.DelimitedSplit8K(t.LIST_OF_VALUES, '-') ds
group by t.id
order by t.id;
[EDIT] Here is an alternate method which produces the same output but does not use the DelimitedSplit8K function. This is the same approach as Gordon's but with an outer GROUP BY clause.
;with charindex_split_cte(id, Item, ItemNumber) as (
select t.id, sp.value,
row_number() over (order by charindex('-' + sp.value + '-', '-' + t.list_of_values + '-'))
from tTable t
cross apply string_split(t.list_of_values, '-') sp)
select id,
max(case when ItemNumber=1 then Item end) as val1,
max(case when ItemNumber=2 then Item end) as val2,
max(case when ItemNumber=3 then Item end) as val3
from charindex_split_cte
group by id
order by id;

Recursive cte to repeat several integers

I'd like a column of numbers:
Seven occurances of the integer 1, followed by 7 occurances of 2, followed by 7 occurances of 3 .... , followed by 7 occurances of n-1, followed by 7 occurances of n. Like so
Num
1
1
1
1
1
1
1
2
2
2
2
2
2
2
...
...
n-1
n-1
n-1
n-1
n-1
n-1
n-1
n
n
n
n
n
n
n
Unfortunately I've not progressed too far. My current attempt is the following, where n=4:
WITH
one AS
(
SELECT num = 1,
cnt = 0
UNION ALL
SELECT num = num,
cnt = cnt + 1
FROM one
WHERE cnt < 7
),
x AS
(
SELECT num,
cnt = 0
FROM one
UNION ALL
SELECT num = num + 1,
cnt = cnt + 1
FROM one
WHERE cnt < 4
)
SELECT *
FROM x
;WITH Numbers AS
(
SELECT n = 1
UNION ALL
SELECT n + 1
FROM Numbers
WHERE n+1 <= 10
),
se7en AS
(
SELECT n = 1
UNION ALL
SELECT n + 1
FROM se7en
WHERE n+1 <= 7
)
SELECT Numbers.n
FROM Numbers CROSS JOIN se7en
with x as
(select 1 as id
union all
select 2 as id
union all
select 3 as id
union all
select 4 as id
union all
select 5 as id
union all
select 6 as id
union all
select 7 as id)
select x1.* from x cross join x x1
The cross join will work in your case.
No need to use recursive CTE for this you can try set based approach solution try something like this. Kind of integer division.
If you have access to master database then use this.
;with cte as
(
SELECT top 1000 [7_seq] = ( ( Row_number()OVER(ORDER BY (SELECT NULL)) - 1 ) / 7 ) + 1
FROM sys.columns
)
select * from cte where [7_seq] <= #n
or use tally table to generate the numbers. I will prefer this solution
DECLARE #n INT = 10;
WITH Tally (num)
AS (
-- 1000 rows
SELECT Row_number()OVER (ORDER BY (SELECT NULL))
FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)),
seq
AS (SELECT [7_seq] = ( ( Row_number()
OVER(
ORDER BY (SELECT num)) - 1 ) / 7 ) + 1
FROM Tally)
SELECT [7_seq]
FROM seq
WHERE [7_seq] <= #n
WITH t1 AS (SELECT 0 as num UNION ALL SELECT 0)
,t2 AS (SELECT 0 as num FROM t1 as a CROSS JOIN t1 as b)
,t3 AS (SELECT 0 as num FROM t2 as a CROSS JOIN t2 as b)
,t4 AS (SELECT 0 as num FROM t3 as a CROSS JOIN t3 as b)
,Tally (number)
AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) FROM t4)
SELECT t1.number
FROM Tally as t1 cross join Tally as t2
where t2.number <=7
ORDER BY t1.number;
You can do It in following:
DECLARE #num INT = 1,
#sub INT = 0,
#max INT = 10,
#timesToRepeat INT = 7
CREATE TABLE #Temp (num INT)
WHILE (#num < #max + 1)
BEGIN
SET #sub = 0;
WHILE (#sub < #timesToRepeat)
BEGIN
INSERT INTO #Temp
SELECT #num x
SET #sub = #sub +1
END
SET #num = #num +1
END
SELECT * FROM #Temp
DROP TABLE #Temp
Set #max variable to number what you want to reach for now It is 10 so It will return result set like:
1
1
1
1
1
1
1
2
2
2
2
2
2
2
.
.
.
10
10
10
10
10
10
10
DECLARE #MAX INTEGER
SET #MAX = 5;
with cte as
(SELECT 7 as num
UNION ALL
SELECT num-1 as num from cte where num>1
),cte2 AS
(SELECT #MAX as num
UNION ALL
SELECT num-1 as num from cte2 where num>1)
select C2.num from cte C1,cte2 C2 ORDER by C2.num asc
Change the value of #MAX to reflect the value of n
Here is a slightly different way to do it.
select null num into #a
union all
select null
union all
select null
union all
select null
union all
select null
union all
select null
union all
select null
select * into #b from
(select rn = row_number()over (order by (select null)) from sys.objects A cross join sys.objects B) A
where rn <=10
select #b.rn as numbers from #a cross join #b
order by 1

How to display duplicate items based on the quantity in SQL?

I have a table with the field content :
item qty
----- -----
tea 2
I want to display as below
item qty
---- ------
tea 1
tea 1
How to create SQL query like above ?
SAMPLE TABLE
CREATE TABLE #TEMP(ITEM VARCHAR(10),QTY INT)
INSERT INTO #TEMP
SELECT 'TEA',2
UNION ALL
SELECT 'COFFEE',3
If you have more than 1 type of item and you want to get the number of items, you can do with the following query.
QUERY
;WITH CTE AS
(
-- You will get each ITEM from your table
SELECT ITEM, QTY, 1 NEWQTY
FROM #TEMP
UNION ALL
-- Here it will loop and shows the repeated values of each ITEM
SELECT C1.ITEM,C1.QTY,NEWQTY + 1
FROM CTE C1
JOIN #TEMP T1 ON C1.item= T1.ITEM
WHERE C1.NEWQTY < T1.qty
)
SELECT ITEM,
1 QTY
FROM CTE
ORDER BY ITEM
Click here to view result
Use Recursive CTE
;WITH cte
AS (SELECT item,qty,1 num from yourtable
UNION ALL
SELECT item,
qty,
num + 1
FROM cte
WHERE num < qty)
SELECT item,
1 as Qty
FROM cte
or use Tally table. It will have a better performance when compared to Recursive CTE
;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
), -- 10
e2(n)
AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b) -- 10*10
--e3(n) AS (SELECT 1 FROM e2 CROSS JOIN e2 AS b), -- 100*100
SELECT a.item,
1 AS qty
FROM yourtable a
JOIN (SELECT n = Row_number()OVER (ORDER BY n)
FROM e2) b
ON b.n <= a.qty;
Note : Based on the Quantity you may have to increase the cross join of CTE's
Check here for more info

How to find the nth Term in tsql

I have a table as under
Term
0
8
24
48
80
X
The desired output being
Term1 Term2 Diff
0 8 8
8 24 16
24 48 24
48 80 32
80 x 40
I have the below script
Declare #t Table(Term varchar(10))
Insert Into #t
Select '0' Union All
Select '8' Union All
Select '24' Union All
Select '48' Union All
Select '80' Union All
Select 'X'
So far I have tried as
;With Cte1 As
(
Select rn = ROW_NUMBER() Over(Order By (select 1)),* From #t
)
,cte2 as(
Select
Term1 = (Select term from Cte1 where rn=1)
,Term2 = (Select term from Cte1 where rn=2)
,Diff = Cast((Select term from Cte1 where rn=2) as int) - Cast((Select term from Cte1 where rn=1) as int)
)
Select * from cte2
I donot know what to do in the recursive part of cte2..
Help needed
I'm unsure why the difference between 80 and x is supposed to be 40, but you can handle that by tweaking the CTE to return what you need for the last row.
;WITH Cte1 AS
(
SELECT rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)), * FROM #t
)
SELECT
Term1 = Cte1.Term,
Term2 = (SELECT Term FROM Cte1 AS a_CTE where a_CTE.rn = Cte1.rn + 1),
Diff = CAST((SELECT CASE Term WHEN 'X' THEN 120 ELSE Term END
FROM Cte1 AS a_CTE
WHERE a_CTE.rn = Cte1.rn + 1) AS int)
- CAST(Cte1.Term AS int)
FROM Cte1
WHERE ISNUMERIC(Cte1.Term) = 1
I recommend using PIVOT and not a recursive CTE. Cf.: http://msdn.microsoft.com/en-us/library/ms177410.aspx