How can I join multiple rows in just one single row through mysql?
Example :
Student Table
Sno.| Name | Subjects
1. | ABC | English
2. | ABC | Mathematics
3. | ABC | Science
4. | FMC | French
5. | ABC | Russian
6. | JBC | French
Now I want it in this format
Sno.| Name | Sub1 | Sub2 | Sub3 | Sub4 |
1. | ABC | Eng | Maths| Science| Russian
2. | FMC | French| Null| Null | Null
3. | JBC | French| Null | Null | Null
I am not sure how to actually do it? And shall I create a view or a table?
I guess a view will be fine.
I agree with the other answers, that GROUP_CONCAT along with PHP to split the comma separated values is probably the best approach, however if for any other reason you needed the output you suggested via Pure SQL I would suggest one of the following appoaches.
1. Self Joins
SELECT t1.Name,
MIN(t1.Subject) AS Sub1,
MIN(t2.Subject) AS Sub2,
MIN(t3.Subject) AS Sub3,
MIN(t4.Subject) AS Sub4
FROM Students t1
LEFT JOIN Students T2
ON t1.Name = t2.Name
AND t2.Subject > t1.Subject
LEFT JOIN Students T3
ON t2.Name = t3.Name
AND t3.Subject > t2.Subject
LEFT JOIN Students T4
ON t3.Name = t4.Name
AND t4.Subject > t3.Subject
GROUP BY t1.Name;
2. Using a ROW_NUMBER Type function to aggregate
SELECT Name,
MAX(IF(RowNum = 1,Subject, NULL)) AS Sub1,
MAX(IF(RowNum = 2,Subject, NULL)) AS Sub2,
MAX(IF(RowNum = 3,Subject, NULL)) AS Sub3,
MAX(IF(RowNum = 4,Subject, NULL)) AS Sub4
FROM ( SELECT Name,
Subject,
#r:= IF(#Name = Name, #r + 1, 1) AS RowNum,
#Name:= Name AS Name2
FROM Students,
(SELECT #Name:='') n,
(SELECT #r:= 0) r
ORDER BY Name, Sno
) t
GROUP BY Name
Using below query, get the Name and his/ her subjects.
SELECT Name, GROUP_CONCAT(Subjects) AS List
FROM myTable
GROUP BY Name
Then in PHP, you can use implode function for printing subjects.
Hope this helps.
Demo at sqlfiddle
Try using GROUP BY and GROUP_CONCAT:
SELECT Name, GROUP_CONCAT(Subjects) AS Subjects_list
FROM students_table
GROUP BY Name
Then use PHP function explode while fetching records to get the different values stored in Subjects_list column.
It ain't pretty!
To use the above solutions, you could do:
SELECT
Sno,
Name,
NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(all_subjects, ',', 1), ',', -1), '') AS Sub1,
NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(all_subjects, ',', 2), ',', -1), '') AS Sub2,
NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(all_subjects, ',', 3), ',', -1), '') AS Sub3,
NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(all_subjects, ',', 4), ',', -1), '') AS Sub4
FROM (
SELECT Sno, Name, CONCAT(GROUP_CONCAT(Subjects ORDER BY Sno),',,,') AS all_subjects FROM table GROUP BY name
) inner_sel
;
Your requests leads to the assumption there can be never more than 4 subjects. You also require NULLs where there is no mention of four subjects.
There kind of requests lead to a forced SQL code, one which is typically what SQL is intended for.
To explain the above: we use GROUP_CONCAT, then break it again to pieces. Since there could be fewer than 4 elements, we pad with commas (',,,'). We then break each piece according to its place within the concatenated string, and return with NULL if it's empty.
Can't say this is one of my better answers in terms of prettiness. I hope you find it useful.
Related
We have requirement in Mysql query, to find with partial string from list of comma separated string. Then need to remove a found a string from comma separated list.
As per the below example, we need to find a string starting with "Pending" then need to remove found string from the comma separated list through Mysql query.
|-------------------|-----------------------------------------|
| Id | Tag |
|-------------------|-----------------------------------------|
| 1 | Completed, #4CHD, Pending with ABC |
|-------------------|-----------------------------------------|
| 2 | Open, Pending with Mrg, #4CHD |
|-------------------|-----------------------------------------|
| 3 | Pending with cons, Resolved |
|-------------------|-----------------------------------------|
Output should be:
|-------------------|-----------------------|
| Id | Tag |
|-------------------|-----------------------|
| 1 | Completed, #4CHD |
|-------------------|-----------------------|
| 2 | Open, #4CHD |
|-------------------|-----------------------|
| 3 | Resolved |
|-------------------|-----------------------|
For MySQL 8+
SELECT test.id, GROUP_CONCAT(TRIM(jsontable.value)) Tag
FROM test
CROSS JOIN JSON_TABLE(CONCAT('["', REPLACE(test.Tag, ',', '","'), '"]'),
'$[*]' COLUMNS( value VARCHAR(255) PATH '$')
) jsontable
WHERE TRIM(jsontable.value) NOT LIKE CONCAT(#criteria, '%')
GROUP BY test.id
For MySQL 5.x
SELECT test.id,
GROUP_CONCAT(TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(test.Tag, ',', nums.num), ',', -1))) Tag
FROM test
JOIN (SELECT 1 num UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) nums
ON nums.num <= 1 + LENGTH(test.Tag) - LENGTH(REPLACE(test.Tag, ',', ''))
WHERE TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(test.Tag, ',', nums.num), ',', -1)) NOT LIKE CONCAT(#criteria, '%')
GROUP BY test.id
fiddle
No rows should be ingnored in output. Let us assume 3rd row has Tag value as "Pending with cons" alone. I this case first two is getting displayed in output and 3rd row is ignored. My requirement is 3rd also should be displayed with empty Tag.
If so LEFT JOIN (and moving condition expression from WHERE to ON) needed:
SELECT test.id,
GROUP_CONCAT(TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(test.Tag, ',', nums.num), ',', -1))) Tag
FROM test
LEFT JOIN (SELECT 1 num UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) nums
ON nums.num <= 1 + LENGTH(test.Tag) - LENGTH(REPLACE(test.Tag, ',', ''))
AND TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(test.Tag, ',', nums.num), ',', -1)) NOT LIKE CONCAT(#criteria, '%')
GROUP BY test.id
fiddle
SELECT DISTINCT * FROM ( SELECT hostname, table2.user-id, table2.user-team from
INNER JOIN table2 on table1.id = table2.id)
So at the moment my SQL query outputs this:
hostname user-id user-team
a 1 alpha
a 2 beta
b 3 beta
c 4 alpha
c 1 null
c 3 alpha
but what I want is something like this:
hostname user-id user-team
a 1, 2 alpha, beta
b 3 beta
c 4, 1, 3 alpha
I'm trying to use a GROUP BY hostname statement at the end of my query, and a GROUP_CONCAT(DISTINCT user_id SEPARATOR ', '), GROUP_CONCAT(DISTINCT user_team SEPARATOR ', '),
But this then only returns the first hostname result and all the values possible for the user-id and team-id. I feel like I'm close, but I can't quite get it. Any help?
(At present it returns)
hostname user-id user-team
a 1,2,3,4 alpha, beta
with this as the SQL query
SELECT DISTINCT *
FROM ( SELECT hostname,
GROUP_CONCAT(DISTINCT table2.user-id SEPARATOR(', '),
GROUP_CONCAT(DISTINCT table2.team-id SEPARATOR(', ')
from table1
INNER JOIN table2 on table1.id = table2.id)
GROUP BY hostname
Although the queries arean't 100% accurate, that is the logic in them (they just contain far more columns in the real world problem I have.)
I think you miss the GROUP BY
SQL DEMO
SELECT hostname,
GROUP_CONCAT(DISTINCT user_id SEPARATOR ', ') as users,
GROUP_CONCAT(DISTINCT user_team SEPARATOR ', ') as teams
FROM Table1
GROUP BY hostname
OUTPUT
| hostname | users | teams |
|----------|---------|-------------|
| a | 1, 2 | alpha, beta |
| b | 3 | beta |
| c | 4, 1, 3 | alpha |
EDIT: After you edit your question, the problem was you put the GROUP BY outside of the subquery
I have an SQL table setup similar to this:
name | subject |
-------+----------+
Harry | Painting |
Sandra | Soccer |
Sandra | English |
How can I write a select statement that merges the rows if they have multiple subject, so it would output a result like this:
name | subject 1 | subject 2 |
-------+------------+------------+
Harry | Painting | |
Sandra | Soccer | English |
You shouldn't. The best approach is to join the tables so you have:
Harry, Painting
Sandra, Soccer
Sandra, English
And then process these rows in your scripting language (PHP etc) to turn it into the hierarchical data structure you desire
In your example above, what would happen when there's 3 subjects per person, 10, 100, etc.
SQL only really works with two dimensional data. For three dimensions you either need to pre/post process as i've suggested, or move to something like NoSQL mongoDB that deals with structured objects instead of table rows.
Since you mentioned that the maximum number of subjects is only 2, you can therefore, generate a sequential number for each name and used that to pivot the columns.
SELECT Name,
MAX(CASE WHEN rn = 1 THEN Subject END) Subject1,
MAX(CASE WHEN rn = 2 THEN Subject END) Subject2
FROM
(
SELECT A.name,
A.subject,
(
SELECT COUNT(*)
FROM tableName c
WHERE c.name = a.name AND
c.subject <= a.subject) AS rn
FROM TableName a
) aa
GROUP BY Name
SQLFiddle Demo
The above is an SQL way.
You need a PIVOT routine. Serach for this in the engine of your choice.
Some RDBMSs have this built in. There is an alternative using a CASE statement in the SELECT clause, for which there are many blog posts out there.
You can accomplish this in one statement using a combination of substring_index() and group_concat() like this (SQLfiddle)
SELECT DISTINCT s.name,
substring_index(p.subject_list, ',', 1) AS "subject_1",
IF(instr(p.subject_list, ','),
substring_index(p.subject_list, ',', -1),
NULL
) AS "subject_2"
FROM subjects s
JOIN (SELECT name, GROUP_CONCAT(subject) AS "subject_list"
FROM subjects
GROUP BY name
) p on p.name = s.name
;
I am running a complicated group by statement and I get all my results in their respective groups. But I want to create a custom column with their "group id". Essentially all the items that are grouped together would share an ID.
This is what I get:
partID | Description
-------+---------+--
11000 | "Oven"
12000 | "Oven"
13000 | "Stove"
13020 | "Stove"
12012 | "Grill"
This is what I want:
partID | Description | GroupID
-------+-------------+----------
11000 | "Oven" | 1
12000 | "Oven" | 1
13000 | "Stove" | 2
13020 | "Stove" | 2
12012 | "Grill" | 3
"GroupID" does not exist as data in any of the tables, it would be a custom generated column (alias) that would be associated to that group's key,id,index, whatever it would be called.
How would I go about doing this?
I think this is the query that returns the five rows:
select partId, Description
from part p;
Here is one way (using standard SQL) to get the groups:
select partId, Description,
(select count(distinct Description)
from part p2
where p2.Description <= p.Description
) as GroupId
from part p;
This is using a correlated subquery. The subquery is finding all the description values less than the current one -- and counting the distinct values. Note that this gives a different set of values from the ones in the OP. These will be alphabetically assigned rather than assigned by first encounter in the data. If that is important, the OP should add that into the question. Based on the question, the particular ordering did not seem important.
Here's one way to get it:
SELECT p.partID,p.Description,b.groupID
FROM (
SELECT Description,#rn := #rn + 1 AS groupID
FROM (
SELECT distinct description
FROM part,(SELECT #rn:= 0) c
) a
) b
INNER JOIN part p ON p.description = b.description;
sqlfiddle demo
This gets assigns a diferent groupID to each description, and then joins the original table by that description.
Based on your comments in response to Gordon's answer, I think what you need is a derived table to generate your groupids, like so:
select
t1.description,
#cntr := #cntr + 1 as GroupID
FROM
(select distinct table1.description from table1) t1
cross join
(select #cntr:=0) t2
which will give you:
DESCRIPTION GROUPID
Oven 1
Stove 2
Grill 3
Then you can use that in your original query, joining on description:
select
t1.partid,
t1.description,
t2.GroupID
from
table1 t1
inner join
(
select
t1.description,
#cntr := #cntr + 1 as GroupID
FROM
(select distinct table1.description from table1) t1
cross join
(select #cntr:=0) t2
) t2
on t1.description = t2.description
SQL Fiddle
SELECT partID , Description, #s:=#s+1 GroupID
FROM part, (SELECT #s:= 0) AS s
GROUP BY Description
I have a Table member with member_id, member_name, club_name, region, zone, email as fields.
I am using the MySQL group_concat function like
SELECT group_concat(distinct m.email
SEPARATOR ', ' ) from member m group by m.club_name
This is working fine. But I would like to be able to group_concat on other fields without creating additional queries.
Is it possible to supply the other fields as parameter?
member_id member_name club_name region zone email
1 member1 A 1 1 email1#example.com
2 member2 A 1 1 email2#example.com
3 member3 B 1 1 email3#example.com
4 member4 C 1 2 email4#example.com
5 member5 D 2 1 email5#example.com
**group by club**
email1#example.com,email2#example.com
email3#example.com
email4#example.com
email5#example.com
**group by region**
email1#example.com, email2#example.com, email3#example.com, email4#example.com
email5#example.com
**group by zone**
email1#example.com, email2#example.com, email3#example.com
email5#example.com
Say every Region has 3 Zones, every zone has more than one club. Now how can I get emails which can be grouped or related to Region, Zone or Club for that matter?
It's hard to understand what are you after exactly from your question but you can try
SELECT club_name,
GROUP_CONCAT(DISTINCT email SEPARATOR ', ' ) emails,
GROUP_CONCAT(DISTINCT member_name SEPARATOR ', ' ) members
...
FROM member
GROUP BY club_name
Sample output:
| CLUB_NAME | EMAILS | MEMBERS |
------------------------------------------------------------------------
| Club1 | m1#mail.com, m2#mail.com, m3#mail.com | Jhon, Mark, Beth |
| Club2 | m4#mail.com, m5#mail.com | Helen, Thomas |
Here is SQLFiddle demo
On a side note: providing sample data and desired output in a question like this usually improves your changes of getting your answer faster and that best fits your needs.
UPDATE: You can deeply pack information using GROUP_CONCAT() using different separators if it's what you want
SELECT 'club' group_type, GROUP_CONCAT(details SEPARATOR '|') details
FROM
(
SELECT CONCAT(club_name, ';', GROUP_CONCAT(DISTINCT email)) details
FROM member
GROUP BY club_name
) a
UNION ALL
SELECT 'region' group_type, GROUP_CONCAT(details SEPARATOR '|') details
FROM
(
SELECT CONCAT(region, ';', GROUP_CONCAT(DISTINCT email)) details
FROM member
GROUP BY region
) a
UNION ALL
SELECT 'zone' group_type, GROUP_CONCAT(details SEPARATOR '|') details
FROM
(
SELECT CONCAT(zone, ';', GROUP_CONCAT(DISTINCT email)) details
FROM member
GROUP BY zone
) a
Sample output:
| GROUP_TYPE | DETAILS |
-----------------------------------------------------------------------------------------------------------------------
| club | A;email1#example.com,email2#example.com|B;email3#example.com|C;email4#example.com|D;email5#example.com |
| region | 1;email1#example.com,email2#example.com,email3#example.com,email4#example.com|2;email5#example.com |
| zone | 1;email1#example.com,email2#example.com,email3#example.com,email5#example.com|2;email4#example.com |
Here is SQLFiddle demo
If you're using php on the client side you can then easily enough unwind details column into separate records using explode() while you're iterating over the resultset.