Extracting data between two delimiters in SQL Server? - sql

I have a column that contains data in the format of aaa|bbb|ccc and need to extract aaa, bbb & ccc from the data separately.
I tried
SELECT
SUBSTRING(Column1, 0, CHARINDEX('|', Column1)) AS [First],
SUBSTRING(Column1, CHARINDEX('|', Column1) + 1, LEN(Column1)) AS [Second]
FROM
Table1
OUTPUT:
aaa [FIRST],bbb|ccc [Second]
but I need aaa [FIRST],bbb [Second],ccc [Third]

If you have a string that are exactly same format (three times) delimited by | then you can PARSENAME() :
select col1, parsename(cols, 3) fisrt, parsename(cols, 2) second, parsename(cols, 1) third
from table1 t1 cross apply
( values (replace(col1, '|', '.'))
) t2 (cols);

You can try below way where function charindex,SUBSTRING and REVERSE used
with t as (select 'aaa|bbb|ccc' as val)
select t.*,
LEFT(val, charindex('|', val) - 1),
SUBSTRING(val, charindex('|', val)+1, len(val) - CHARINDEX('|', reverse(val)) - charindex('|', val)),
REVERSE(LEFT(reverse(val), charindex('|', reverse(val)) - 1))
from t;
val
aaa|bbb|ccc aaa bbb ccc

You can split the value in the column using a common table expression (CTE) since split function is not available in your version of SQL Server
WITH CTE(userString,startIndex,EndIndex)
AS
(
SELECT Column1,1,CHARINDEX('|',Column1)
FROM Table1
UNION ALL
SELECT Column1, EndIndex+1,CHARINDEX('|',Column1,EndIndex+1)
FROM CTE
WHERE EndIndex !=0
)
SELECT SUBSTRING(userString,
startIndex,
CASE WHEN EndIndex > 0
THEN EndIndex - startIndex ELSE LEN(Column1) END)
as splitVALUES
FROM CTE

I do this all the time and it works well for me:
DECLARE #delimString VARCHAR(255) = 'aaa|bbb|ccc';
DECLARE #xml XML = '<val>' + REPLACE( #delimString, '|', '</val><val>' ) + '</val>'
SELECT
x.f.value( '.', 'VARCHAR(50)' ) AS val
FROM #xml.nodes( '//val' ) x( f );
Returns
+-----+
| val |
+-----+
| aaa |
| bbb |
| ccc |
+-----+
If you're looking for a columnar return and know you will always only have three values to parse, you might be able to get away with something like the below example. You can run it in SSMS.
DECLARE #table TABLE ( [value] VARCHAR(255) );
INSERT INTO #table ( [value] ) VALUES
( 'aaa|bbb|ccc' )
, ( '0A-PRDS|JQLM-1|1967' )
, ( 'J1658|G-1|2003' );
SELECT
[value]
, SUBSTRING( [value], 0, CHARINDEX( '|', [value] ) ) AS Column1
, SUBSTRING(
[value]
, ( CHARINDEX( '|', [value]) + 1 ) -- starting position of column 2.
, CHARINDEX( '|', [value], ( CHARINDEX( '|', [value] ) + 1 ) ) - ( CHARINDEX( '|', [value]) + 1 ) -- length of column two is the number of characters between the two delimiters.
) AS Column2
, SUBSTRING(
[value]
, CHARINDEX( '|', [value], ( CHARINDEX( '|', [value] ) + 1 ) ) + 1
, LEN( [value] )
) AS Column3
FROM #table;
Returns
+---------------------+---------+---------+---------+
| value | Column1 | Column2 | Column3 |
+---------------------+---------+---------+---------+
| aaa|bbb|ccc | aaa | bbb | ccc |
| 0A-PRDS|JQLM-1|1967 | 0A-PRDS | JQLM-1 | 1967 |
| J1658|G-1|2003 | J1658 | G-1 | 2003 |
+---------------------+---------+---------+---------+

Related

'Unpivoting' a SQL table

