This question already has answers here:
MySQL: Split comma separated list into multiple rows
(4 answers)
Closed 9 years ago.
I have a column with a variable number of comma seperated values:
somethingA,somethingB,somethingC
somethingElseA, somethingElseB
And I want the result to take each value, and create a row:
somethingA
somethingB
somethingC
somethingElseA
somethingElseB
How can I do this in SQL (MySQL)?
(I've tried googling "implode" and "lateral view", but those don't seem to turn up related questions. All the related SO questions are trying to do much more complicated things)
You can do it with pure SQL like this
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(t.values, ',', n.n), ',', -1) value
FROM table1 t CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(t.values) - LENGTH(REPLACE(t.values, ',', '')))
ORDER BY value
Note: The trick is to leverage tally(numbers) table and a very handy in this case MySQL function SUBSTRING_INDEX(). If you do a lot of such queries (splitting) then you might consider to populate and use a persisted tally table instead of generating it on fly with a subquery like in this example. The subquery in this example generates a sequence of numbers from 1 to 100 effectively allowing you split up to 100 delimited values per row in source table. If you need more or less you can easily adjust it.
Output:
| VALUE |
|----------------|
| somethingA |
| somethingB |
| somethingC |
| somethingElseA |
| somethingElseB |
Here is SQLFiddle demo
This is how the query might look with a persisted tally table
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(t.values, ',', n.n), ',', -1) value
FROM table1 t CROSS JOIN tally n
WHERE n.n <= 1 + (LENGTH(t.values) - LENGTH(REPLACE(t.values, ',', '')))
ORDER BY value
Here is SQLFiddle demo
Related
This question already has answers here:
Search with comma-separated value mysql
(1 answer)
MySql PHP select count of distinct values from comma separated data (tags)
(5 answers)
Closed 4 years ago.
I have a table that stores data like:
userid books
ym0001 dictionary,textbooks,notebooks
ym0002 textbooks,dictionary
I want to count number of times each book occurs. I want my result to be in this format.
books Counts
dictionary 2
notebooks 1
textbooks 2
This is mysql. Please help
The following approach builds a result of 1000 integers, then uses those integers (n) to locate segments within the comma seperated string, and for each segment it creates a new row so that the derived table looks like this:
userid | book
:----- | :---------
ym0001 | dictionary
ym0002 | textbooks
ym0001 | textbooks
ym0002 | dictionary
ym0001 | notebooks
Once that exists it is a simple matter of grouping by book to arrive at the counts.
select
book, count(*) Counts
from (
select
t.userid
, SUBSTRING_INDEX(SUBSTRING_INDEX(t.books, ',', numbers.n), ',', -1) book
from (
select #rownum:=#rownum+1 AS n
from
(
select 0 union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6
union all select 7 union all select 8 union all select 9
) a
cross join (
select 0 union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6
union all select 7 union all select 8 union all select 9
) b
cross join (
select 0 union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6
union all select 7 union all select 8 union all select 9
) c
cross join (select #rownum:=0) r
) numbers
inner join mytable t
on CHAR_LENGTH(t.books)
-CHAR_LENGTH(REPLACE(t.books, ',', '')) >= numbers.n-1
) d
group by
book
order by
book
book | Counts
:--------- | -----:
dictionary | 2
notebooks | 1
textbooks | 2
If you already have a table of numbers, use that instead.
the cross joins of a b and c dynamically produce 1000 rows, if you need more add further cross joins similar to c. i.e. the number of numbers should exceed the maximum length of your comma seperated data
db<>fiddle here
My database is like
Name | IC | Item
--------------------------
lee | xxx | pear,bear
--------------------------
ron | xxx | apple,dog
what should I do to retrieve the 4 values contained in the column "Item" and then separate them?
Do you have only two items separated by comma in Item? Or it may vary?
LE: you can use this SQL split comma separated row
LLE: just played around with that and this what I've done:
create table myTable(name varchar(7), ic varchar(7), item varchar(200));
insert into myTable(name,ic,item) values ('lee','xxx','pear,bear');
insert into myTable(name,ic,item) values ('ron','xxx','apple,dog');
insert into myTable(name,ic,item) values ('a','xxx','gamma');
insert into myTable(name,ic,item) values ('b','xxx','a,b,c,d');
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(t.item, ',', n.n), ',', -1) value
FROM myTable t CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(t.item) - LENGTH(REPLACE(t.item, ',', '')))
ORDER BY value;
In MySQL, given the following table where Tags is a column that contains comma separated strings
Id Name Salary Tags
----------------------------
1 James 5000 Sales, Marketing
2 John 4000 Sales, Finance
3 Sarah 3000 HR, Marketing, Finance
How could we get sum(Salary) for each word/tag in Tags? so the result would look like this?
Tag TotalSalary
------------------------
Sales 9000
Marketing 8000
Finance 7000
HR 3000
Any help would be very appreciated. Thank you!
While I'd highly recommend normalizing your data structure and not store comma delimited lists, here's one handy approach utilizing a "numbers" table:
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(tags, ',', n.n), ',', -1) value, sum(salary)
FROM tags CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(tags) - LENGTH(REPLACE(tags, ',', '')))
GROUP BY SUBSTRING_INDEX(SUBSTRING_INDEX(tags, ',', n.n), ',', -1)
SQL Fiddle Demo
This leverages substring_index and can support up to 100 delimited items - easy to adjust as needed. I've used this approach several times which works quite well -- I first saw it used in this post.
This is, maybe, a stupid question. Not for me: honestly, I'm not so skilled with MySql queries so I'm looking for a little help.
I have a table:
id | type_a | type_b |
__________________________________________________
1 | *color_1*color_2*color_3*| *color_1* |
2 | *color_3* | *color_3*color_2*|
3 | *color_2*color_3* | *color_4* |
4 | *color_1*color_3*color_4*| |
5 | *color_4* | *color_5* |
__________________________________________________
I would like to move "type_b" column content in "type_a" column ignoring duplicate fields (delimited by * and *, ex: color_1. This kind of storage is builded by a Joomla component).
I would like to have this final result:
id | type_a | type_b |
_________________________________________
1 | *color_1*color_2*color_3*| |
2 | *color_3*color_2* | |
3 | *color_2*color_3*color_4*| |
4 | *color_1*color_3*color_4*| |
5 | *color_4*color_5* | |
_________________________________________
What's the best way to accomplish something similar?
Thanks to all!
You could do it with this statement (no, it's not looking nice), assuming the name of your table is example:
UPDATE
example e1
SET
e1.type_a = (
SELECT
CONCAT('*', GROUP_CONCAT(DISTINCT n1.value ORDER BY n1.value SEPARATOR '*'), '*') as type_a
FROM (
SELECT
id,
CASE
WHEN SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(BOTH '*' FROM e.type_a), '*', n.n), '*', -1) = '' THEN NULL
ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(BOTH '*' FROM e.type_a), '*', n.n), '*', -1)
END value
FROM example e CROSS JOIN (
SELECT
a.N + b.N * 10 + 1 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE
n.n <= 1 + (LENGTH(e.type_a) - LENGTH(REPLACE(e.type_a, '*', '')))
UNION
SELECT
id,
CASE
WHEN SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(BOTH '*' FROM e.type_b), '*', n.n), '*', -1) = '' THEN NULL
ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(BOTH '*' FROM e.type_b), '*', n.n), '*', -1)
END value
FROM example e CROSS JOIN (
SELECT
a.N + b.N * 10 + 1 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE
n.n <= 1 + (LENGTH(e.type_b) - LENGTH(REPLACE(e.type_b, '*', '')))
) n1
WHERE
n1.id = e1.id
GROUP BY
id
),
e1.type_b = ''
;
Demo of the SELECT statement
Explanation
Basically I adapted the method of peterm to get the split done. I had to remove the outer * first by TRIM.
To allow the empty string as column value, I've added the CASE construct, to eliminate such values. If your column has NULL values instead, you could substitute the CASE by
SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(BOTH '*' FROM e.type_a), '*', n.n), '*', -1)
and
SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(BOTH '*' FROM e.type_a), '*', n.n), '*', -1)
The UNION (without the ALL keyword) of this construct will give us the list of distinct color values and with GROUP BY id and GROUP_CONCAT we'll get the * separated value list. Last we add a leading and a trailing * to match your requirements.
For the update you've got to modificate the select, so that it returns just one column with one row (with the where clause).
Note
As stated by peterm this will allow up to 100 values in your value list. I don't believe you will need more, but if you will, then you've got to adapt the generating of the numbers up to your needs.
There is a table(Course Interests) which has all the values in one cell. But those values are just ids and I want to join them with another table(Course) so I can know their names.
Course Interests:
MemberID MemberName CoursesInterested
-------------- --------------------- --------------
1 Al 1,4,5,6
2 A2 3,5,6
Course Table:
CourseId Course
-------------- ---------------------
1 MBA
2 Languages
3 English
4 French
5 Fashion
6 IT
Desired Output:
MemberID MemberName CoursesInterested
-------------- --------------------- --------------
1 Al MBA,French,Fashion,IT
2 A2 English,Fashion,IT
I would like to do a SQL query in MySql that can help me to extract the desired output. I know how to do it in the opposite way(join values to one cell), but I've struggling on seek a way to separate the ids and do a cross-join into another table.
I'll appreciate any help from the community. Thanks
Use FIND_IN_SET to search for something in a comma-delimited list.
SELECT i.MemberID, i.MemberName, GROUP_CONCAT(c.Course) AS CoursesInterested
FROM CourseInterests AS i
JOIN Course AS c ON FIND_IN_SET(c.CourseId, i.CoursesInterested)
However, it would be better to create a relation table instead of storing the courses in a single column. This type of join cannot be optimized using an index, so it will be expensive for a large table.
Try this Out:
SELECT MemberID,MemberName,Group_Concat(C.Course) from
(
SELECT MemberID,MemberName,SUBSTRING_INDEX(SUBSTRING_INDEX(t.CoursesInterested, ',', n.n), ',', -1) value
FROM Table1 t CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(t.CoursesInterested) - LENGTH(REPLACE(t.CoursesInterested, ',', '')))
ORDER BY MemberID,value
) T JOIN course C ON T.value = C.CourseId
Group By MemberID,MemberName
Fiddle Demo
Output:
MemberID MemberName CoursesInterested
-------------- --------------------- --------------
1 Al MBA,French,Fashion,IT
2 A2 English,Fashion,IT