Joining tables and aggregating data in MySQL? - mysql

I have 2 tables. Members and their projects. I need to extract all the users, with the number of their projects, sorted by the number of projects.
Table: users:
id | username | email | password | reg_date
Table: projects:
id | title | descr | autor
For the join:
projects.autor = users.id

SELECT
users.id,
users.username,
COUNT(projects.id) AS `num_projects`
FROM
users
LEFT OUTER JOIN
projects
ON
projects.autor = users.id
GROUP BY
users.id
ORDER BY
num_projects DESC

SELECT u.id AS id, u.username AS username, u.email AS email, u.password AS password, u.reg_date AS reg_date, COUNT(p.id) AS projects_count
FROM users u
LEFT OUTER JOIN projects p ON p.autor = u.id
GROUP BY u.id
ORDER BY projects_count DESC

Related

MySQL Join 3 Tables with Count

I have the following tables:
users
id
username
first_name
email
students
id
user_id
payments
id
student_id
is_paid
date
And I have the following query:
SELECT users.username, users.first_name, users.email,
COUNT(payments.id) as pending_payments
FROM users
LEFT JOIN students ON users.id = students.user_id
LEFT JOIN payments ON payments.student_id = students.id AND payments.is_paid = 0
WHERE users.username LIKE 'testuser';
When the username exists and there are not pending payments, it returns:
| username | first_name | email | pending_payments |
| testuser | test | testuser#example.com | 0 |
When the username exists and there are 2 pending payments, it returns:
| username | first_name | email | pending_payments |
| testuser2| test2 | testuser#2example.com | 2 |
But when the username does not exists, it returns:
| username | first_name | email | pending_payments |
| NULL | NULL | NULL | 0 |
Instead, when the username does not exists, I want as result:
Empty set.
How can I modify my query to get the expected results?
(keep the behavior when user exists, but returns Empty set when does not exists, instead of a row will NULL values).
See SQLfiddle: http://sqlfiddle.com/#!9/aab424/11
EDIT
A working solution was provided:
http://sqlfiddle.com/#!9/aab424/72
Aggregate functions like COUNT(*) always generate one row if no GROUP BY clause is used. So use a GROUP BY clause, if you want an empty result:
SELECT users.username, users.first_name, users.email,
COUNT(payments.id) as pending_payments
FROM users
LEFT JOIN students ON users.id = students.user_id
LEFT JOIN payments ON payments.student_id = students.id AND payments.is_paid = 0
WHERE users.username LIKE 'testuser3'
GROUP BY users.username, users.first_name, users.email;
Note that you will get one row per user with that username. If username is not UNIQUE and you want to get one row for all users with the same username, you can use the HAVING clause instead of GROUP BY to "remove" the "empty" row.
SELECT users.username, COUNT(payments.id) as pending_payments
FROM users
LEFT JOIN students ON users.id = students.user_id
LEFT JOIN payments ON payments.student_id = students.id AND payments.is_paid = 0
WHERE users.username LIKE 'testuser3'
HAVING username IS NOT NULL;
SQLFiddle
This has nothing to do with the LEFT or any JOIN. It is just how aggregate functions work (in MySQL), and you will see the same behavior with a simple example without joins like
SELECT username, COUNT(*)
FROM users
WHERE username = 'testuser3';
| username | COUNT(*) |
|----------|----------|
| (null) | 0 |
Note that this query is not SQL standard compliant, and in strict mode you will get an error, because username is selected without beeng listed in the GROUP BY clause. (SQLFiddle).
The documentation says:
If you name columns to select in addition to the COUNT() value, a
GROUP BY clause should be present that names those same columns.
Otherwise, the following occurs:
If the ONLY_FULL_GROUP_BY SQL mode is enabled, an error occurs:
[...]
If ONLY_FULL_GROUP_BY is not enabled, the query is processed by
treating all rows as a single group, but the value selected for each
named column is indeterminate. The server is free to select the value
from any row:
[...]
Again: "The server is free to select the value from any row". But since there is no matching row, it returns NULL.
The fact that you receive "null" response is due to the LEFT JOIN statements. A Left join gets done, even if parameters do not match in that table, because they might make up columns in another (joined) table. To show only results that match criteria, use an INNER JOIN statement.
Also, you wish to COUNT(*) payments due by username, you should GROUP BY username I should think.
Try the following (untested, sorry, not in front of pc with editor on it)
SELECT u.username, u.first_name, u.email, COUNT(p.is_paid) as pending_payments
FROM users u
INNER JOIN students s ON u.id = s.user_id
INNER JOIN payments p ON s.user_id = p.student_id
WHERE u.username LIKE '%testuser%'
AND u.username IS NOT NULL
GROUP BY u.username
ORDER BY p.is_paid DESC, u.username ASC
Additionally, if you'd want to show only the results that have open pending payments you could add a HAVING statement after the GROUP BY, such as
HAVING u.pending_payments > 0
Tested with the following setup:
Below showing the query results. Second query does not have the HAVING statement.
I think the NULL values found for username etc. is mostly because of the missing group by in your queries.
I think it should work using:
SELECT users.username,
users.first_name,
users.email,
SUM(COALESCE( p.is_paid = 0, 0)) as pending_payments
FROM users
LEFT JOIN students s on s.user_id = users.id
LEFT JOIN payments p on p.student_id = s.id
WHERE users.username LIKE 'testuser3'
GROUP BY users.username,
users.first_name,
users.email;
And when leaving out the WHERE line, it will list for all the users:
SELECT users.username,
users.first_name,
users.email,
SUM(COALESCE( p.is_paid = 0, 0)) as pending_payments
FROM users
LEFT JOIN students s on s.user_id = users.id
LEFT JOIN payments p on p.student_id = s.id
GROUP BY users.username,
users.first_name,
users.email;
It will mention all users, even those who don't have any payment (pending or not) or users who are not listed in the Students table
You obviously has users with NULL username in users table. You can filter out the NULL value in the WHERE clause.
SELECT users.username, users.first_name, users.email,
COUNT(payments.is_paid = 0) as pending_payments
FROM users
LEFT JOIN students ON users.id = students.user_id
LEFT JOIN payments ON payments.student_id = students.id
WHERE users.username LIKE 'testuser' AND users.username IS NOT NULL;
Why cant use IS NOT NULL ?
SELECT users.username, users.first_name, users.email,
COUNT(payments.id) as pending_payments
FROM users
LEFT JOIN students ON users.id = students.user_id
LEFT JOIN payments ON payments.student_id = students.id AND payments.is_paid = 0
WHERE users.username LIKE 'testuser' and users.username is NOT NULL;