I'm looking to 'unpivot' a table, though I'm not sure what the best way of going about it, is. Additionally, the values are separated by a ';'. I've listed a sample of what I'm looking at:
​
Column_A
Column_B
Column_C
Column_D
000
A;B;C;D
01;02;03;04
X;Y;D;E
001
A;B
05;06
S;T
002
C
07
S
​
From that, I'm looking for a way to unpivot it, but also to keep the relations it's currently in. As in, the first value in Column_B, C, and D are tied together:
​
|Column_A|Column_B|Column_C|Column_D|
|:-|:-|:-|:-|
|000|A|01|X|
|000|B|02|Y|
|000|C|03|D|
|000|D|04|E|
|001|A|05|S|
And so on.
My initial thought is to use a CTE, which I've set up as:
WITH TEST AS(
SELECT DISTINCT Column_A, Column_B, Column_C, VALUE AS Column_D
from [TABLE]
CROSS APPLY STRING_SPLIT(Column_D, ';'))
SELECT \* FROM TEST
;
Though that doesn't seem to produce the correct results, especially after stacking the CTEs and string splits.
As an update, there were really helpful solutions below. They all ran as expected, however I had one last addition. Is it possible/reasonable to ignore a row/column if it's blank? For example, skipping over Column_C where Column_A is '001'.
|Column_A|Column_B|Column_C|Column_D|
|:-|:-|:-|:-|
|000|A;B;C;D|01;02;03;04|X;Y;D;E|
|001|A;B||S;T|
|002|C|07|S|
Here is a JSON based method. SQL Server 2016 onwards.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ColA varchar(3), ColB varchar(8000), ColC varchar(8000), ColD varchar(8000));
INSERT INTO #tbl VALUES
('000','A;B;C;D','01;02;03;04','X;Y;D;E'),
('001','A;B','05;06','S;T'),
('002','C','07','S');
-- DDL and sample data population, end
WITH rs AS
(
SELECT *
, ar1 = '["' + REPLACE(ColB, ';', '","') + '"]'
, ar2 = '["' + REPLACE(ColC, ';', '","') + '"]'
, ar3 = '["' + REPLACE(ColD, ';', '","') + '"]'
FROM #tbl
)
SELECT ColA, ColB.[value] AS [ColB], ColC.[value] AS ColC, ColD.[value] AS ColD
FROM rs
CROSS APPLY OPENJSON (ar1, N'$') AS ColB
CROSS APPLY OPENJSON (ar2, N'$') AS ColC
CROSS APPLY OPENJSON (ar3, N'$') AS ColD
WHERE ColB.[key] = ColC.[key]
AND ColB.[key] = ColD.[key];
Output
+------+------+------+------+
| ColA | ColB | ColC | ColD |
+------+------+------+------+
| 000 | A | 01 | X |
| 000 | B | 02 | Y |
| 000 | C | 03 | D |
| 000 | D | 04 | E |
| 001 | A | 05 | S |
| 001 | B | 06 | T |
| 002 | C | 07 | S |
+------+------+------+------+
You can use a recursive CTE to walk through the strings. Assuming they are all the same length (i.e. same number of semicolons):
with cte as (
select a, convert(varchar(max), null) as b, convert(varchar(max), null) as c, convert(varchar(max), null) as d,
convert(varchar(max), b + ';') as rest_b, convert(varchar(max), c + ';') as rest_c, convert(varchar(max), d + ';') as rest_d,
0 as lev
from t
union all
select a,
left(rest_b, charindex(';', rest_b) - 1),
left(rest_c, charindex(';', rest_c) - 1),
left(rest_d, charindex(';', rest_d) - 1),
stuff(rest_b, 1, charindex(';', rest_b), ''),
stuff(rest_c, 1, charindex(';', rest_c), ''),
stuff(rest_d, 1, charindex(';', rest_d), ''),
lev + 1
from cte
where rest_b <> ''
)
select a, b, c, d
from cte
where lev > 0
order by a, lev;
Here is a db<>fiddle.
EDIT:
You can ensure filter to use rows that have the same number of semicolons by using:
where (length(b) - length(replace(b, ';', ''))) = (length(c) - length(replace(c, ';', ''))) and
(length(b) - length(replace(b, ';', ''))) = (length(d) - length(replace(d, ';', '')))
You could also extend c and d with a bunch of semicolons so no error occurs and the resulting values are empty strings. Extra semicolons in those columns don't matter, so you could use:
select a, convert(varchar(max), null) as b, convert(varchar(max), null) as c, convert(varchar(max), null) as d,
convert(varchar(max), b + ';') as rest_b, convert(varchar(max), c + replicate(';', length(b))) as rest_c, convert(varchar(max), d + replicate(';', length(b))) as rest_d,
0 as lev
The real problem here is your design. Hopefully the reason you're doing this is to fix your design.
Unfortunately you can't use SQL Server's inbuilt STRING_SPLIT for this, as it doesn't provide an ordinal position. As such I use DelimitedSplit8K_LEAD to separate the values into rows, and then "join" then back up. This does assume that all the columns have the same number of delimited values.
CREATE TABLE dbo.YourTable (ColA varchar(3),
ColB varchar(8000),
ColC varchar(8000),
ColD varchar(8000));
INSERT INTO dbo.YourTable
VALUES('000','A;B;C;D','01;02;03;04','X;Y;D;E'),
('001','A;B','05;06','S;T'),
('002','C','07','S');
GO
SELECT YT.ColA,
DSLB.Item AS ColB,
DSLC.Item AS ColC,
DSLD.Item AS ColD
FROM dbo.YourTable YT
CROSS APPLY dbo.DelimitedSplit8K_LEAD(YT.ColB,';') DSLB
CROSS APPLY dbo.DelimitedSplit8K_LEAD(YT.ColC,';') DSLC
CROSS APPLY dbo.DelimitedSplit8K_LEAD(YT.ColD,';') DSLD
WHERE DSLB.ItemNumber = DSLC.ItemNumber
AND DSLC.ItemNumber = DSLD.ItemNumber;
GO
DROP TABLE dbo.YourTable;

