JOIN and GROUP_CONCAT with three tables - mysql

I have three tables:
users: sports: user_sports:
id | name id | name id_user | id_sport | pref
---+-------- ---+------------ --------+----------+------
1 | Peter 1 | Tennis 1 | 1 | 0
2 | Alice 2 | Football 1 | 2 | 1
3 | Bob 3 | Basketball 2 | 3 | 0
3 | 1 | 2
3 | 3 | 1
3 | 2 | 0
The table user_sports links users and sports with an order of preference (pref).
I need to make a query that returns this:
id | name | sport_ids | sport_names
---+-------+-----------+----------------------------
1 | Peter | 1,2 | Tennis,Football
2 | Alice | 3 | Basketball
3 | Bob | 2,3,1 | Football,Basketball,Tennis
I have tried with JOIN and GROUP_CONCAT but I get weird results.
Do I need to do a nested query?
Any ideas?

Its not particularly difficult.
Join the three tables using the JOIN clause.
Use Group_concat on the fields you're interested in.
Don't forget the GROUP BY clause on the fields you're not concatenating or weird things will happen
SELECT u.id,
u.Name,
Group_concat(us.id_sport order by pref) sport_ids,
Group_concat(s.name order by pref) sport_names
FROM users u
LEFT JOIN User_Sports us
ON u.id = us.id_user
LEFT JOIN sports s
ON US.id_sport = s.id
GROUP BY u.id,
u.Name
DEMO
Update LEFT JOIN for when the user doesn't have entries in User_Sports as per comments

I think this is just a simple join and aggregation:
select u.id, u.name, group_concat(s.name order by pref separator ',')
from user_sports us join
users u
on us.id_user = u.id join
sports s
on us.id_sport = s.id
group by u.id, u.name

Related

Filtering results from a subquery in MySql

I have a many to many user-sport relation and I'm getting a concatenated string of all sport names a user plays using a subquery. My structure looks like:
user
===============
| id | name |
---------------
| 1 | Hugo |
| 2 | Paco |
| 3 | Luis |
---------------
sport
=================
| id | name |
-----------------
| 1 | tennis |
| 2 | football |
| 3 | handball |
-----------------
user_sport
======================
| user_id | sport_id |
----------------------
| 1 | 3 |
| 1 | 1 |
| 2 | 1 |
----------------------
how do I filter the results with users that play any sport from a list, for example getting all users who play tennis or handball.
I'm trying with this query:
SELECT u.id, u.name,
COALESCE (
(SELECT GROUP_CONCAT(s.name SEPARATOR ', ')
FROM sport AS s
LEFT JOIN user_sport AS us ON us.sport_id = s.id
WHERE us.user_id = u.id),'') AS sports
FROM user u
WHERE us.sport_id IN (1,3)
GROUP BY u.id
ORDER BY g.name
but it is not working because the where clause doesn't know the user_sport table. So, I have to create a new JOIN outside the subquery?
You have to join with user_sport twice. Once to filter it to just the sports in the list, and the other time to get all the sports that the selected users play in.
SELECT u.id, u.name, GROUP_CONCAT(s.name SEPARATOR ', ') AS sports
FROM user AS u
JOIN user_sport AS us_filtered ON u.id = us_filtered.user_id
JOIN user_sport AS us_all ON u.id = us_all.user_id # all
JOIN sport AS s ON s.id = us_all.sport_id
WHERE us_filtered.sport_id IN (1, 3)
GROUP BY u.id
ORDER BY u.name

How to count rows in nested tables with one SQL query?

I have three tables. Each User can have multiple Subscriptions and each Subscription can have multiple Payments.
Me goal is to count all Payments for a single User using one SQL query. Is it possible to do and how?
In the case below, The result for a User with id 1 should be 2 (because the User has two Payments)
Users
+----+------+
| Id | Name |
+----+------+
| 1 | John |
+----+------+
Subscriptions
+----+--------+-----------+
| Id | userId | data |
+----+--------+-----------+
| 1 | 1 | some data |
+----+--------+-----------+
| 2 | 1 | some data |
+----+--------+-----------+
Payments
+----+----------------+--------+
| Id | subscriptionId | amount |
+----+----------------+--------+
| 1 | 1 | 30 |
+----+----------------+--------+
| 2 | 2 | 50 |
+----+----------------+--------+
try like below by using join and aggregation
SELECT u.id, u.Name, COUNT(p.id) AS numberofpayment
FROM users u
Left JOIN Subscriptions s ON u.Id=s.userId
Left JOIN Payments p ON s.id=p.subscriptionId
GROUP BY u.id, u.Name
You can try to do something like this:
SELECT COUNT(p.Id) AS PaymentCount
FROM Users u
LEFT JOIN Subscriptions s ON u.Id=s.userId
LEFT JOIN Payments p ON s.id=p.subscriptionId
WHERE u.Id = #yourUserID
Pay attention on COUNT(p.Id) - it means count of existing payments.
PS: this answer for #Kickstart.