mysql join with two row into one

I have 2 tables table name is users and projects.the structure of table is:
user table
id | name | role
1 | samjad | user
2 | saneer | constructor
projects table
id | name | user_id | constructor_id |
1 | school | 1 | 2 |
How can i get all details from both table in a single row based on project table id.
i want to select
projectname username, constroctorname, user_id, constroctor_id
in a single row
You can join the user table twice - Once as users and then as constructors.
select p.name as projectname,
u.name as username,
c.name as contructorname,
p.user_id,
p.contructor_id
from projects p
left join user u on p.user_id = u.id
left join user c on p.contructor_id = c.id
where u.role = 'user' -- check if the said user has role "user"
and c.role = 'constructor'; -- check if the said constructor has role "constructor"
Do the fact you have two relation between project table and user (one for user and one for constroctor) You can use user joined for two time
select p.name, u1.username, u2.username, p.user_id, p.constroctor_id
from projects as p
inner join user as u1 on p.user_id = u1.id
inner join user as u2 on p.constroctor_id = u2.id
You can use concat() function:
SELECT CONCAT(field1, field2, field3);
https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat
or CONCAT_WS(separator,str1,str2,...)
Assuming there is a table called Constructor with columns name and constructor_id your query would be
select
p.name as projectname,
u.name as username,
c.name as constructorname,
u.id as userid,
c.id as constructorid
from
projects p
inner join user u on p.user_id=u.id
inner join constructor c on p.constructor_id=c.id
Use a join. You probably want an INNER JOIN:
SELECT * -- actually include only the fields you need
FROM Projects p
INNER JOIN Users u ON u.id = p.user_id
INNER JOIN Users uc ON uc.id = p.constructor_id
You'll want to join the tables on their keys. In this case something like the below:
select p.name as projectname
, u.name as username
, if(u.role='constructor',u.name,null) as constructorname
, p.user_id, p.constructor_id
from users u
join projects p
on p.user_id = u.id;

How to Select max date in this query?