Split comma separated values of a multiple column in row in SQL query

I have data Like this
Code address phno
123 test1,test2,test3 123,456,789
And I want output
Code address phno
123 test1 123
123 test2 456
123 test3 789
My code is this
declare #address VARCHAR(500) = 'test1,test2,test3', #phoneno VARCHAR(500) = '123,456,789'
select *
from STRING_SPLIT(#address, ','), STRING_SPLIT(#phoneno, ',')
but it returns multiple values
You can use recursive CTEs for this:
with cte as (
select code, cast(NULL as varchar(max)) as address, cast(NULL as varchar(max)) as phno,
cast(address + ',' as varchar(max)) as rest_address, cast(phno + ',' as varchar(max)) as rest_phno, 0 as lev
from t
union all
select code, left(rest_address, charindex(',', rest_address) - 1), left(rest_phno, charindex(',', rest_phno) - 1),
stuff(rest_address, 1, charindex(',', rest_address), ''), stuff(rest_phno, 1, charindex(',', rest_phno), ''), lev + 1
from cte
where rest_address <> ''
)
select code, address, phno
from cte
where lev > 0;
Here is a db<>fiddle.
If you can have more than 100 elements in the lists, you'll need option (maxrecursion 0).
Please try the following solution.
SQL
DECLARE #tbl TABLE (Code CHAR(3), address VARCHAR(100), phno VARCHAR(100));
INSERT INTO #tbl (Code, address, phno) VALUES
('123', 'test1,test2,test3', '123,456,789');
;WITH cte1 AS
(
SELECT Code, value AS address
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS seq
FROM #tbl
CROSS APPLY STRING_SPLIT(address, ',')
), cte2 AS
(
SELECT Code, value AS phno
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS seq
FROM #tbl
CROSS APPLY STRING_SPLIT(phno, ',')
)
SELECT cte1.code, cte1.address, cte2.phno
FROM cte1 INNER JOIN cte2
ON cte2.seq = cte1.seq;
SQL #2, JSON based
;WITH rs AS
(
SELECT *
, ar1 = '["' + Replace(address, ',', '","') + '"]'
, ar2 = '["' + Replace(phno, ',', '","') + '"]'
FROM #tbl
)
SELECT Code, address.[value] AS [address], phno.[value] AS phno
FROM rs
CROSS APPLY OPENJSON (ar1, N'$') AS address
CROSS APPLY OPENJSON (ar2, N'$') AS phno
WHERE address.[key] = phno.[key];
Output
+------+---------+------+
| code | address | phno |
+------+---------+------+
| 123 | test1 | 123 |
| 123 | test2 | 456 |
| 123 | test3 | 789 |
+------+---------+------+

SQL Server : get a substring (first name, last name, surname)

I'm looking for a way to get table below:
CREATE TABLE [dbo].[#temp]
(
[ID_TASK] [NVARCHAR](300) NULL,
[CHNAME_NAME] [NVARCHAR](300) NULL,
[CHNAME_PHONE] [NVARCHAR](300) NULL
) ON [PRIMARY]
INSERT INTO [dbo].[#temp]
VALUES ('ID005', 'Anderson Abreu Oliveira', '68157120'),
('ID006', 'Gonzalez-IV', '64106929'),
('ID009', 'Parker W.H.', '60994308')
I'm using this SQL query:
SELECT
ID_TASK, CHNAME_NAME,
RTRIM(LTRIM(SUBSTRING(CHNAME_NAME, 1, CHARINDEX(' ', CHNAME_NAME)))) AS SURNAME,
RTRIM(LTRIM(SUBSTRING(CHNAME_NAME, CHARINDEX(' ', CHNAME_NAME) + 1, LEN(CHNAME_NAME) - (CHARINDEX(' ', CHNAME_NAME) - 1)))) AS FIRSTNAME,
REPLACE((RTRIM(LTRIM(CHNAME_PHONE))), '8-', '') AS CHNAME_PHONE
FROM
[dbo].[#temp]
but I get this result:
How do I fix this SQL query to get the desired result? Thank you
You might try to use case..when structure
with CHARINDEX('-', CHNAME_NAME) and CHARINDEX(' ', CHNAME_NAME)
as in the following statement :
SELECT
ID_TASK, CHNAME_NAME,
RTRIM(LTRIM(SUBSTRING(CHNAME_NAME, 1,
(Case CHARINDEX(' ', CHNAME_NAME) When 0 Then CHARINDEX('-', CHNAME_NAME)-1
Else CHARINDEX(' ', CHNAME_NAME) End)))) AS SURNAME,
RTRIM(LTRIM(SUBSTRING(CHNAME_NAME,
(Case CHARINDEX(' ', CHNAME_NAME) When 0 Then CHARINDEX('-', CHNAME_NAME)
Else CHARINDEX(' ', CHNAME_NAME) End) + 1,
LEN(CHNAME_NAME) - (CHARINDEX(' ', CHNAME_NAME) - 1)))) AS FIRSTNAME,
REPLACE((RTRIM(LTRIM(CHNAME_PHONE))), '8-', '') AS CHNAME_PHONE
FROM
[dbo].[#temp]
This is quite an advanced thing to achieve I've done some SQL to get you there, but not tidied it up...
DECLARE #temp TABLE (
[ID_TASK] [nvarchar](300) NULL,
[CHNAME_NAME] [nvarchar](300) NULL,
[CHNAME_PHONE] [nvarchar](300) NULL
)
INSERT INTO #temp
SELECT 'ID005','Anderson Abreu Oliveira','68157120' UNION ALL
SELECT 'ID006','Gonzalez-IV','64106929' UNION ALL
SELECT 'ID009','Parker W.H.','60994308'
select ID_TASK, col1, col2, col3
from
(
select a.ID_TASK, Item , 'col' + CONVERT(NVARCHAR(10), ItemNumber) ItemNumber
from #temp a
CROSS APPLY [dbo].[DelimitedSplit] ( REPLACE(a.CHNAME_NAME, '-', ' '), ' ' ) b
) d
pivot
(
MAX(item)
for itemNumber in (col1, col2, col3)
) piv
GROUP BY ID_TASK, col1, col2, col3
Helper function:
CREATE FUNCTION [dbo].[DelimitedSplit] (
#pString VARCHAR(MAX),
#pDelimiter CHAR(1)
)
RETURNS TABLE WITH SCHEMABINDING
AS
RETURN
WITH E1(N) AS ( -- 10
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),
E3(N) AS (SELECT 1 FROM E2 a, E2 b, E2 c),
E4(N) AS (SELECT 1 FROM E3 a, E3 b, E3 c, E3 d),
cteTally(N) AS (
SELECT 0 UNION ALL
SELECT TOP ( DATALENGTH( ISNULL( #pString, 1 ) ) ) ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL ) ) FROM E4
),
cteStart(N1) AS (
SELECT t.N + 1
FROM cteTally t
WHERE ( SUBSTRING( #pString, t.N, 1 ) = #pDelimiter OR t.N = 0 )
)
-- ------
SELECT
ItemNumber = ROW_NUMBER() OVER( ORDER BY s.N1 ),
Item = SUBSTRING( #pString, s.N1, ISNULL( NULLIF( CHARINDEX( #pDelimiter, #pString, s.N1 ), 0) - s.N1, 1000000 ) )
FROM cteStart s
GO
Here you go:
CREATE TABLE [dbo].[#temp]
(
[ID_TASK] [NVARCHAR](300) NULL,
[CHNAME_NAME] [NVARCHAR](300) NULL,
[CHNAME_PHONE] [NVARCHAR](300) NULL
) ON [PRIMARY]
INSERT INTO [dbo].[#temp]
VALUES ('ID005', 'Anderson Abreu Oliveira', '68157120'),
('ID006', 'Gonzalez-IV', '64106929'),
('ID009', 'Parker W.H.', '60994308');
WITH CTE AS
(
SELECT [ID_TASK],
LEFT([CHNAME_NAME], IIF( CHARINDEX(' ', [CHNAME_NAME]) = 0 , CHARINDEX('-', [CHNAME_NAME])-1, CHARINDEX(' ', [CHNAME_NAME]))) AS T,
[CHNAME_NAME],
[CHNAME_PHONE]
FROM #Temp
)
SELECT [ID_TASK],
[CHNAME_NAME],
T AS SURNAME,
LTRIM(REPLACE(CASE WHEN (T IS NULL) OR (T = '') THEN [CHNAME_NAME] ELSE SUBSTRING([CHNAME_NAME], LEN(T)+1, LEN([CHNAME_NAME])) END, '-', '')) FirstName,
[CHNAME_PHONE]
FROM CTE;
Results:
+---+---------+-------------------------+-----------+-----------------+--------------+
| | ID_TASK | CHNAME_NAME | SURNAME | FirstName | CHNAME_PHONE |
+---+---------+-------------------------+-----------+-----------------+--------------+
| 1 | ID005 | Anderson Abreu Oliveira | Anderson | Abreu Oliveira | 68157120 |
| 2 | ID006 | Gonzalez-IV | Gonzalez | IV | 64106929 |
| 3 | ID009 | Parker W.H. | Parker | W.H. | 60994308 |
+---+---------+-------------------------+-----------+-----------------+--------------+
Demo

SQL Server split string with delimiter

I have an input string
100|2|3,101|2|1,103|2|3.
I would like to parse and add this in table having 3 columns so it should go f.x
col1 col2 col3
100 2 3
similar other data separated by comma as record and | as column.
Thanks
nik
Or this way:
SELECT
LEFT(value, Charindex('|', value) - 1),
SUBSTRING(value, Charindex('|', value) + 1,Len(value) - Charindex('|', Reverse(value)) - Charindex('|', value)),
RIGHT(value, Charindex('|', Reverse(value)) - 1)
FROM
string_split('100|2|3,101|2|1,103|2|3',',')
Try this way
DECLARE #TAB TABLE(COLUMN1 INT, COLUMN2 INT, COLUMN3 INT)
DECLARE #STRING VARCHAR(MAX)='100|2|3,101|2|1,103|2|3,'
SELECT #STRING = 'SELECT ' + REPLACE( REPLACE (#STRING, ',','
UNION ALL
SELECT '),'|',',')
SELECT #STRING = SUBSTRING(#STRING,1,LEN(#STRING)-18)
INSERT INTO #TAB
EXEC(#STRING)
SELECT * FROM #TAB
And the result will be
+---------+---------+---------+
| COLUMN1 | COLUMN2 | COLUMN3 |
+---------+---------+---------+
| 100 | 2 | 3 |
| 101 | 2 | 1 |
| 103 | 2 | 3 |
+---------+---------+---------+
Declare #temp table(col1 int,col2 int ,col3 int)
Declare #pos int,#str nvarchar(max),#len int
Set #str='100|2|3'
Set #pos=1
Select #len=len(#str)
Insert into #temp
Select substring(#str,#pos,charindex('|',#str,#pos)-1),
substring(#str,charindex('|',#str,#pos)+1,charindex('|',#str,#pos)-3),
substring(#str,charindex('|',#str,charindex('|',#str,#pos)+1)+1,#len)
Select * from #temp
;WITH tb(s)AS(
SELECT '100|2|3' UNION
SELECT '101|2|1' UNION
SELECT '103|2|3'
)
SELECT PARSENAME(REPLACE(s,'|','.'),3)
,PARSENAME(REPLACE(s,'|','.'),2)
,PARSENAME(REPLACE(s,'|','.'),1)
FROM tb
(No column name) (No column name) (No column name)
100 2 3
101 2 1
103 2 3

SQL count ids in fields

I have a table contains IDs in field. It looks like:
FieldName
-------------------------
1,8,2,3,4,10,5,9,6,7
-------------------------
1,8
-------------------------
1,8
I need to count these IDs to get result:
ID | Count
---|------
1 | 3
8 | 3
2 | 1
3 | 1
Any ideas?
Thanks!
Try this :
Declare #demo table(FieldName varchar(100))
insert into #demo values('1,8,2,3,4,10,5,9,6,7')
insert into #demo values('1,8')
insert into #demo values('1,8')
select ID, COUNT(id) ID_count from
(SELECT
CAST(Split.a.value('.', 'VARCHAR(100)') AS INT) AS ID
FROM
(
SELECT CAST ('<M>' + REPLACE(FieldName, ',', '</M><M>') + '</M>' AS XML) AS ID
FROM #demo
) AS A CROSS APPLY ID.nodes ('/M') AS Split(a)) q1
group by ID
I like Devart's answer because of the faster execution. Here is a modified earlier answer to suite your need :
Declare #col varchar(200)
SELECT
#col=(
SELECT FieldName + ','
FROM #demo c
FOR XML PATH('')
);
;with demo as(
select cast(substring(#col,1,charindex(',',#col,1)-1) AS INT) cou,charindex(',',#col,1) pos
union all
select cast(substring(#col,pos+1,charindex(',',#col,pos+1)-pos-1)AS INT) cou,charindex(',',#col,pos+1) pos
from demo where pos<LEN(#col))
select cou ID, COUNT(cou) id_count from demo
group by cou
Try this one -
Query:
SET NOCOUNT ON;
DECLARE #temp TABLE (txt VARCHAR(8000))
INSERT INTO #temp (txt)
VALUES ('1,8,2,3,4,10,5,9,6,7'), ('1,8'), ('1,8')
SELECT
t.ID
, [Count] = COUNT(1)
FROM (
SELECT
ID =
SUBSTRING(
t.string
, number + 1
, ABS(CHARINDEX(',', t.string, number + 1) - number - 1)
)
FROM (
SELECT string = (
SELECT ',' + txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)')
) t
CROSS JOIN [master].dbo.spt_values n
WHERE [type] = 'p'
AND number <= LEN(t.string) - 1
AND SUBSTRING(t.string, number, 1) = ','
) t
GROUP BY t.ID
ORDER BY [Count] DESC
Output:
ID Count
----- -----------
1 3
8 3
9 1
10 1
2 1
3 1
4 1
5 1
6 1
7 1
Query cost: