split comma separated values into distinct rows - sql

I have a table which looks like this:
id fk_det userid
3 9 name1,name2
6 1 name3
9 2 name4,name5
12 3 name6,name7
I have already learned to regret having the values of the userid in comma separated values, so i want to split the rows up and end up with something that looks like
id fk_det userid
3 9 name1
x 9 name2
6 1 name3
9 2 name4
x 2 name5
12 3 name6
x 3 name7
i had been looking at something like this:
select fk_det, det, LEFT(userid, CHARINDEX(',',userid+',')-1),
STUFF(userid, 1, CHARINDEX(',',userid+','), '')
from global_permissions
but i'm not sure how to make it work when userid contains more than 2 items (which it might, some might have none, some might have multiple, just depends)

this is what I tend to use:
IF EXISTS (
SELECT 1
FROM dbo.sysobjects
WHERE id = object_id(N'[dbo].[ParseString]')
AND xtype in (N'FN', N'IF', N'TF'))
BEGIN
DROP FUNCTION [dbo].[ParseString]
END
GO
CREATE FUNCTION dbo.ParseString (#String VARCHAR(8000), #Delimiter VARCHAR(10))
RETURNS TABLE
AS
/*******************************************************************************************************
* dbo.ParseString
*
* Creator: magicmike
* Date: 9/12/2006
*
*
* Outline: A set-based string tokenizer
* Takes a string that is delimited by another string (of one or more characters),
* parses it out into tokens and returns the tokens in table format. Leading
* and trailing spaces in each token are removed, and empty tokens are thrown
* away.
*
*
* Usage examples/test cases:
Single-byte delimiter:
select * from dbo.ParseString2('|HDI|TR|YUM|||', '|')
select * from dbo.ParseString2('HDI| || TR |YUM', '|')
select * from dbo.ParseString2(' HDI| || S P A C E S |YUM | ', '|')
select * from dbo.ParseString2('HDI|||TR|YUM', '|')
select * from dbo.ParseString2('', '|')
select * from dbo.ParseString2('YUM', '|')
select * from dbo.ParseString2('||||', '|')
select * from dbo.ParseString2('HDI TR YUM', ' ')
select * from dbo.ParseString2(' HDI| || S P A C E S |YUM | ', ' ') order by Ident
select * from dbo.ParseString2(' HDI| || S P A C E S |YUM | ', ' ') order by StringValue
Multi-byte delimiter:
select * from dbo.ParseString2('HDI and TR', 'and')
select * from dbo.ParseString2('Pebbles and Bamm Bamm', 'and')
select * from dbo.ParseString2('Pebbles and sandbars', 'and')
select * from dbo.ParseString2('Pebbles and sandbars', ' and ')
select * from dbo.ParseString2('Pebbles and sand', 'and')
select * from dbo.ParseString2('Pebbles and sand', ' and ')
*
*
* Notes:
1. A delimiter is optional. If a blank delimiter is given, each byte is returned in it's own row (including spaces).
select * from dbo.ParseString3('|HDI|TR|YUM|||', '')
2. In order to maintain compatibility with SQL 2000, ident is not sequential but can still be used in an order clause
If you are running on SQL2005 or later
SELECT Ident, StringValue FROM
with
SELECT Ident = ROW_NUMBER() OVER (ORDER BY ident), StringValue FROM
*
*
* Modifications
*
*
********************************************************************************************************/
RETURN (
SELECT Ident, StringValue FROM
(
SELECT Num as Ident,
CASE
WHEN DATALENGTH(#delimiter) = 0 or #delimiter IS NULL
THEN LTRIM(SUBSTRING(#string, num, 1)) --replace this line with '' if you prefer it to return nothing when no delimiter is supplied. Remove LTRIM if you want to return spaces when no delimiter is supplied
ELSE
LTRIM(RTRIM(SUBSTRING(#String,
CASE
WHEN (Num = 1 AND SUBSTRING(#String,num ,DATALENGTH(#delimiter)) <> #delimiter) THEN 1
ELSE Num + DATALENGTH(#delimiter)
END,
CASE CHARINDEX(#Delimiter, #String, Num + DATALENGTH(#delimiter))
WHEN 0 THEN LEN(#String) - Num + DATALENGTH(#delimiter)
ELSE CHARINDEX(#Delimiter, #String, Num + DATALENGTH(#delimiter)) - Num -
CASE
WHEN Num > 1 OR (Num = 1 AND SUBSTRING(#String,num ,DATALENGTH(#delimiter)) = #delimiter)
THEN DATALENGTH(#delimiter)
ELSE 0
END
END
)))
End AS StringValue
FROM dbo.Numbers
WHERE Num <= LEN(#String)
AND (
SUBSTRING(#String, Num, DATALENGTH(ISNULL(#delimiter,''))) = #Delimiter
OR Num = 1
OR DATALENGTH(ISNULL(#delimiter,'')) = 0
)
) R WHERE StringValue <> ''
)
You would use it like this:
SELECT id, pk_det, V.StringValue as userid
FROM myTable T
OUTER APPLY dbo.ParseString(T.userId) V
The UDF requires a 'tally' or Number table which assumes the following schema:
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Numbers')
BEGIN
CREATE TABLE dbo.Numbers
(
Num INT NOT NULL
CONSTRAINT [PKC__Numbers__Num] PRIMARY KEY CLUSTERED (Num) on [PRIMARY]
)
;WITH Nbrs_3( n ) AS ( SELECT 1 UNION SELECT 0 ),
Nbrs_2( n ) AS ( SELECT 1 FROM Nbrs_3 n1 CROSS JOIN Nbrs_3 n2 ),
Nbrs_1( n ) AS ( SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2 ),
Nbrs_0( n ) AS ( SELECT 1 FROM Nbrs_1 n1 CROSS JOIN Nbrs_1 n2 ),
Nbrs ( n ) AS ( SELECT 1 FROM Nbrs_0 n1 CROSS JOIN Nbrs_0 n2 )
INSERT INTO dbo.Numbers(Num)
SELECT n
FROM ( SELECT ROW_NUMBER() OVER (ORDER BY n)
FROM Nbrs ) D ( n )
WHERE n <= 50000 ;
END
Numbers tables are an invaluable addition to your toolset. To quote Adam Machanic:
Numbers tables are truly invaluable. I use them all of the time for
string manipulation, simulating window functions, populating test
tables with lots of data, eliminating cursor logic, and many other
tasks that would be incredibly difficult without them.
Is using a table of numbers a hack, as I've seen some people claim?
No. Show me another way to efficiently do all of the things a numbers
table can. Does it waste space? No. The script below will use up
around 900 KB of disk space in each database. That's absolutely
nothing. You'll end up getting millions, maybe billions of times the
disk space investment back in terms of ease of development and time
saved.
http://dataeducation.com/you-require-a-numbers-table/

Try this :)
DECLARE #Name TABLE
(
id INT NULL ,
fk_det INT NULL ,
userid NVARCHAR(100) NULL
)
INSERT INTO #Name
( id, fk_det, userid)
VALUES (3,9,'name1,name2' )
INSERT INTO #Name
( id, fk_det, userid)
VALUES (6,1,'name3' )
INSERT INTO #Name
( id, fk_det, userid)
VALUES (9,2,'name4,name5' )
INSERT INTO #Name
( id, fk_det, userid)
VALUES (12,3,'name6,name7' )
SELECT *
FROM #Name
SELECT id,A.fk_det,
Split.a.value('.', 'VARCHAR(100)') AS String
FROM (SELECT id,fk_det,
CAST ('<M>' + REPLACE(userid, ',', '</M><M>') + '</M>' AS XML) AS String
FROM #Name) AS A CROSS APPLY String.nodes ('/M') AS Split(a);

As an alternative to the standard sproc call you see everywhere:
with temp as(
select id,fk_det,cast('<comma>'+replace(userid,',','</comma><comma>')+'</comma>' as XMLcomma
from global_permissions
)
select id,fk_det,a.value('comma[1]','varchar(512)')
cross apply temp.XMLcomma.nodes('/comma') t(a)

Related

SQL split string (all possible combination)

I would like to transform this string:
A1+A2+A3.B1+B2.C1
into
A1.B1.C1
A1.B2.C1
A2.B1.C1
A2.B2.C1
A3.B1.C1
A3.B2.C1
How can I do that? (note that each dimension(= a group separate by .), could have x values, I mean it can be A1+A2.B1.C1 or A1+A2.B1+B2+B3+B4+B5.C1+C2)
Thanks
If you have only 3 columns, then just use STRING_SPLIT: number your groups from first split and then do a join 3 times and select each group on corresponding join.
with a as (
select s2.value as v, dense_rank() over(order by s1.value) as rn
from STRING_SPLIT('A1+A2+A3.B1+B2.C1', '.') as s1
cross apply STRING_SPLIT(s1.value, '+') as s2
)
select
a1.v + '.' + a2.v + '.' + a3.v as val
from a as a1
cross join a as a2
cross join a as a3
where a1.rn = 1
and a2.rn = 2
and a3.rn = 3
| val |
----------
|A1.B1.C1|
|A2.B1.C1|
|A3.B1.C1|
|A1.B2.C1|
|A2.B2.C1|
|A3.B2.C1|
If you have indefinite number of groups, then it's better to use recursive CTE instead of dynamic SQL. What you should do:
Start with all the values from the first group.
On recursion step crossjoin all the values of the next group (i.e. step group number is current group number + 1).
Select the last recursion step where you'll have the result.
Code is below:
with a as (
select s2.value as v, dense_rank() over(order by s1.value) as rn
from STRING_SPLIT('A1+A2+A3.B1+B2+B3+B4.C1+C2.D1+D2+D3', '.') as s1
cross apply STRING_SPLIT(s1.value, '+') as s2
)
, b (val, lvl) as (
/*Recursion base*/
select cast(v as nvarchar(1000)) as val, rn as lvl
from a
where rn = 1
union all
/*Increase concatenation on each iteration*/
select cast(concat(b.val, '.', a.v) as nvarchar(1000)) as val, b.lvl + 1 as lvl
from b
join a
on b.lvl + 1 = a.rn /*Recursion step*/
)
select *
from b
where lvl = (select max(rn) from a) /*You need the last step*/
order by val
I won't add a tabular result since it is quite big. But try it by yourself.
Here is SQL server version and fiddle:
with lst(s) as (select * from STRING_SPLIT('A1+A2.B1+B2+B3+B4+B5.C1+C2','.'))
select t1+'.'+t2+'.'+t3 as res from
(select * from STRING_SPLIT((select s from lst where s like 'A%'), '+')) s1(t1) cross join
(select * from STRING_SPLIT((select s from lst where s like 'B%'), '+')) s2(t2) cross join
(select * from STRING_SPLIT((select s from lst where s like 'C%'), '+')) s3(t3);
Of course you can grow it in a regular fashion if the number of dimensions grows.
Here is a Postgresql solution:
with x(s) as (select string_to_array('A1+A2.B1+B2+B3+B4+B5.C1+C2','.'))
select t1||'.'||t2||'.'||t3 as res from
unnest((select string_to_array(s[1],'+') from x)) t1 cross join
unnest((select string_to_array(s[2],'+') from x)) t2 cross join
unnest((select string_to_array(s[3],'+') from x)) t3;
result:
res |
--------|
A1.B1.C1|
A1.B2.C1|
A1.B3.C1|
A1.B4.C1|
A1.B5.C1|
A2.B1.C1|
A2.B2.C1|
A2.B3.C1|
A2.B4.C1|
A2.B5.C1|
A1.B1.C2|
A1.B2.C2|
A1.B3.C2|
A1.B4.C2|
A1.B5.C2|
A2.B1.C2|
A2.B2.C2|
A2.B3.C2|
A2.B4.C2|
A2.B5.C2|
Here my code with your help. I didn't mention, but I can also have more or less than 3 parts, so I'm using a dynamic SQL for this:
declare #FILTER varchar(max)='B+C+D.A+G.T+Y+R.E'
-- Works also with A.B.C
-- Works also with A+B+C.D.E+F
-- Works also with A+B+C.D+E+F+G+H
declare #NB int
declare #SQL varchar(max)=''
select #NB=count(*) from STRING_SPLIT(#FILTER,'.')
set #SQL='
;with T(A,B) as
(select *, row_number() over (order by (select NULL))
from STRING_SPLIT(''' + #FILTER + ''',''.'')
)
select '
;with T(V,N) as (
select *, row_number() over (order by (select NULL))
from STRING_SPLIT(#FILTER,'.')
)
select #SQL=#SQL + 'T' + cast(N as varchar(max)) + ' + ''.'' + ' from T
set #SQL=left(#SQL,len(#SQL)-1) + ' as res from'
;with T(V,N) as (
select *, row_number() over (order by (select NULL))
from STRING_SPLIT(#FILTER,'.')
)
select #SQL=#SQL + '
(select * from STRING_SPLIT((select A from T where B=' + cast(N as varchar(max)) + '), ''+'')) s' + cast(N as varchar(max)) + '(t' + cast(N as varchar(max)) + ') cross join'
from T
set #SQL=left(#SQL,len(#SQL)-len('cross join'))
exec(#SQL)

How can I retrieve first second and third word of a String in SQL?

I need a query which would extract the first second and third word of a string.
I have approximately 5 words in each row and I need only the first three words out of 5 in the same row (1 row). Example "ATV BDSG 232 continue with other words".
I need only the first three words together in one row (in the same row) like "ATV BDSG 232" as a first row. The table has about 1000 rows and at the end of it I should have 1000 rows again but each row should contain only the first three words of the string.
I found a query which works fine for extracting first two like "ATV BDSG" discussed in stack overflow. The query is
"SELECT SUBSTRING(field1, 0, CHARINDEX(' ', field1, CHARINDEX(' ', field1, 0)+1))
FROM Table"
Can we derive this for extracting first three words?
Thanks in advance
If you don't want to create a dedicated function, you can use successive CROSS APPLYs:
SELECT
T.s,
FirstSpace.i,
SecondSpace.j,
ThirdSpace.k,
CASE
When ThirdSpace.k > 0 THEN LEFT(T.s, Thirdspace.k - 1)
ELSE T.S
END AS Phrase
FROM t
CROSS APPLY (SELECT CHARINDEX(' ', T.s, 1)) AS FirstSpace(i)
CROSS APPLY (SELECT CHARINDEX(' ', T.S, FirstSpace.i + 1)) AS SecondSpace(j)
CROSS APPLY (SELECT CHARINDEX(' ', T.s, SecondSpace.j + 1)) AS ThirdSpace(k)
gives you the results you need:
| s | i | j | k | phrase |
|----------------------------------------|---|---|----|------------------|
| ATV BDSG 232 Continue with other words | 4 | 9 | 13 | ATV BDSG 232 |
Things are easy, SQL Server provide STRING_SPLIT() function make that too easy
DECLARE #Var VARCHAR(100) = 'ATV BDSG 232 Continue with other words';
SELECT Word
FROM
(
SELECT Value AS Word,
ROW_NUMBER()OVER(ORDER BY (SELECT NULL)) RN
FROM STRING_SPLIT(#Var, ' ')
) T
WHERE RN <= 3;
But since you are working on 2012 version, you need to define your own function.
You can also take the hard way, first you need to get the first word, then replace it with '' and get the second word, then do the same for the 3rd word as
DECLARE #Var VARCHAR(100) = 'ATV BDSG 232 Continue with other words';
WITH FW AS
(
SELECT LEFT(#Var, CHARINDEX(' ', #Var)) FirstWord
),
SW AS
(
SELECT LEFT(REPLACE(#Var, FirstWord, ''),
CHARINDEX(' ', REPLACE(#Var, FirstWord, ''))) SecondWord
FROM FW
)
SELECT FirstWord,
SecondWord,
LEFT(REPLACE(REPLACE(V, FirstWord, ''), SecondWord, ''),
CHARINDEX(' ', REPLACE(REPLACE(V, FirstWord, ''), SecondWord, ''))
) ThirdWord
FROM
(
SELECT *, #Var V
FROM FW CROSS APPLY SW
) T
Demo
UPDATE
If you want to select the three first words then simply
SELECT SUBSTRING(Str, 0, CHARINDEX(' ', Str, CHARINDEX(' ', Str, CHARINDEX(' ', Str, 0)+1)+1)) Words
FROM Strings
Demo
--make some test data
declare #test as nvarchar(100) = 'my test string for words';
select 1 id, cast('my test string for words' as nvarchar(max)) word into #test;
insert #test (id,word) values (2,'a b c d e f g hhh yyyyyy') ;
insert #test (id,word) values (3,' a required test string d e f g hhh yyyyyy') ;
insert #test (id,word) values (4,'a quick test') ;
insert #test (id,word) values (5,'a test') ;
insert #test (id,word) values (6,'last') ;
--break up letters, count the first 3 words
;WITH CTE AS (SELECT 1 x, substring(#test,1,1) charx
UNION ALL
SELECT X + 1, substring(#test,x + 1,1) from CTE WHERE x < len(#test)
)
select * from cte c3 where (SELECT count(0) cnt FROM CTE c1 JOIN CTE c2 on c1.x <= c3.x and c1.x + 1 = c2.x and c1.charx =' ' and c2.charx != ' ') < 3
;WITH tabx as (select id, cast(ltrim(word) as nvarchar(max)) 'word' from #test), --do some ltrim
CTE AS (
SELECT id, 1 x, substring(word,1,1) charx from tabx
UNION ALL
SELECT t.id, c.X + 1, substring(t.word,x + 1,1)
from tabx t
JOIN CTE c on c.id = t.id and x < len(t.word)
),
disj as
(select * from cte c3 where
(SELECT count(0) cnt
FROM CTE c1
JOIN CTE c2 on c1.id = c3.id and c1.id = c2.id and c1.x <= c3.x and c1.x + 1 = c2.x and c1.charx =' ' and c2.charx != ' '
) < 3
),
rj as
(select disj.id,disj.x, disj.charx z
from disj
where disj.x = 1
UNION ALL
select d.id, d.x, r.z + d.charx
FROM rj r
join disj d on r.id = d.id and r.x + 1 = d.x
)
select *
from rj r1
cross apply (select max(r2.x) TheRow from rj r2 where r1.id = r2.id) dq
where r1.x = dq.TheRow
order by r1.id;
--delete test data
drop table #test
/* This is not perfect - but interesting */
declare #t table (fullname varchar(100))
insert #t values('Mr Jones'),('Mrs Amy smith'),('Jim Smith'),('Dr Harry Web '),('Paul Fred andrew jones')
select fullname,
a.value as a ,
b.Value as b,
c.Value as c,
d.Value as d,
e.Value as e,
f.value as f
from #t
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ')) a
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ') where value not in (a.value )) b
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ') where value not in (a.value,b.value ) ) c
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ') where value not in (a.value,b.value,c.value )) d
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ') where value not in (a.value,b.value,c.value,d.value) ) e
outer apply (select top 1 value from STRING_SPLIT(fullname, ' ') where value not in (a.value,b.value ,c.value,d.value,e.value) ) f
To Select First Word -
Select top 1 Ltrim(Rtrim(value)) FROM STRING_SPLIT(#input,' ')
To Select Only Second Word -
Select Ltrim(Rtrim(value)) from STRING_SPLIT(#input,' ') Order by (Select NULL) OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY

Split string and divide value

I got a table Test with columns A and B.
The A column contains different values in one entry, e.g. abc;def;ghi, all separated by ;. And the B column contains numeric values, but only one.
What I want is to seperate the values from column A into multiple rows.
So:
abc;def;ghi;jkl
-->
abc
def
ghi
jkl
In column B is one value, e.g. 20 and I want that value split to the amount of rows,
So the final result shut be:
abc 5
def 5
ghi 5
jkl 5
The issue is that the amount of values in column A must be variable.
First you need to create this function
REATE 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),
origVal nvarchar(max)
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#delimited,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val,origval)
select
r.value('.','varchar(max)') as item, #delimited
from #xml.nodes('//root/r') as records(r)
RETURN
END
GO
then this query might help
Select x.Val, test.B / (len(test.A) - len(replace(Test.A, ';', '')) + 1) from Test
inner join dbo.Split(Test.A,';') x on x.origVal = Test.A
this part len(test.A) - len(replace(Test.A, ';', '')) will count the number of ; in string
Be aware this query might have some malfunctioning if there will be duplicate strings in A column, in this situation you need to pass the unique value (for example ID) to split function and return it in the result table, then join it by this value (ie. x.origVal = Test.A => x.origID = Test.ID)
You can use some tricks with CTE, STUFF and windows functions
DECLARE #t TABLE
(
ID INT ,
A NVARCHAR(MAX) ,
B INT
)
INSERT INTO #t
VALUES ( 1, 'a;b;c;d;', 20 ),
( 2, 'x;y;z;', 40 );
WITH cte ( ID, B, D, A )
AS ( SELECT ID ,
B ,
LEFT(A, CHARINDEX(';', A + ';') - 1) ,
STUFF(A, 1, CHARINDEX(';', A + ';'), '')
FROM #t
UNION ALL
SELECT ID ,
B ,
LEFT(A, CHARINDEX(';', A + ';') - 1) ,
STUFF(A, 1, CHARINDEX(';', A + ';'), '')
FROM cte
WHERE A > ''
)
SELECT ID ,
B ,
D,
CAST(B AS DECIMAL) / COUNT(*) OVER (PARTITION BY ID) AS Portion
FROM cte
Output:
ID B D Portion
1 20 a 5.00000000000
1 20 b 5.00000000000
1 20 c 5.00000000000
1 20 d 5.00000000000
2 40 x 13.33333333333
2 40 y 13.33333333333
2 40 z 13.33333333333
this an example how you can achieve required result
DECLARE #table AS TABLE
(
ColumnA VARCHAR(100) ,
ColumnB FLOAT
)
INSERT INTO #table
( ColumnA, ColumnB )
VALUES ( 'abc;def;ghi;jkl', 20 ),
( 'asf;ret;gsd;jas', 30 ),
( 'dfa;aef;gffhi;fjfkl', 40 );
WITH C AS ( SELECT n = 1
UNION ALL
SELECT n + 1
FROM C
WHERE n <= 100
),
SetForSplit
AS ( SELECT T.ColumnA ,
T.ColumnB ,
C.n ,
( CASE WHEN LEFT(SUBSTRING(T.ColumnA, n, 100), 1) = ';'
THEN SUBSTRING(T.ColumnA, n + 1, 100) + ';'
ELSE SUBSTRING(T.ColumnA, n, 100) + ';'
END ) AS SomeText
FROM #table AS T
JOIN C ON C.n <= LEN(T.ColumnA)
WHERE SUBSTRING(T.ColumnA, n, 1) = ';'
OR n = 1
)
SELECT ROW_NUMBER() OVER ( PARTITION BY columnA ORDER BY LEFT(SomeText,
CHARINDEX(';',
SomeText) - 1) ) AS RowN,
LEFT(SomeText, CHARINDEX(';', SomeText) - 1) AS ColA ,
ColumnB / COUNT(*) OVER ( PARTITION BY ColumnA ) AS ColB
FROM SetForSplit
ORDER BY ColumnA
This is full working exmaple:
DECLARE #DataSource TABLE
(
[A] VARCHAR(MAX)
,[B] INT
);
INSERT INTO #DataSource ([A], [B])
VALUES ('a;b;c;d', 20 ),
('x;y;z', 40 );
SELECT T.c.value('.', 'VARCHAR(100)')
,[B] / COUNT([B]) OVER (PARTITION BY [B])
FROM #DataSource
CROSS APPLY
(
SELECT CONVERT(XML, '<t>' + REPLACE([A], ';', '</t><t>') + '</t>')
) DS([Bxml])
CROSS APPLY [Bxml].nodes('/t') AS T(c)
and of couse you can ROUND the devision as you like.

Return Distinct Rows That Contain The Same Value/Character In SQL

I have a bit of a tricky situation. I have a column that contains a pipe delimited set of numbers in numerous rows in a table. For example:
Courses
-------------------
1|2
1|2|3
1|2|8
10
11
11|12
What I want to achieve is to return rows where the number only appears once in my output.
Ideally, I want to try and carry this out using SQL rather than having to carry out checks at a web application level. Carrying out a DISTINCT does not achieve what I want.
The desired output would be:
Courses
-------------------
1
2
3
8
10
11
12
I would appreciated if anyone can guide me in the right direction.
Thanks.
Please try:
declare #tbl as table(Courses nvarchar(max))
insert into #tbl values
('1|2'),
('1|2|3'),
('1|2|8'),
('10'),
('11'),
('11|12')
select * from #tbl
SELECT
DISTINCT CAST(Split.a.value('.', 'VARCHAR(100)') AS INT) AS CVS
FROM
(
SELECT CAST ('<M>' + REPLACE(Courses, '|', '</M><M>') + '</M>' AS XML) AS CVS
FROM #tbl
) AS A CROSS APPLY CVS.nodes ('/M') AS Split(a)
ORDER BY 1
Try this one -
SET NOCOUNT ON;
DECLARE #temp TABLE
(
string VARCHAR(500)
)
DECLARE #Separator CHAR(1)
SELECT #Separator = '|'
INSERT INTO #temp (string)
VALUES
('1|2'),
('1|2|3'),
('1|2|8'),
('10'),
('11'),
('11|12')
-- 1. XML
SELECT p.value('(./s)[1]', 'VARCHAR(500)')
FROM (
SELECT field = CAST('<r><s>' + REPLACE(t.string, #Separator, '</s></r><r><s>') + '</s></r>' AS XML)
FROM #temp t
) d
CROSS APPLY field.nodes('/r') t(p)
-- 2. CTE
;WITH a AS
(
SELECT
start_pos = 1
, end_pos = CHARINDEX(#Separator, t.string)
, t.string
FROM #temp t
UNION ALL
SELECT
end_pos + 1
, CHARINDEX(#Separator, string, end_pos + 1)
, string
FROM a
WHERE end_pos > 0
)
SELECT d.name
FROM (
SELECT
name = SUBSTRING(
string
, start_pos
, ABS(end_pos - start_pos)
)
FROM a
) d
WHERE d.name != ''
Try this :
create table course (courses varchar(100))
insert into course values('1|2')
insert into course values('1|2|3')
insert into course values('1|2|8')
insert into course values('10')
insert into course values('11')
insert into course values('11|12')
Declare #col varchar(200)
SELECT
#col=(
SELECT DISTINCT c.courses + '|'
FROM course c
FOR XML PATH('')
);
select * from course
;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 distinct cou from demo
Could not manage without recursion :( Something like this could do the trich?
WITH splitNum(num, r)
AS
(
SELECT
SUBSTRING(<field>,1, CHARINDEX('|', <field>)-1) num,
SUBSTRING(<field>,CHARINDEX('|', <field>)+1, len(<field>)) r
FROM <yourtable> as a
UNION ALL
SELECT
SUBSTRING(r,1, CHARINDEX('|', r)-1) num,
SUBSTRING(r,CHARINDEX('|', r)+1, len(r)) r
FROM <yourtable> b
WHERE CHARINDEX('|', r) > 0
inner join splitNum as c on <whatevertheprimarykeyis>
)
SELECT distinct num FROM splitNum
Didn't make it run, but it should do the trick, just replace the and with the correct info
One way would be to use a recursive CTE:
with cte as
(select cast(case charindex('|',courses) when 0 then courses
else left(courses,charindex('|',courses)-1) end as int) course,
case charindex('|',courses) when 0 then ''
else right(courses,len(courses)-charindex('|',courses)) end courses
from courses
union all
select cast(case charindex('|',courses) when 0 then courses
else left(courses,charindex('|',courses)-1) end as int) course,
case charindex('|',courses) when 0 then ''
else right(courses,len(courses)-charindex('|',courses)) end courses
from cte
where len(courses)>0)
select distinct course from cte
SQLFiddle here.

Single text value which get unique value from all rows

Assume I have these rows:
ROW 1 apple,watermelon,pineapple
ROW 2 apple,pineapple,orange
ROW 3 apple,blue berry
I want to create single text value which get unique value from all rows.
How do we do it with query?
Expected Result:
apple,watermelon,pineapple,orange,blue berry
Try this:
select array_agg(val) from (
select distinct unnest(string_to_array(my_column, ',')) val from my_table) x
A breakdown of what's going on:
string_to_array() splits the string, using the specified delimiter, into a true array
unnest() turns an array into separate rows - one for each element
distinct removes duplicate rows
array_agg() joins all rows into a single CSV string (typically you would us a group by clause, but no need here as there's only one group)
You can use Following function to Split the row values by ','
CREATE FUNCTION [dbo].[ConvertToTable]
(
#delimiter char(1),
#string nvarchar(MAX)
)
RETURNS #Values TABLE ( VALUE NVARCHAR(MAX) )
AS BEGIN
SET #string = #string + #delimiter ;
WITH Nbrs_3 ( n ) AS ( SELECT 1 UNION SELECT 0 ) ,
Nbrs_2 ( n ) AS ( SELECT 1 FROM Nbrs_3 n1 CROSS JOIN Nbrs_3 n2 ) ,
Nbrs_1 ( n ) AS ( SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2 ) ,
Nbrs_0 ( n ) AS ( SELECT 1 FROM Nbrs_1 n1 CROSS JOIN Nbrs_1 n2 ) ,
Nbrs ( n ) AS ( SELECT 1 FROM Nbrs_0 n1 CROSS JOIN Nbrs_0 n2 )
INSERT #Values ( [VALUE] )
SELECT SUBSTRING(#string, n + 1, CHARINDEX(#delimiter, #string, n + 1) - n - 1)
FROM ( SELECT 0 AS 'n' UNION ALL
SELECT TOP ( LEN(#string) - 1 ) ROW_NUMBER() OVER ( ORDER BY n ) AS 'n' FROM Nbrs
) x
WHERE SUBSTRING(#string, n, 1) = #delimiter
OR n = 0
RETURN
END
And Use following Code to Get the result..
DECLARE #unique_value NVARCHAR(MAX)
WITH cte AS (
SELECT 1 AS id, 'apple,watermelon,pineaple' AS String
UNION
SELECT 2 ,'apple,pineaple,orange'
UNION
SELECT 3 ,'apple,blue berry'
)
SELECT #unique_value= COALESCE(#unique_value+',','')+[VALUE] FROM [cte]
CROSS APPLY [dbo].[ConvertToTable](',',[String]) AS CTT
GROUP BY [VALUE]
SELECT #unique_value
UPDATE: I didn't notice this is about postgresql. i have gave the answer for MSSQL, If you could do the same in postgresql. this method would help you..
try this:
SELECT ARRAY(SELECT distinct regexp_split_to_table(myTable.columns, E',') AS split_columns FROM myTable);