Frequency join statement

I have 2 tables:
users:
id | name
-----------
1 | user 1
2 | user 2
3 | user 3
and posts:
id | userId | text
--------------------
1 | 1 | text 1
2 | 1 | text 2
3 | 2 | text 3
4 | 2 | text 4
5 | 2 | text 5
6 | 2 | text 6
I need to retrieve users ordered by post-frequency, e.g.:
id | name | posts
-------------------
2 | user 2 | 4
1 | user 1 | 1
3 | user 3 | 0
Some users might not have posts!
Currently I have 2 queries and doing it in 3 steps:
retrieve all users
retrieve all posts grouped by userId
use php to join the above
Question
Is the above possible to do in a single sql query?
select u.id, u.name, count(p.id) as posts
from users u
left join posts p on p.userid = u.id
group by u.id, u.name
order by posts desc
Another version, which prevents listing all fields from users table in group by clause. Also more fast in many cases IMO.
select u.id, u.name, coalesce(c.cnt, 0) as posts
from users u
left join
(select userId, couint(*) cnt from posts group by userId) c
on u.id = c.userId

Union and Intersection of data

I have three tables:
user
id | name
------------------
1 | Foo
2 | Bar
3 | Baz
group_type
id | name
------------------
1 | Group 1
2 | Group 2
3 | Group 3
4 | Group 4
5 | Group 5
user_group
id | user_id | group_type_id | [..]
------------------------------------
1 | 1 | 1 | [..]
2 | 1 | 3 | [..]
3 | 2 | 1 | [..]
4 | 1 | 5 | [..]
5 | 2 | 3 | [..]
6 | 3 | 3 | [..]
Well, currently, I can find all users from a specified list of groups with union, which is like a "or" clause:
SELECT u.*
FROM user u,
user_group ug
WHERE ug.user_id = u.id
AND ug.group_type_id IN( 1, 3, 5 )
Resulting:
id | name
------------------
1 | Foo
2 | Bar
3 | Baz
Now, I need to intersect the gorup, find all users which have groups of type 1 AND 3, resulting:
id | name
------------------
1 | Foo
2 | Bar
I have tried some queries, but don't imagine a way of doing this correctly.
SELECT u.id, u.name
FROM user u
INNER JOIN user_group g
ON u.id = g.user_id
WHERE ug.group_type_id IN (1,3)
GROUP BY u.id, u.name
HAVING count(distinct ug.group_type_id) = 2
Not as clean as the normal case, but it's certainly possible.
Try to use INTERSECT query. The syntax for the SQL INTERSECT query is:
select field1, field2, ... field_n
from tabl,tab2...
INTERSECT
select field1, field2, ... field_n
from tablel,table2...
I'm not sure if my syntax is perfect, but I'd reccomend self-joining user_group onto itself using user_id and forcing one of the selected entries (ug1 and ug2) to have ug1.group_type_id=1 and the other ug2.group_type_id=3. This gives you all user_id's with 1 AND 3 as their group_type_id. Now that you have that, you can do another join onto your user table, giving you all of the results that you were looking for.
SELECT u.*
FROM user u
JOIN (SELECT ug1.user_id
FROM user_group ug1 JOIN user_group ug2
ON ug1.user_id=ug2.user_id
WHERE ug1.group_type_id=1 and ug2.group_type_id=3) ug
ON u.id=ug.user_id

Joining tables when certain one table has no values?

I have these tables:
USER TABLE
uid | name | role
| |
1 | bob | package 1
2 | jill | package 2
3 | pam | package 1
NODE TABLE
nid | uid | type
| |
1 | 1 | car
2 | 1 | car
3 | 1 | car
4 | 2 | page
5 | 1 | car
6 | 3 | car
If I do:
select u.uid, u.name, count(nid) as totalNodes from USER as u left join NODE on n.uid = u.uid where n.type = 'car' group by u.uid
I end up with:
uid | name | totalNodes
| |
1 | bob | 4
3 | pam | 1
In other words, Jill is excluded. Why? And how can I avoid this? I.e. I want Jill to also appear in the list, but with totalNodes as 0 (or even NULL).
You need to perform your aggregate before attempting to join the tables as what you are currently doing is left joining, then restricting the data (at which point Jill is excluded) then grouping. If you do the count and restriction in a subquery you can then left join these results to the user table for the output you want:
SELECT u.uid, u.name, IFNULL(c.nodecount,0) AS `count`
FROM USER u LEFT JOIN (
SELECT uid, `type` , COUNT(nid) AS nodecount
FROM node
WHERE TYPE = 'car'
GROUP BY uid, type
) AS c ON u.uid = c.uid
use RIGHT JOIN instead left,
try :
select u.uid, u.name, count(nid) as totalNodes from USER as u
right join NODE on n.uid = u.uid where n.type IS NULL or n.type = 'car' group by n.uid
see this excellent post a visual explanation of joins :
http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html
mysql syntax of join :
http://dev.mysql.com/doc/refman/5.0/en/join.html