MySQL junction table concatenation - mysql

I have an requirement to return all educators associated with a session as a concatenated list, but to do it within a join (don't ask - I know there are multiple ways of doing it but I'm working with a library that won't accept those methods ;-))
I have a sessions table with session_id and session_name.
| session_id | session_name |
+------------+--------------+
| 1 | Swimming |
| 2 | Chess |
I have a session_educators table which is basically a junction table session_id and contact_id.
| session_id | contact_id |
+------------+------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 2 | 4 |
I have a contacts table with contact_id and full_name
| contact_id | full_name |
+------------+--------------+
| 1 | Fred Bloggs |
| 2 | Mary Bloggs |
| 3 | Mark Smith |
| 4 | Shelly Smith |
So far this seems to be the closest I've come:
SELECT
sessions.session_id,
sessions.name,
educators.names
FROM
`sessions`
LEFT JOIN
(
SELECT
GROUP_CONCAT(contacts.full_name SEPARATOR ', ') as names
FROM
`contacts`
WHERE
contact_id
IN
(
SELECT
contact_id
FROM
session_educator
WHERE
session_educator.session_id = sessions.session_id
)
) `educators`
USING
(`session_id`)
But I'm really fumbling in the dark trying to figure it out, can anyone help?
What I'm wanting, as you can probably tell from the query, is a result like this:
| session_id | session_name | educators |
+------------+--------------+--------------------------+
| 1 | Swimming | Fred Bloggs, Mary Bloggs |
| 2 | Chess | Mark Smith, Shelly Smith |
Any help greatly appreciated - even if it's just to say it can't be done!

You shouldn't need subqueries for this. It is a few joins and an aggregation:
select s.session_id, s.session_name,
group_concat(e.name separator ', ') as educators
from sessions s left join
session_educators se
on se.session_id = s.session_id left join
educators e
on e.educator_id = se.educator_id
group by s.session_id;

I do believe you're making it all a little more complex than needed (unless I've missed something in your requirements? I know how much of a pain libraries can be...)
This worked for me:
SELECT
session_id,
session_name,
(
SELECT
GROUP_CONCAT(full_name SEPARATOR ', ') as names
FROM
contacts c,
session_educators se
WHERE
c.contact_id = se.contact_id
AND
se.session_id = s.session_id
)
FROM
sessions s
;

Related

Is there a more idiomatic way to merge related rows from two tables?

I'm using a contrived example in order to illustrate the issue.
Imagine a simple table of books containing a title and subject/genre. In addition, there's an associated table of related subjects.
> SELECT * FROM books;
+----+--------+-----------+
| id | title | subject |
+----+--------+-----------+
| 1 | Book A | science |
| 2 | Book B | reference |
| 3 | Book C | fiction |
+----+--------+-----------+
> SELECT * FROM related_subjects;
+----+---------+---------+
| id | book_id | subject |
+----+---------+---------+
| 1 | 1 | physics |
| 2 | 1 | space |
| 3 | 3 | crime |
+----+---------+---------+
I'd like a query that could output all the title + subject combinations, so that it would look something like:
+----+--------+-----------+
| id | title | SUBJECT |
+----+--------+-----------+
| 1 | Book A | science |
| 1 | Book A | space |
| 1 | Book A | physics |
| 2 | Book B | reference |
| 3 | Book C | fiction |
| 3 | Book C | crime |
+----+--------+-----------+
The most obvious way, is to use a UNION as follows:
SELECT books.id, books.title, SUBJECT FROM books
UNION
SELECT books.id, books.title, related_subjects.subject FROM books
INNER JOIN related_subjects ON related_subjects.book_id = books.id;
Which yields a good result:
+----+--------+-----------+
| id | title | SUBJECT |
+----+--------+-----------+
| 1 | Book A | science |
| 2 | Book B | reference |
| 3 | Book C | fiction |
| 1 | Book A | space |
| 1 | Book A | physics |
| 3 | Book C | crime |
+----+--------+-----------+
However, it would be preferable if the natural output ordering was similar to my desired output, where the books row comes out first, followed by its related rows from the related_subjects table, and so on.
I'm curious as to whether there's a better/more efficient way of doing this sort of task? Particularly one that would give me a more natural ordering without having to apply a sort on the end result first.
Note: of course, I know I can apply a DB sort to the union output by ordering on books.id, related_subjects.id, but the output in my real world app consists of hundreds of thousands of rows, and so no harm in avoiding a relatively expensive sort if it can be avoided.
Introduce a computed column into the union query for ordering:
SELECT id, title, subject
FROM
(
SELECT id, title, subject, 1 AS src FROM books
UNION ALL
SELECT b.id, b.title, rs.subject, 2
FROM books b
INNER JOIN related_subjects rs ON rs.book_id = b.id
) t
ORDER BY id, src;
It seems like you have a one-to-many relationship between books and subjects. So you could drop the subject column from the books table, and just make sure all the subjects that apply to the book are in the related_subjects table. Then you don't have to use UNION to get both, you just do the join from book to related_subjects.
In theory SQL does not guarantee any order of query results unless you specify the ORDER BY. But in practice, InnoDB returns rows in the order it reads them in the index it uses to look them up.

