Concatenate results from SQL query base on another column - mysql

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

Related

Use a GROUP_CONCAT and JOIN in a subquery

I have a DB under MySQL with one main table with unique id, few tables that list different choices (with one id column, one text column to describe the item). So, for one record in the main table, I can have multiple choices associated to the choice table.
I'd like to create a View where all information could be visible, using GROUP_CONCAT to concatenate into one field the different choices from a given 'choice' table. However, my query repeats many times each list of choices when the record is related to other multiple choices from another 'choice' table. The query returns all the possible combinations between those choices...
Here my query (reduced to 2 'choice' tables -t_age, t_animal- for the example)
SELECT general.id_g,
GROUP_CONCAT(CAST(t_age AS CHAR) SEPARATOR ', ') AS age,
GROUP_CONCAT(CAST(t_animal AS CHAR) SEPARATOR ', ') AS animal
FROM general
LEFT JOIN interm_age
INNER JOIN t_age ON interm_age.id_age = t_age.id_age
ON general.id_g = interm_age.id_g
LEFT JOIN interm_animal
INNER JOIN t_animal ON interm_animal.id_animal = t_animal.id_animal
ON general.id_g = interm_animal.id_g
GROUP BY id_g;
I tried to include each CONCAT/JOIN within a subquery into a main SELECT as followed, but MySQL tells me "returns more than 1row", which is the case indeed. And?
SELECT id_g, (
SELECT GROUP_CONCAT(CAST(t_age AS CHAR) SEPARATOR ', ')
FROM general
LEFT JOIN interm_age
INNER JOIN t_age ON interm_age.id_age = t_age.id_age
ON general.id_g = interm_age.id_g
GROUP BY general.id_g )
FROM general;
[EDIT]
In more details, this is my DB (with FK)
general
-----------
id_g | date
902 | 2016/01/01
956 | 2016/02/01
959 | 2016/02/01
interm_age
-----------
id_age | id_g
1 | 902
3 | 902
1 | 956
4 | 956
interm_animal
-----------
id_animal | id_g
1 | 902
5 | 902
5 | 959
7 | 959
t_age
-----------
id_age | age
1 | <10y
3 | >10y
4 | >60y
t_animal
-----------
id_animal | animal
1 | bird
5 | mammal
7 | insect
And I would like something like :
id_g | date | age | animal
902 | 2016/01/01 | <10y, >10y | bird, mammal
and so on...
Thanks in advance!
Although it would be better if you provided some sample data, actual results, and expected results, I'll have a stab at the solution.
The issue probably is that you have multiple matching records in the joined tables, which leads to duplication of values within group_concat().
As MySQL documentation on group_concat() indicates, you can use the distinct keyword to remove duplicate values:
To eliminate duplicate values, use the DISTINCT clause.
SELECT general.id_g,
GROUP_CONCAT(DISTINCT CAST(t_age AS CHAR) SEPARATOR ', ') AS age,
GROUP_CONCAT(DISTINCT CAST(t_animal AS CHAR) SEPARATOR ', ') AS animal
FROM general
LEFT JOIN interm_age
INNER JOIN t_age ON interm_age.id_age = t_age.id_age
ON general.id_g = interm_age.id_g
LEFT JOIN interm_animal
INNER JOIN t_animal ON interm_animal.id_animal = t_animal.id_animal
ON general.id_g = interm_animal.id_g
GROUP BY id_g;
Try this
SELECT general.id_g,
GROUP_CONCAT(CAST(t_age AS CHAR) SEPARATOR ', ') AS age,
GROUP_CONCAT(CAST(t_animal AS CHAR) SEPARATOR ', ') AS animal
FROM general
LEFT JOIN interm_age
INNER JOIN t_age ON interm_age.id_age = t_age.id_age AND general.id_g = interm_age.id_g
LEFT JOIN interm_animal
INNER JOIN t_animal ON interm_animal.id_animal = t_animal.id_animal AND general.id_g = interm_animal.id_g
GROUP BY id_g;
Hope this helps.

Mysql query from two tables as parent and child objects

