How to get multiple comma-separated values as individual columns? - sql

I have a table with data like this:
select * from data
id | col1 | col2 | col3
---+-------+-------+-------
1 | 1,2,3 | 4,5,6 | 7,8,9
I want to get the data like this:
id | name | dd | fn | suf
---+------+----+----+-----
1 | col1 | 1 | 2 | 3
1 | col2 | 4 | 5 | 6
1 | col3 | 7 | 8 | 9
Currently, I use split_part() in a query like this:
SELECT * from(
select id,
'col1' as name,
NULLIF(split_part(col1, ',', 1), '') AS dd,
NULLIF(split_part(col1, ',', 2), '') AS fn,
NULLIF(split_part(col1, ',', 3), '') AS suf
from data
UNION
select id,
'col2' as name,
NULLIF(split_part(col2, ',', 1), '') AS dd,
NULLIF(split_part(col2, ',', 2), '') AS fn,
NULLIF(split_part(col2, ',', 3), '') AS suf
from data
UNION
select id,
'col3' as name,
NULLIF(split_part(col3, ',', 1), '') AS dd,
NULLIF(split_part(col3, ',', 2), '') AS fn,
NULLIF(split_part(col3, ',', 3), '') AS suf
from data
);
Is there a more elegant way? I have 20 columns.

Assuming this table:
CREATE TABLE tbl (id int, col1 text, col2 text, col3 text);
INSERT INTO tbl VALUES (1 ,'1,2,3', '4,5,6', '7,8,9');
A VALUES expression in a LATERAL subquery should be an elegant solution.
Then just use split_part(). Add NULLIF() only if there can be actual empty strings in the source ...
SELECT id, x.name
, split_part(x.col, ',', 1) AS dd
, split_part(x.col, ',', 2) AS fn
, split_part(x.col, ',', 3) AS suf
FROM tbl t, LATERAL (
VALUES (text 'col1', t.col1)
, ( 'col2', t.col2)
, ( 'col3', t.col3)
-- ... many more?
) x(name, col);
Works in PostgreSQL 9.3 or later.
SQL Fiddle.
Related:
What is the difference between LATERAL and a subquery in PostgreSQL?
SELECT DISTINCT on multiple columns
Split comma separated column data into additional columns

I would do the union all first and the split_part() second:
select id, name,
coalesce(split_part(col, ',', 1), '') as dd,
coalesce(split_part(col, ',', 2), '') as fn,
coalesce(split_part(col, ',', 3), '') as suf
from ((select id, 'col1' as name, col1 as col from data
) union all
(select id, 'col2' as name, col2 as col from data
) union all
(select id, 'col3' as name, col3 as col from data
)
) t;

Related

Concatenate or merge many columns values with a separator between and ignoring nulls - SQL Server 2016 or older