I would like to write a query which retrieves name, id, and last modified date for each User. The below query gives the name, id, and last modified date from tables UserDetails1 and UserDetails2.
How could I modify this query to return a single date value, the max date for a given user_id in either of the details tables?
SELECT
id,
name,
MAX(userdetails1.date_modified),
MAX(userdetails2.date_modified)
FROM User user
INNER JOIN UserDetails1 userdetails1
ON userdetails1.user_id = user.id
INNER JOIN UserDetails2 userdetails2
ON userdetails2.user_id = user.id
User
id | name
---------
1 | name1
2 | name2
3 | name3
UserDetails1
user_id | date_modified
---------------------
1 | 2016-11-28 16:28:26
....
UserDetails2
user_id | date_modified
---------------------
1 | 2016-11-29 16:29:26
....
Try this, although I think there can be a more optimized way to write it.
SELECT
id,
name,
(CASE
WHEN MAX(userdetails1.date_modified) > MAX(userdetails2.date_modified)
THEN MAX(userdetails1.date_modified)
ELSE MAX(userdetails2.date_modified)
END)
FROM User user
INNER JOIN UserDetails1 userdetails1
ON userdetails1.user_id = user.id
INNER JOIN UserDetails2 userdetails2
ON userdetails2.user_id = user.id
GROUP BY id, name
One option is to UNION your two date tables together. This can be done before or after you JOIN. I personally would UNION before JOINING as it is simpler in my mind and to write.
Please excuse the SQL Server-esque syntax.
Before JOINing:
SELECT
u.id,
u.name,
MAX(d.date_modified) last_modified
FROM [User] u
INNER JOIN
(
SELECT user_id, date_modified
FROM UserDetails1
UNION ALL
SELECT user_id, date_modified
FROM UserDetails2
) d
ON u.id = d.user_id
GROUP BY u.id, u.name
After JOINing:
SELECT
id,
name,
max(date_modified) last_modified
FROM
(
SELECT
u.id, u.name, d.date_modified
FROM [User] u
INNER JOIN UserDetails1 d
ON d.user_id = user.id
UNION ALL
SELECT
u.id, u.name, d.date_modified
FROM [User] u
INNER JOIN UserDetails2 d
ON d.user_id = u.id
)
GROUP BY id, name

SQL join count(*) of one table to another table as aliases

I have two table, users and comments.
In the users table, there're columns id and username.
In the comments table I have user_id and his message.
And I wanted to create a table that select the username and his comment count when I search a particular username.
How do I write this?
my testing attempt:
SELECT COUNT(*) AS comment_count
FROM song_comments
RIGHT JOIN users
WHERE user_id = 7 AND comments.user_id = users.id
Try this:
SELECT U.Username, COUNT(SC.message) AS comment_count
FROM song_comments SC JOIN
users U ON U.id=SC.user_id
WHERE U.user_id = 7
GROUP BY U.Username
This gives you users and count
select u.username, count(c.user_id) as comment_count
from users u
join comments c on u.id = c.user_id
group by u.username
You can add a where to get one user's count
select u.username, count(c.user_id) as comment_count
from users u
join comments c on u.id = c.user_id
where u.username = 'Hogan'
group by u.username

Mysql queries issue

I have 3 tables in my mysql DB to query.
Users_rates (fields: id,userid,raterid,rate,eventid) containing all of the rates(rate) that have been assigned to users(userid), participating to specific events(eventid), by other users(raterid)
Events_participants (fields:id,userid,eventid) containing all of the users(userid) participating to each event(eventid)
Users (fields:id,name,lastname)containing all the user relative data
I need to query those three tables to retrieve an event-specific rank for the users' rates.
Ex. John,Erik and Mark participated to 'eventid=31'.
John received 1 rate from Mark, and 2 from Erik.
Mark received 1 rate from Erik.
Nobody has rated Erik though.
I need to retrieve for each user name,lastname and the sum of the rates received for eventid=31
I tried with this:
SELECT events_participants.userid,users.name,users.lastname,
(SELECT SUM(rate)FROM users_rates WHERE users_rates.eventid=31 AND users_rates.userid=events_participants.userid)AS rate
FROM(( events_participants INNER JOIN users ON events_participants.userid=users.id)
LEFT OUTER JOIN users_rates ON events_participants.userid=users_rates.userid )
WHERE events_participants.eventid=31
But I receive:
userid | name | lastname | rate
1 | luca | silvestro | 1
3 | claudio | buricchi | 6
3 | claudio | buricchi | 6
What's the right query?
Thanks
Luca
Try this:
SELECT users.userid, users.name, users.lastname, temp.sum as rate
FROM users LEFT JOIN (
SELECT userid, SUM(rate) as sum FROM users_rates WHERE eventid = 31 GROUP BY userid
) as temp USING (userid)
It might give an error, this might work instead:
SELECT users.userid, users.name, users.lastname, temp.sum as rate
FROM users, (
SELECT userid, SUM(rate) as sum FROM users_rates WHERE eventid = 31 GROUP BY userid
) as temp WHERE users.userid = temp.userid
I don't know if I got the problem right, but maybe something like:
SELECT u.id, u.name, u.lastname, SUM(ur.rate) AS rate
FROM users AS u
INNER JOIN users_rates AS ur ON ur.userid = u.id
WHERE ur.eventid = 31
GROUP BY u.id
edit: If you want to receive a list with all users regardless of whether they have any rates at all, you could also join the users_participants table and replace the INNER JOIN of users_rates by a LEFT JOIN. The WHERE clause has to reference events_participants then (not users_rates anymore as it could be NULL):
SELECT u.id, u.name, u.lastname, SUM(ur.rate) AS rate
FROM users AS u
INNER JOIN events_participants AS ep ON ep.userid = u.id
LEFT JOIN users_rates AS ur ON ur.userid = u.id AND ur.eventid = ep.eventid
WHERE ep.eventid = 31
GROUP BY u.id