MySQL Query Multiple LEFT Joins problem - php

I am trying to get the required result from the following query but it doesnt seem to work...
SELECT DISTINCT
u.user_name as user_name,
u.total_points as total_points,
u.user_id as user_id,
COUNT(a.id) as user_total_articles_published,
COUNT(r.id) as user_total_replies_published,
COUNT(v.id) as user_total_votes_done
FROM users as u
LEFT JOIN articles as a ON u.user_id=a.user_id
LEFT JOIN replies as r ON u.user_id=r.user_id
LEFT JOIN votes as v ON u.user_id=v.user_id
GROUP BY u.user_id
ORDER BY u.total_points DESC
LIMIT 10
If i remove the last 2 LEFT jOINS the query will work... whats wrong with the other 2? Do i have to use another method for this to work?
thanks

I think by 'not working' you mean that the query returns too many records? That is because of the combination of joins. You return each reply for each article record, so the numbers are multiplied. You can solve this by using DISTINCT in the COUNT. That way, you count the unique id's, so you count each article only once:
COUNT(distinct a.id) as user_total_articles_published,
COUNT(distinct r.id) as user_total_replies_published,
COUNT(distinct v.id) as user_total_votes_done
[edit]
A possibly faster solution, eliminating the need for DISTINCT and GROUP BY:
SELECT
u.user_name as user_name,
u.total_points as total_points,
u.user_id as user_id,
(SELECT COUNT(a.id) FROM articles a
WHERE a.user_id = u.user_id) as user_total_articles_published,
(SELECT COUNT(r.id) FROM replies r
WHERE r.user_id = u.user_id) as user_total_replies_published,
(SELECT COUNT(v.id) FROM votes v
WHERE v.user_id = u.user_id) as user_total_votes_done
FROM users as u
ORDER BY u.total_points DESC
LIMIT 10

Related

Resolve data for 2 tables