Mysql, select from table multiple values

I have two tables in my database.
Couples table:
+---------------------------------------+
| ID | Partner 1 | Partner 2 |
+---------------------------------------+
| 1 | 101 | 102 |
+---------------------------------------+
B table:
+--------------------------------------------------------------
| ID | name | date | Letter | Phonenumber
+--------------------------------------------------------------
| 101| Mark | 1/1/2001 | D | 061234
| 102| lisa | 1/1/2002 | E | 061235
I cant quite figure out how to do the following:
Select from couples table the partners 1 & 2.
From partner 1, grab the name, date, letter & phonenumber.
From partner 2, grab the name, letter and date.
Do this for every couple in the couples table
I cant figure out how to do this, anyone know how? I have been stuck for quite some time now haha.
something like this
+------------------------------------------------------------------------
| ID | name | date | Letter | Phonenumber | name | Letter| date
+------------------------------------------------------------------------
| 1 | Mark | 1/1/2001 | D | 061234 | lisa | E | 1/1/2002
+------------------------------------------------------------------------
You need to join twice - once for each partner. e.g.:
SELECT p1.id, p1.name, p1.date, p1.letter, p1.phonenumber, p2.name, p2.letter, p2.date from couples
JOIN B on (B.id = partner_1) as p1
JOIN B on (B.id = partner_2) as p2
My syntax may not be perfect, but it sounds like you know enough SQL to not need a copy+paste solution, just a pointer on how to solve.

Get shared contacts for two users from multiple MySQL tables

I have two tables in my MySQL database.
The first one stores a list of users, and the other, stores a list of contacts for each user.
Users table:
+----+----------+--------------+
| id | name | phoneNumber |
+----+----------+--------------+
| 1 | David | 661-618-5436 |
| 2 | Sarah | 818-526-4830 |
| 3 | Suzan | 323-623-3493 |
+----+----------+--------------+
Contacts table:
+----+-----------------+--------+--------------+
| id | belongsToUserId | name | phoneNumber |
+----+-----------------+--------+--------------+
| 1 | 1 | Gerard | +18185329384 |
| 2 | 1 | Austin | +18739283847 |
| 3 | 2 | Jamie | +15655468907 |
| 4 | 2 | Jade | +19893828192 |
| 5 | 3 | Phil | +18786754234 |
| 6 | 3 | Duke | +18765467832 |
| 7 | 3 | Gerard | +18185329384 |
| 8 | 3 | Jade | +19893828192 |
+----+-----------------+--------+--------------+
What I want to do, is create a query that efficiently takes 2 user IDs and returns the common contacts by phoneNumber for these two users.
For example: User IDs 1 & 3 both have Gerard | +18185329384 in their contacts so the query will return only him.
What could be the most efficient query for this kind of task?
Thanks :)
If I understood your question correctly, I think this might be what you're looking for:
SELECT
c1.id,
c1.belongsToUserId,
c1.phoneNumber,
c1.name
FROM
Contacts c1
JOIN
Contacts c2 ON (c1.phoneNumber=c2.phoneNumber AND c2.userId=3)
WHERE
c1.belongsToUserId =1
You want a self-join:
select c1.name, c1.phonenumber
from contacts c1 join
contacts c2
on c1.name = c2.name and c1.phonenumber = c2.phonenumber and
c1.belongsToUserId = 1 and
c2.belongsToUserId = 3;
The following SELECT will give you all contacts who has more than 1 User.
SELECT Contacts.name, Contacts.phoneNumber
FROM Users INNER JOIN Contacts ON (Users.id = Contacts.belongsToUserId)
GROUP BY Contacts.name, Contacts.phoneNumber
HAVING COUNT (*) > 1
Following query will display shared contact along with the UseID who's having common contacts.
If you just want to show details of shared contacts, then you can skip first 2 columns.
select min(belongsToUserId) as User1,
max(belongsToUserId) as User2,
name,
phone Number
from contacts
group by name,phoneNumber
having count(*) > 1;
SELECT
cp.name,
cs.phoneNumber
FROM
Contacts cp
INNER JOIN Contacts cs ON
cp.name = cs.name AND cp.phoneNumber = cs.phoneNumber
AND
cp.belongsToUserId IN ( 1,3 );

MySQL Select use IN and GROUP BY