I want to simulate the CONCAT_WS SQL Server 2017+ function with SQL Server 2016 version or older in order to concatenate many columns which values are strings like that:
Input:
| COLUMN1 | COLUMN2 | COLUMN3 | COLUMN4 |
'A' 'B' NULL 'D'
NULL 'E' 'F' 'G'
NULL NULL NULL NULL
Output:
| MERGE |
'A|B|D'
'E|F|G'
NULL
Notice that the output result is a new column that concatenate all values separated by '|'. The default value should be NULL if there are no values in the columns.
I tried with CONCAT and a CASE statement with many WHEN conditions but is really dirty and I am not allowed to use this solution. Thanks in advance.
One convenient way is:
select stuff( coalesce(',' + column1, '') +
coalesce(',' + column2, '') +
coalesce(',' + column3, '') +
coalesce(',' + column4, ''), 1, 1, ''
)
Here is another method by using XML and XQuery.
The number of columns is not hard-coded, it could be dynamic.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (id INT IDENTITY PRIMARY KEY, col1 CHAR(1), col2 CHAR(1), col3 CHAR(1), col4 CHAR(1));
INSERT INTO #tbl (col1, col2, col3, col4) VALUES
( 'A', 'B', NULL, 'D'),
(NULL, 'E' , 'F' , 'G'),
(NULL, NULL, NULL , NULL);
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = '|';
SELECT id, REPLACE((
SELECT *
FROM #tbl AS c
WHERE c.id = p.id
FOR XML PATH('r'), TYPE, ROOT('root')
).query('data(/root/r/*[local-name() ne "id"])').value('.', 'VARCHAR(100)') , SPACE(1), #separator) AS concatColumns
FROM #tbl AS p;
Output
+----+---------------+
| id | concatColumns |
+----+---------------+
| 1 | A|B|D |
| 2 | E|F|G |
| 3 | |
+----+---------------+

SQL Subquery with delimiter

I need to be able to split one string by the delimiter * into separate columns without including *
The column y from table x looks like this:
column y
*1HS*AB*GXX*123*02*PA45*2013-08-10*
*1R1*B*GX*123*02*PA45*2013-08-10*
*1HS*B*GX*13*01*PA45*2013-08-01*
*1P*C*GXX*123*02*PA45*2013-08-10*
STRING_SPLIT is not avalible
The outcome should be this:
Column1 Column2 Column3 Column4 Column5 Column6 Column7
1HS AB GXX 123 2 PA45 10-08-2013
1R1 B GX 123 2 PA45 10-08-2013
1HS B GX 13 1 PA45 01-08-2013
1P C GXX 123 2 PA45 10-08-2013
will you use the below query..
select RTRIM (REGEXP_SUBSTR (column y, '[^,]*,', 1, 1), ',') AS column 1
, RTRIM (REGEXP_SUBSTR (column y, '[^,]*,', 1, 2), ',') AS column 2
, RTRIM (REGEXP_SUBSTR (column y, '[^,]*,', 1, 3), ',') AS column 3
, LTRIM (REGEXP_SUBSTR (column y, ',[^,]*', 1, 3), ',') AS column 4
from YOUR_TABLE
Unfortunately, string_split() does not guarantee that it preserves the ordering of the values. And, SQL Server does not offer other useful string functions.
So, I recommend using recursive CTEs for this purpose:
with t as (
select *
from (values ('*1HS*AB*GXX*123*02*PA45*2013-08-10*'), ('1HSB*GX*13*01*PA45*2013-08-01*')) v(str)
),
cte as (
select convert(varchar(max), null) as val, 0 as lev, convert(varchar(max), str) as rest,
row_number() over (order by (select null)) as id
from t
union all
select left(rest, charindex('*', rest) - 1), lev + 1, stuff(rest, 1, charindex('*', rest) + 1, ''), id
from cte
where rest <> '' and lev < 10
)
select max(case when lev = 1 then val end) as col1,
max(case when lev = 2 then val end) as col2,
max(case when lev = 3 then val end) as col3,
max(case when lev = 4 then val end) as col4,
max(case when lev = 5 then val end) as col5,
max(case when lev = 6 then val end) as col6,
max(case when lev = 7 then val end) as col7
from cte
where lev > 0
group by cte.id;
Here is a db<>fiddle.
Assuming you can add a table valued function to your database then Jeff Moden's string split function is the best approach I've encountered. It will allow you to maintain order as well.
Find details here

Incremental concatenation of columns per row in SQL

I am trying to write an Oracle or MS SQL script which outputs the first row containing the cell value in column A, second row contains the cell value of column A concatenated with column B and separated by a comma,
third row contains the cell values of column A, B and C concatenated and separated by a comma.
Suppose the following SQL Table:
|columnA |columnB|columnC |columnD |columnF |columnG |
|--------|-------|--------|--------|--------|--------|
| matty | lucy | james | mike | tala | mark |
| jana | steph | alex | mohd | hani | elie |
The output would be:
matty
matty,lucy
matty,lucy,james
matty,lucy,james,mike
matty,lucy,james,mike,tala
matty,lucy,james,mike,tala,mark
jana
jana,steph
jana,steph,alex
jana,steph,alex,mohd
jana,steph,alex,mohd,hani
jana,steph,alex,mohd,hani,elie
How should I write the SQL select statement?
You can use apply:
select tt.*
from table t cross apply
( values (columnA, null, null, null, null, null),
(columnA, columnB, null, null, null, null),
. . .
(columnA, columnB, columnC, columnD, columnF, columnG)
) tt(col1, col2, col3, col4, col5, col6);
If you want to combine all the data into single column then use concat() :
select tt.*
from table t cross apply
( values (columnA),
(concat(columnA, ',', columnB)),
(concat(columnA, ',', columnB, ',', columnC)),
(concat(columnA, ',', columnB, ',', columnC, ',', columnD)),
(concat(columnA, ',', columnB, ',', columnC, ',', columnD, ',', columnF)),
(concat(columnA, ',', columnB, ',', columnC, ',', columnD, ',', columnF, ',', columnG))
) tt(cols);
One way is to unpivot data and make recursive concatenation (Oracle solution):
--data
with t(a, b, c, d, e, f) as (
select 'matty', 'lucy', 'james', 'mike', 'tala', 'mark' from dual union all
select 'jana ', 'steph', 'alex', 'mohd', 'hani', 'elie' from dual )
-- end of data
select ltrim(sys_connect_by_path(name, ','), ',') path
from (select rownum r1, a, b, c, d, e, f from t)
unpivot (name for r2 in (a as 1, b as 2, c as 3, d as 4, e as 5, f as 6))
connect by prior r1 = r1 and r2 = prior r2 + 1
start with r2 = 1
demo
If you want a version that works in both databases:
select colA
from t
union all
select concat(Col1, concat(',', colB))
from t
union all
select concat(concat(Col1, concat(',', colB)), concat(',', colC))
from t
union all
. . .

Extracting data between two delimiters in SQL Server?

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 |
+---------------------+---------+---------+---------+

How do I pivot in big query

Say I have data
id,col1,col2,col3,col4,col5
1,a,b,c,d,e
and I want the result to be ...
1,a
1,b
1,c
1,d
1,e
How do I pivot on id in big query ?
Below is for BigQuery Standard SQL
#standardSQL
CREATE TEMP FUNCTION cols_to_rows(root STRING) AS (
ARRAY(SELECT REPLACE(SPLIT(kv, ':') [OFFSET(1)], '"', '') cols
FROM UNNEST(SPLIT(REGEXP_REPLACE(root, r'^{|}$', ''))) kv
WHERE SPLIT(kv, ':') [OFFSET(0)] != '"id"'
)
);
SELECT id, col
FROM `project.dataset.table` t,
UNNEST(cols_to_rows(TO_JSON_STRING(t))) col
You can test / play with above using dummy data as below
#standardSQL
CREATE TEMP FUNCTION cols_to_rows(root STRING) AS (
ARRAY(SELECT REPLACE(SPLIT(kv, ':') [OFFSET(1)], '"', '') cols
FROM UNNEST(SPLIT(REGEXP_REPLACE(root, r'^{|}$', ''))) kv
WHERE SPLIT(kv, ':') [OFFSET(0)] != '"id"'
)
);
WITH `project.dataset.table` AS (
SELECT 1 id, 'a' col1, 'b' col2, 'c' col3, 'd' col4, 'e' col5 UNION ALL
SELECT 2 id, 'x', 'y', 'z', 'v', 'w'
)
SELECT id, col
FROM `project.dataset.table` t,
UNNEST(cols_to_rows(TO_JSON_STRING(t))) col
with result as
id col
1 a
1 b
1 c
1 d
1 e
2 x
2 y
2 z
2 v
2 w