I am creating an Angularjs app with two tables "Contact Group" and "Contact List". The contact list table items have corespondent id to items in the contact group.
What I want to achieve is a MySQL/Sqlite select query that will have each contact list items as a child object of its parent.
contact_group
id | title
------------
1 | friends
2 | mates
3 | family
contact_list
id | gID | name | number
--------------------------
1 | 1 | dave |0208500
2 | 1 | dan |4213839
3 | 1 | sans |5656434
4 | 2 | fola |1918982
5 | 3 | brian|2398343
6 | 5 | grace|0934232
Select query results examples (this is what i want to get)
id: 1
title: friends
child: {id:1, name:dave, number:0208500}, {id:2, name:dan, number:4213839},{id:3, name:sans, number:5656434}
I case there is another way of doing it, this what i want to achieve. I have created an accordion with title from the contact_group title. Under each accordion are the correspondent contacts from the contact_list.
I will be glad if anyone can help me. Thank you
In order to achieve this you will have to use MySql GROUP_CONCAT (documentation here: https://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat)
Using GROUP_CONCAT we will be able to join all matching rows from the contacts TB into a string in one column. With some more formatting of the text we glue together from the contacts TB and additional usage of CONCAT we will build the string in this column as a JSON string that you will then be able to work with on your application.
Here is the SQL:
SELECT
a.id,
a.title,
CONCAT(
'[',
GROUP_CONCAT(
CONCAT(
'{"id":',
b.id,
', "name":"',
b. `name`,
'", "number":"',
b.number,
'"}'
)
ORDER BY
b.`name`
), ']')
AS people
FROM
contact_group AS a
LEFT JOIN contact_list AS b ON a.id = b.gID
GROUP BY
a.id,
a.title
And this is the result we will get:
Don't forget to add Indexes and Foreign keys to your tables so query processing would be better.
SQLLITE Version:
in order to adjust the query to sqllite, there are 2 features that need to be changed.
SqlLite uses || operator instead of CONCAT and it does not support ORDER BY inside the GROUP_CONCAT. so for the SQLLITE DB the query will look like this:
SELECT
a.id,
a.title,
'[' ||
GROUP_CONCAT(
'{"id":' ||
b.id ||
', "name":"' ||
b. `name` ||
'", "number":"' ||
b.number ||
'"}'
) || ']'
AS people
FROM
contact_group AS a
LEFT JOIN contact_list AS b ON a.id = b.gID
GROUP BY
a.id,
a.title
MySQL Fiddle Demo
SqlLite Fiddle Demo
Try the below query,
select * from contact_group join contact_list on contact_group.id= contact_list.gid

MySql query on 3 tables not returning all rows

I have 3 tables: questions, options, comments_to_options(opt_comments).
I want to write a query that returns in each row the following values, concatenated:
A question, all options to it, all comments to each option.
My query is:
select
concat('{', '"qid":"', q.q_id, '", "qt":"', q.q_title,
'", "op":[', group_concat('{"oi":"', o.op_id, '", "ot":"', o.opt_value, '", ', oc_list, '}'
order by o.opt_upvotes desc), ']}')
as r
from questions q, options o,
(select o.op_id as ocid, concat('"oc":[', group_concat('{"oci":"', oc.opt_com_id, '", "occ":"', oc.opt_com_value, '"}'
order by oc.opt_com_added_at), ']')
as oc_list
from options o, opt_comments oc
where oc.opt_com_to=o.op_id
group by o.op_id)
as r2
where o.op_id=r2.ocid
and q.q_id=o.option_to
group by q.q_id
order by q.q_added_at desc
limit 3;
But the above query gives only those options that have at least one comment to them.
How should I modify?
You are using the old JOIN syntax with comma-separated lists of tables and subqueries. That syntax is correct, but generates INNER JOIN operations. Such joins suppress rows that don't match the join criterion.
You need to adopt the LEFT JOIN syntax. Without refactoring your entire query, I will say that you should change
FROM a,
(select something from z) AS b
WHERE a.value=b.value
to
FROM a
LEFT JOIN (select something from z) AS b ON a.value=b.value
Also, beware, you may encounter the character-string length limit in GROUP_CONCAT(). Read this:
MySQL and GROUP_CONCAT() maximum length
Use "left join".
Example:
create table opt (oid int,name varchar(100));
insert into opt values (1,'opt1');
insert into opt values (2,'opt2');
insert into opt values (3,'opt3');
create table optcom (oid int,com varchar(100));
insert into optcom values (1,'opt1_1');
insert into optcom values (1,'opt1_2');
insert into optcom values (3,'opt3_1');
When using "simple join":
select opt.*,optcom.* from opt join optcom on opt.oid=optcom.oid;
+------+------+------+--------+
| oid | name | oid | com |
+------+------+------+--------+
| 1 | opt1 | 1 | opt1_1 |
| 1 | opt1 | 1 | opt1_2 |
| 3 | opt3 | 3 | opt3_1 |
+------+------+------+--------+
When "left join":
select opt.*,optcom.* from opt left join optcom on opt.oid=optcom.oid;
+------+------+------+--------+
| oid | name | oid | com |
+------+------+------+--------+
| 1 | opt1 | 1 | opt1_1 |
| 1 | opt1 | 1 | opt1_2 |
| 2 | opt2 | NULL | NULL |
| 3 | opt3 | 3 | opt3_1 |
+------+------+------+--------+
To follow up on the above responses, the SQL amended to use outer joins:-
SELECT CONCAT('{', '"qid":"', q.q_id, '", "qt":"', q.q_title,'", "op":[', GROUP_CONCAT('{"oi":"', o.op_id, '", "ot":"', o.opt_value, '", ', oc_list, '}' ORDER BY o.opt_upvotes DESC), ']}') AS r
FROM options o
LEFT OUTER JOIN questions q
ON q.q_id = o.option_to
LEFT OUTER JOIN
(
SELECT o.op_id AS ocid,
CONCAT('"oc":[', GROUP_CONCAT('{"oci":"', oc.opt_com_id, '", "occ":"', oc.opt_com_value, '"}' ORDER BY oc.opt_com_added_at), ']') AS oc_list
FROM options o
INNER JOIN opt_comments oc
ON oc.opt_com_to=o.op_id
GROUP BY o.op_id
) r2
ON o.op_id = r2.ocid
GROUP BY q.q_id
ORDER BY q.q_added_at DESC
LIMIT 3;
Looking at this I am unsure about the join to the sub query. This appears to be bringing back an encoded string, but beyond the actual join nothing from this sub query is actually used.
As such I am unsure if that sub query is just being used to narrow down the rows returned (in which case joining against it using an INNER JOIN would be appropriate - and you may as well not bring back the encoded string), or if you have posted a cut down version of the query that you have been trying to debug.

mysql group_concat group by on multiple fields

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.

Add multiple rows in just one row from a single table

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.