I have two tables messages and users I want to find out which users received the messages however the query is only returning one message.
My Schemas are as follow
Messages
msg_id | msg_content | recipients |
-----------------------------------
1 | Hello world | 1,2,3,4,5
2 | Test | 1,3,5
3 | Welcome | 1,2,4
Users
uid | fname | lname |
---------------------------
1 | John |Doe |
2 | Jane |Doe |
3 | Mark |Someone |
4 | Mary |lady |
5 | Anthony |Doe |
So I would love to see my results simply as
msg_id | msg_content | recipients |
-----------------------------------
1 | Hello world | John,Jane,Mark,Mary,Anthony
2 | Test | John,Mark,Anthony
3 | Welcome | John,Jane,Mary
So I am doing my query as so
SELECT msg_id,msg_content,fname AS recepients FROM messages a
LEFT JOIN users ON uid IN(a.recipients)
When I run that query I only get one recipient. Please advice. Thanks.
I think you have to use a alternative way for create tables
Messages
msg_id | msg_content |
----------------------
1 | Hello world |
2 | Test |
3 | Welcome |
Users
uid | fname | lname |
---------------------------
1 | John |Doe |
2 | Jane |Doe |
3 | Mark |Someone |
4 | Mary |lady |
5 | Anthony |Doe |
users_has_messages
uhm_id | uid | msg_id |
---------------------------
1 | 1 | 1 |
2 | 2 | 1 |
3 | 3 | 1 |
4 | 2 | 2 |
5 | 1 | 3 |
Then you can use your code
Okay, so this schema isn't the best (using comma separated lists of IDs is not a great idea, and the performance of any joins will get pretty bad pretty quick). Best bet is to have a third table mapping uid's to msg_id's as mentioned by #Thilina.
That said, this query will do probably what you're after:
SELECT msg_id,msg_content,GROUP_CONCAT(fname) AS recepients FROM messages a
LEFT JOIN users ON FIND_IN_SET(uid, a.recipients)
GROUP BY msg_id
I tried this in Oracle 12c and it is working fine.
So basically what I did is
- Separate the userid from recipient field and used this a columns.
- Join with USERS table to get user fnames
- Used LISTAGG function to aggregate it back.
For MySql we need to find the corresponding functions to Separate the IDs between commas, Convert it to rows and Aggregate. But the inherent logic would be same.
with users (user_id,fname) as (
select 1 ,'John' from dual union
select 2 ,'Jane' from dual union
select 3 ,'Mark' from dual union
select 4 ,'Mary' from dual union
select 5 ,'Anthony' from dual
),
messages(msg_id, msg_content,recipients) as(
select 1,'Hello world','1,2,3,4,5' from dual union
select 2 , 'Test' ,'1,3,5' from dual union
select 3,' Welcome','1,2,4' from dual
),
flat as(
select msg_id,msg_content,
REGEXP_SUBSTR (recipients, '[^,]+', 1, COLUMN_VALUE) as user_id
from messages,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(recipients ,',' ) + 1
) AS SYS.ODCINUMBERLIST
)
)
),
unames as
( select f.msg_id,f.msg_content,u.fname from flat f inner join users u
on f.user_id = u.user_id
order by f.msg_id
)
SELECT msg_id,msg_content,LISTAGG(fname, ',') WITHIN GROUP (ORDER BY fname) as recipients
from unames
group by msg_id,msg_content

Building an SQL query for GROUP_CONCAT-ing multiple one/many-to-many tables

I have four tables, person, email, organ and person_organ and:
person and email has one-to-many relation;
person and organ has many-to-many relation through person_organ.
Question, how to I write a query so I have a set with columns full name, all email and all organ for persons in a particular organ?
This is as far as I get, using GROUP_CONCAT to combine emails grouped by person:
select person.id as 'ID',
organ.short_name as 'Organ',
CONCAT ( person.first_name, ' ', person.last_name) as 'Full Name',
GROUP_CONCAT(email.email SEPARATOR ', ') as 'Emails'
from person
left join person_organ on person.id = person_organ.person_id
join organ on organ.id = person_organ.organ_id
join email on email.person_id = person.id
where person_organ.organ_id = 1
group by person.id;
What can I add to the above query so that another column is added that shows all the groups that this person belongs to?
What I have so far:
+----+-------+--------------+----------------------------+
| ID | Organ | Full Name | Emails |
+----+-------+--------------+----------------------------+
| 1 | SC | John Doe | john#doe.me, jdoe#mail.org |
| 2 | SC | Richard Rowe | rowe#mail.us |
| 3 | SC | San Zhang | zhangsan#mail.cn |
| 4 | SC | Taro Yamada | yamada#mail.jp |
+----+-------+--------------+----------------------------+
What I want, is to have the Organs as an additional column to the above result set, in addition to the singular Organ field:
+----+-------+--------------+----------------------------+------------+
| ID | Organ | Full Name | Emails | Organs |
+----+-------+--------------+----------------------------+------------+
| 1 | SC | John Doe | john#doe.me, jdoe#mail.org | SC, EC |
| 2 | SC | Richard Rowe | rowe#mail.us | SC, EC, HR |
| 3 | SC | San Zhang | zhangsan#mail.cn | SC, HR |
| 4 | SC | Taro Yamada | yamada#mail.jp | SC, EC, HR |
+----+-------+--------------+----------------------------+------------+
Thank you very much for your answer! I am quite new to making real applications and I feel that this should be quite a common task. Any help on pointing to the right resources is much welcome!
-- update: SQLfiddle link: http://www.sqlfiddle.com/#!9/bc08de/40
http://www.sqlfiddle.com/#!9/bc08de/14/0 I am not much aware of the syntax in MySQL but I have tried this and got to this result. The id that is 1 has duplicates. Maybe because of some syntax. just have a look at it
Have got the desired result.