Let's say, I have 3 tables. "users", "topics" and "replies.
The "topics" and "replies" table do have a column "user_id" which references to "id" on the "users" table. The "replies" table also have a column "topic_id" which references "id" on "topics". Also imagine that each topic has 1 reply.
Now, I want to fetch all topics and resolve the username for both tables, so the output should include a "topic_username" and a "reply_username".
I know how to select the username for the topic, but how can I do that for both the topics and replies table?
Thanks
Updated code:
SELECT
t.*,
t_users.username as topic_user,
last_reply.user_id as last_reply_user_id,
r_users.username as last_reply_username,
last_reply.replies_count,
ORDER BY IFNULL(last_reply.created_at,t.created_at DESC
FROM
topics as t
left join users as t_users on t_users.id = t.user_id
left join ( select r.*,count(r.id) as replies_count from (select * from replies order by id desc) as r group by r.topic_id ) as last_reply on last_reply.topic_id = t.id
left join users as r_users on r_users.id = last_reply.user_id
Try this
SELECT
t.*,
t_users.name as topic_user,
r_users.name as reply_user
FROM
topics as t
left join replies as r on r.topic_id=t.id
left join users as t_users on t_users.id = t.user_id
left join users as r_users on r_users.id = r.user_id
this would work well if there is 1:1 topic and reply, if you have 1 to many replies then you are better off group by topic id and get the usernames by group concat
Version 2 (to get reply count and last reply user)
SELECT
t.*,
t_users.name as topic_user,
last_reply.user_id as last_reply_user_id,
r_users.name as last_reply_username,
last_reply.replies_count,
IFNULL(last_reply.created_at,t.created_at) as last_update
FROM
topics as t
left join users as t_users on t_users.id = t.user_id
left join ( select r.*,count(r.id) as replies_count from (select * from replies order by id desc) as r group by r.topic_id ) as last_reply on last_reply.topic_id = t.id
left join users as r_users on r_users.id = last_reply.user_id
ORDER BY IFNULL(last_reply.created_at,t.created_at) DESC
You wanna do this in a single query? You can do it like this but I believe a separate query for fetching replies and topics might be better (fetch replies first, get the unique topic IDs and then fetch topics in a separate query)
SELECT r.id, r.topic_id, ru.username AS reply_username, tu.username AS topic_username
FROM replies AS r
INNER JOIN topics AS t ON (t.id = r.topic_id)
INNER JOIN users AS ru ON (r.user_id = u.id)
INNER JOIN users AS tu ON (t.user_id = u.id)

MySQL Using SUM with multiple joins

I have a projects table and a tasks table I want to do a query that gets all projects and the sum of the time_spent columns grouped by project id. So essentially list all projects and get the total of all the time_spent columns in the tasks table belonging to that project.
With the query posted below I get the latest added time_spent column and not the sum of all the columns.. :S
Below is the query I have at the moment:
SELECT `projects`.`id`, `projects`.`description`, `projects`.`created`,
`users`.`title`, `users`.`firstname`, `users`.`lastname`, `users2`.`title`
as assignee_title, `users2`.`firstname` as assignee_firstname,
`users2`.`lastname` as assignee_lastname,
(select sum(tasks2.time_spent)
from tasks tasks2
where tasks2.id = tasks.id)
as project_duration
FROM (`projects`)
LEFT JOIN `users`
ON `users`.`id` = `projects`.`user_id`
LEFT JOIN `users` as users2
ON `users2`.`id` = `projects`.`assignee_id`
LEFT JOIN `tasks` ON `tasks`.`project_id` = `projects`.`id`
GROUP BY `projects`.`id`
ORDER BY `projects`.`created` DESC
Below is my projects table:
Below is my tasks table:
Thanks in advance!
Usually this query will help you.
SELECT p.*, (SELECT SUM(t.time_spent) FROM tasks as t WHERE t.project_id = p.id) as project_fulltime FROM projects as p
In your question, you don't say about users. Do you need users?
You are on right way, maybe your JOINs can't fetch all data.
This query should do it for you.
Note, whenever you do a group by you must include every column that you select from or order by. Some MySql installations don't prevent you from doing this, but in the end it results in an incorrect result set.
As well you should never do a query as part of your SELECT statement, known as a sub-query, as it will result in an equal amount of additional queries in relation to the number of rows returned. So if you got 1,000 rows back, it would result in 1,001 queries instead of 1 query.
SELECT
p.id,
p.description,
p.created,
u.title,
u.firstname,
u.lastname,
a.title assignee_title,
a.firstname assignee_firstname,
a.lastname assignee_lastname,
SUM(t.time_spent) project_duration
FROM
projects p
LEFT JOIN
users u ON
u.id = p.user_id
LEFT JOIN
users a ON
a.id = u.assignee_id
LEFT JOIN
tasks t ON
t.project_id = p.id
GROUP BY
p.id,
p.description,
p.created,
u.title,
u.firstname,
u.lastname,
a.title,
a.firstname,
a.lastname
ORDER BY
p.created DESC

return a total count of counts in prepared statement

The following statement gets row counts for user_ids from various tables/conditions where the users are within specific computers of a specific account. It works as expected. An example output would be something like this :
Array
(
[0] => Array
(
[computer_name] => COMPUTER_1
[username] => Steve
[t1count] => 13
[t2count] =>
[t3count] => 23
[t4count] => 64
)
... and so on for each
the statement :
$stmt = $db->prepare("
SELECT c.computer_name, users.username, t1count, t2count, t3count, t4count
FROM
( SELECT account_id, computer_id, computer_name
FROM computers
WHERE account_id = ".$_SESSION['user']['account_id']."
ORDER BY computer_id ASC LIMIT 0, ".$_SESSION['user']['licenses']."
) as c
LEFT JOIN users
on users.computer_id = c.computer_id
LEFT JOIN
(SELECT user_id, COUNT(user_id) as t1count
FROM t1
WHERE t1.title LIKE 'started'
GROUP BY user_id) as t_t1
on t_t1.user_id = users.user_id
LEFT JOIN
(SELECT user_id, COUNT(user_id) as t2count
FROM t2
GROUP BY user_id) as t_t2
on t_t2.user_id = users.user_id
LEFT JOIN
(SELECT user_id, COUNT(user_id) as t3count
FROM t1
WHERE t1.title LIKE 'blocked'
GROUP BY user_id) as t_t3
on t_t3.user_id = users.user_id
LEFT JOIN
(SELECT user_id, COUNT(user_id) as t4count
FROM t1
WHERE t1.title LIKE 'closed'
GROUP BY user_id) as t_t4
on t_t4.user_id = users.user_id
... and so on for each
WHERE c.account_id = ?
");
I want to also return a totalsum = t1count + t2count + t3count + t4count in this statement as well, but can't seem to get anything working. In this situation I cannot do outside processing (adding the values that are returned)... it needs to be in the statement. I am also open to any suggestions in what I already have if there are better options.
What are you trying to do with these values? SUM(t1count,t2count,t3count,t4count) AS totalsum should work to get a count of counts.
Your query is a bit hard to read. You have implicit and explicit JOINs. You are grouping numerous times and pulling columns when you can generally do this all in one shot. If you share an SQLFiddle, I will be able to clean this up, but in short:
SELECT
c.computer_name,
users.username,
count(t_t1.user_id) AS t1count,
count(t_t2.user_id) AS t2count,
count(t_t3.user_id) AS t3count,
count(t_t4.user_id) AS t4count,
(count(t_t1.user_id)+count(t_t2.user_id)+count(t_t3.user_id)+count(t_t4.user_id)) AS totalsum FROM users
LEFT JOIN computers AS c ON users.computer_id=c.computer_id AND c.account_id=?
LEFT JOIN t1 AS t_t1 ON t_t1.user_id = users.user_id AND t_t1.title LIKE "started"
LEFT JOIN t2 AS t_t2 ON t_t2.user_id = users.user_id
LEFT JOIN t1 AS t_t3 ON t_t3.user_id = users.user_id AND t_t3.title LIKE 'blocked'
LEFT JOIN t1 AS t_t4 ON t_t4.user_id = users.user_id AND t_t4.title LIKE 'closed'
WHERE c.account_id = ?
AND t_t1.title LIKE started
GROUP BY users.user_id;
This may need tweaking, as I stated, but it is a lot cleaner and easier to read and should accomplish something very similar.
Alternately, if you can't get the query to run the way you want it to when you change it to entirely explicit joins, rather than using SUM, try adding the values together like I did in the example above. It should prevent them from aggregating in the same way.
EDIT
After viewing your SQLFiddle, I have doctored up a solution which does away with nested queries. The positive is that it is cleaner. The negative is that it requires you to specify the users using an IN clause.
SELECT computers.account_id,computers.computer_id,computers.computer_name,users.user_id,users.username,count(distinct t_count1.log_id) AS count1,count(distinct t_count2.log_id) AS count2,count(distinct t_count3.log_id) AS count3, count(distinct t_count4.event_id) AS count4,
(count(distinct t_count1.log_id) + count(distinct t_count2.log_id) + count(distinct t_count3.log_id) + count(distinct t_count4.event_id)) AS totalcount
FROM users
INNER JOIN computers ON computers.computer_id=users.computer_id
LEFT JOIN logs AS t_count1 ON t_count1.type LIKE 'type1' AND t_count1.user_id=users.user_id
LEFT JOIN logs AS t_count2 ON t_count2.type LIKE 'type2' AND t_count2.user_id=users.user_id
LEFT JOIN logs AS t_count3 ON t_count3.type LIKE 'type3' AND t_count3.user_id=users.user_id
LEFT JOIN events AS t_count4 ON t_count4.user_id = users.user_id
WHERE computers.account_id=1 AND computers.computer_id in (1,2)
GROUP BY users.user_id
ORDER BY users.user_id ASC,computers.computer_id ASC;
If you choose to keep your current query structure for any reason, adapting it like so should make it work for you:
select *,ifnull(count1,0)+ifnull(count2,0)+ifnull(count3,0)+ifnull(count4,0) AS totalcount from
( select account_id, computer_id, computer_name
from computers
order by computer_id asc limit 0, 2
) as c
left join users
on users.computer_id = c.computer_id
left join
(select user_id, count(user_id) as count1
from logs
where logs.type like 'type1'
group by user_id) as t_count1
on t_count1.user_id = users.user_id
left join
(select user_id, ifnull(count(user_id),0) as count2
from logs
where logs.type like 'type2'
group by user_id) as t_count2
on t_count2.user_id = users.user_id
left join
(select user_id, count(user_id) as count3
from logs
where logs.type like 'type3'
group by user_id) as t_count3
on t_count3.user_id = users.user_id
left join
(select user_id, count(user_id) as count4
from events
group by user_id) as t_count4
on t_count4.user_id = users.user_id
where c.account_id = 1;
My advice would be to follow along each query to understand what you are asking SQL to do. To actually add all values together, you should be counting the number of records that are returned. Counting values of your primary keys helps you to count records. Also, using your other example, ifnull in the second example make sure that null values are not going to interfere with adding. "If a value is null make it 0 instead."

MySQL query to order results by number of votes in another table for the specifc result/row

Okay, so I have a query below I am trying to get to work.
Basically everything works up until the inner join of the 'votes' table. What I am trying to do is order the results of this query in accordance with the number of votes each content row has in another table called votes. I know I'm not too far off from what I need to do!
Thanks in advance!!
mysql_query("
SELECT content.id, content.type, content.title, content.url, users.username
FROM content
INNER JOIN users ON content.uploaderuid = users.id
INNER JOIN votes ON votes.id = content.id
WHERE (content.type = 'pic')
ORDER BY COUNT(votes.id) DESC");
Try doing:
SELECT content.id, content.type, content.title, content.url, users.username
FROM content
INNER JOIN users ON content.uploaderuid = users.id
INNER JOIN (
SELECT id,COUNT(*) as voteCount
FROM votes
GROUP BY id
) v ON v.id = content.id
WHERE (content.type = 'pic')
ORDER BY v.voteCount DESC
When you do an INNER JOIN with votes table directly, if you have multiple occurrences of the same id, you will get a lot more rows than before you did the JOIN.
If you are only interested in the number of votes for each id, by doing a JOIN with a subquery that calculates the count of votes for each id, will leave your previous query results as they were, and lets you use the voteCount to order by it.
You could try this:
SELECT c.id, c.type, c.title, c.url, u.username, COUNT(v.id) votes
FROM content c
INNER JOIN users u ON c.uploaderuid = u.id
INNER JOIN votes v ON v.id = c.id
WHERE c.type = 'pic'
GROUP BY c.id, c.type, c.title, c.url, u.username
ORDER BY votes DESC
Here is the SQL Fiddle that demonstrates the below query:
SELECT c.id, c.type, c.title, c.url, u.username, COUNT(v.id)
FROM content AS c
INNER JOIN users AS u ON c.uploaderuid = u.id
INNER JOIN votes AS v ON v.id = c.id
WHERE c.type = 'pic'
GROUP BY c.id, c.type, c.title, c.url, u.username
ORDER BY COUNT(v.id) DESC

PHP/MySQL: I think I need to use a JOIN here, but I don't know how

This is my query:
SELECT messages.id AS m_id, messages.user_id AS m_uid, messages.project_id AS m_pid, messages.date_created AS m_dc, messages.type AS m_type, messages.file_url AS m_fu, messages.message_text AS m_text, messages.deleted AS m_del,
projects.id AS p_id, projects.name AS p_name, projects.company_id AS p_cid,
users.id AS u_id, users.name AS u_name
FROM messages, projects, users
HAVING `m_pid` = '$project_id' AND m_uid = u_id
ORDER BY `m_dc` DESC
I've been using HAVING instead of WHERE because WHERE doesn't seem to be working with the AS keywords I'm using. The result is that my query is returning more results than I need. I think I'm supposed to be using JOINs here, but I can't really get a grasp on them. Please help!
:) Thanks
It's true, you should use JOIN instead of the FROM table1, table2 syntax.
To answer your issue more directly: the WHERE clause operates on the columns in the tables - the HAVING clause operates on your result set (which includes your aliases).
How do your tables relate to each other? That is the question you need to answer first - once you have that answer, you put it pretty much directly into the ON clause of the JOIN.
SELECT messages.id AS m_id, messages.user_id AS m_uid, messages.project_id AS m_pid, messages.date_created AS m_dc, messages.type AS m_type, messages.file_url AS m_fu, messages.message_text AS m_text, messages.deleted AS m_del, projects.id AS p_id, projects.name AS p_name, projects.company_id AS p_cid,
users.id AS u_id, users.name AS u_name
FROM messages
JOIN users ON messages.user_id = user.id
JOIN projects ON projects.user_id = user.id # this is just a guess
WHERE messages.id = $project_id
ORDER BY `m_dc` DESC
To get a better idea of how JOINs relate tables together, you might want to check out this handy page showing JOINs as Venn diagrams.
SELECT
messages.id AS m_id, messages.user_id AS m_uid, messages.project_id AS m_pid,
messages.date_created AS m_dc, messages.type AS m_type, messages.file_url AS m_fu,
messages.message_text AS m_text, messages.deleted AS m_del,
projects.id AS p_id, projects.name AS p_name, projects.company_id AS p_cid,
users.id AS u_id, users.name AS u_name
FROM
messages
JOIN
users ON messages.user_id = users.id
JOIN
projects ON projects.id=messages.project_id
WHERE
`m_pid` = '$project_id'
ORDER BY
`m_dc` DESC
Try this?
SELECT m.id AS m_id,
m.user_id AS m_uid,
m.project_id AS m_pid,
m.date_created AS m_dc,
m.type AS m_type,
m.file_url AS m_fu,
m.message_text AS m_text,
m.deleted AS m_del,
p.id AS p_id,
p.name AS p_name,
p.company_id AS p_cid,
u.id AS u_id,
u.name AS u_name
FROM messages as m
INNER JOIN projects as p on p.id = m.project_id
INNER JOIN users as u ON u.id = m.user_id
WHERE m.project_id = '$project_id'
ORDER BY ....

Categories