Mysql count joined fields - php

I have 3 tables: accounts, comments and followers
In account I track user info, in comments their comments and in followers, who are they following. I want to create a query where I get basic info of the account, a count of comments and a count of followers. I have a query and is not working:
SELECT
a.company,
a.firstname,
a.lastname,
a.title,
a.email,
a.zipcode,
a.created,
a.newsletter,
COUNT(c.id) comments,
COUNT(f.id) follows,
a.linkedinid
FROM accounts a
LEFT JOIN comments c ON a.id = c.user_id
LEFT JOIN followers f ON a.id = f.user_id
Any idea what am I doing wrong?

Think there are 2 issues.
Firstly you have no GROUP BY clause so it will bring back the counts for everything (one row returned), rather than one row with the counts per account. The account details would be from an undefined row.
Secondly you just COUNT(c.id) / COUNT(f.id). If an account has multiple follows and multiple comments then it will get every combination of those followers and accounts, so the counts would apply to the number of combinations. For example 2 comments and 3 followers would give 6 combinations, and both counts would be 6.
Fixing these by adding a GROUP BY and also adding DISTINCT to the COUNTs gives:-
SELECT
a.company,
a.firstname,
a.lastname,
a.title,
a.email,
a.zipcode,
a.created,
a.newsletter,
COUNT(DISTINCT c.id) comments,
COUNT(DISTINCT f.id) follows,
a.linkedinid
FROM accounts a
LEFT JOIN comments c ON a.id = c.user_id
LEFT JOIN followers f ON a.id = f.user_id
GROUP BY a.company,
a.firstname,
a.lastname,
a.title,
a.email,
a.zipcode,
a.created,
a.newsletter,
a.linkedinid
Note that in MySQL it is not necessary to GROUP BY all the non aggregate fields as long as the others are dependent on a field that in in the GROUP BY. However most other flavours of SQL will fail if you do this so to me it is best practice to put them all in the GROUP BY.

Try this
SELECT
a.company,
a.firstname,
a.lastname,
a.title,
a.email,
a.zipcode,
a.created,
a.newsletter,
COUNT(c.id) comments,
COUNT(f.id) follows,
a.linkedinid
FROM accounts a
LEFT JOIN comments c ON a.id = c.user_id
LEFT JOIN followers f ON a.id = f.user_id
GROUP BY f.user_id

Related

Postgresql query issue

so I have a problem that I'm trying to resolve since a couple of weeks, but I'm not coming to any solution. So here are the tables that I'm trying to make a query on:
Tables
I obviously joined them (easy):
SELECT a.date,b.title,b.author,u.nick
FROM book_add a
INNER JOIN user u on(a.user_fk=u.id)
INNER JOIN book b on (a.book_fk=b.id)
INNER JOIN status s ON(a.status_fk=s.id)
WHERE s.description='active';
Now here comes the problem: I want to order the rows by date desc and distinct them, so that the last inserted row (with the newest date) the first row is. But results are very odd once they get distincted. I tried this:
SELECT a.date,b.title,b.author,u.nick
FROM book_add a
INNER JOIN user u on(a.user_fk=u.id)
INNER JOIN book b on (a.book_fk=b.id)
INNER JOIN status s ON(a.status_fk=s.id)
WHERE s.description='active' ORDER BY a.date DESC;
this works, though once i try distinctig a.book_fk results are wrong:
SELECT DISTINCT ON(a.book_fk)a.book_fk,a.date,b.title,b.author,u.nick
FROM book_add a
INNER JOIN user u on(a.user_fk=u.id)
INNER JOIN book b on (a.book_fk=b.id)
INNER JOIN status s ON(a.status_fk=s.id)
WHERE s.description='active' ORDER BY a.date DESC;
I even tried approaches like this one, but without success:
SELECT * FROM (SELECT DISTINCT
ON(a.book_fk)a.book_fk,a.date,b.title,b.author,u.nick
FROM book_add a
INNER JOIN user u on(a.user_fk=u.id)
INNER JOIN book b on (a.book_fk=b.id)
INNER JOIN status s ON(a.status_fk=s.id)
WHERE s.description='active') res ORDER BY res.date DESC
Could someone help me? I would be very happy! Thank you!
It sounds like you're trying to get one row per book, with the most recent user/status? In that case this should work:
SELECT DISTINCT ON(a.book_fk)
a.book_fk, a.date, b.title, b.author, u.nick
FROM book_add a
INNER JOIN user u on(a.user_fk=u.id)
INNER JOIN book b on (a.book_fk=b.id)
INNER JOIN status s ON(a.status_fk=s.id)
WHERE s.description='active'
ORDER BY a.book_fk, a.date DESC
;

SQL: Selecting count of multiple tables

I don't think this will be too complicated to explain, but certainly complicated to get it working.
First of all, I have a couple of tables regarding users comments, one table for each section (forum, articles etc), as shown below:
site_users (id, username, ...) [Table that holds user's info]
site_articles_comments (id, user_id, comment, ...) [Where user_id = site_users.id]
site_forum_comments (id, user_id, comment, ...) [Same for site_articles_comments]
The thing is that every new row is a new comment and users can comment multiple times, which means that more rows are being added, thus making the need of sorting the number of rows to get the amount of comments in some sort of ranking system.
I was able to make a simple forum rank by doing this simple query:
SELECT u.id, u.username, COUNT(r.id) AS rank FROM site_users AS u LEFT
JOIN site_forum_comments AS r ON u.id = r.user_id GROUP BY u.username,
u.id ORDER BY rank DESC LIMIT :l
This query sorts all users from the database, where the user who has commented the most is always on top.
What I need, in the other hand, is to have a global ranking system, which sums the amount of comments in each section (articles, forum etc) and displays the users accordingly.
I was playing around with the sql to do that and the last thing I came up with was this huge query:
SELECT u.id, u.username, (COUNT(a.id) + COUNT(f.id)) AS rank FROM
site_users u LEFT JOIN site_articles_comments a ON a.user_id = u.id
LEFT JOIN site_forum_comments f ON f.user_id = u.id GROUP BY
u.username, u.id ORDER BY rank DESC LIMIT :l
This, however, returns null. What could I possibly do to achieve the result I want?
Thanks in advance,
Mateus
EDIT1: Sorry for the lack of information, this is regarding MySQL.
The problem is math with nulls, and ordering with nulls (check into the "NULLS LAST" option for overriding the default ordering which returns the nulls first for a descending order).
In your case, with the outer joins, if the user has a ton of article comments but no forum comments, well, 100 + null = null in Oracle math. So to get the math to work you need to make null=0. That's where NVL() comes in (and also has the nice side-effect of eliminating pesky nulls from your result set)!
SELECT u.id, u.username, (NVL(COUNT(a.id),0) + NVL(COUNT(f.id),0)) AS rank
FROM site_users u
LEFT JOIN site_articles_comments a ON a.user_id = u.id
LEFT JOIN site_forum_comments f ON f.user_id = u.id
GROUP BY u.username, u.id ORDER BY rank DESC LIMIT :l
I see you have both MySQL and Oracle in your tags - the above is for Oracle. If for MYSQL use COALESCE(COUNT(),0) instead.
try SELECT u.id, MIN(u.username) AS username, (COALESCE(COUNT(DISTINCT(a.id)),0) + COALESCE(COUNT(DISTINCT(f.id)),0)) AS rank
FROM site_users AS u
LEFT JOIN site_articles_comments AS a ON (a.user_id = u.id)
LEFT JOIN site_forum_comments AS f ON (f.user_id = u.id)
GROUP BY u.id
ORDER BY rank DESC
LIMIT :l

Getting columns with the same name from different Mysql tables

I have a table for comments ("event_comments") to different events with the following columns:
post_id
event_id
username
comment
date
I want to be able to retrieve this info from the database and also be able to print the username, first name and last name; for this, I thought of using INNER JOIN, but it is not working for the following reason: I have 3 different profile types (3 different tables) "students", "guardians", "teachers" and when I try to use the INNER JOIN using "username" I get an error message saying that Column 'username' in from clause is ambiguous.
SELECT event_comments.post_id, event_comments.event_id, event_comments.username, event_comments.comment, event_comments.date,
students.first_name, students.last_name, students.picture,
guardians.first_name, guardians.last_name, guardians.picture,
teachers.first_name, teachers.last_name, teachers.picture
FROM event_comments
INNER JOIN students
INNER JOIN guardians
INNER JOIN teachers
USING (username)
ORDER BY date DESC
LIMIT 20
I tried to do this and it worked, but it only shows 1 comment per user; if the user has more than 1 comment then the info is ignored:
SELECT event_comments.post_id, event_comments.event_id, event_comments.username, event_comments.comment, event_comments.date,
students.first_name, students.last_name, students.picture,
guardians.first_name, guardians.last_name, guardians.picture,
teachers.first_name, teachers.last_name, teachers.picture
FROM event_comments
INNER JOIN students
INNER JOIN guardians
INNER JOIN teachers
GROUP BY username
ORDER BY date DESC
LIMIT 20
Does anybody how to get the INNER JOINs to work? is there a better way to do what I want? I hope I explained myself well.
Thanks!
do it like this:
SELECT event_comments.post_id, event_comments.event_id, event_comments.username, event_comments.comment, event_comments.date,
students.first_name, students.last_name, students.picture,
guardians.first_name, guardians.last_name, guardians.picture,
teachers.first_name, teachers.last_name, teachers.picture
FROM event_comments
INNER JOIN students
on event_comments.username=students.username
INNER JOIN guardians
on event_comments.username=guardians.username
INNER JOIN teachers
on event_comments.username=teachers.username
ORDER BY date DESC
LIMIT 20
This will work but assuming that a username from one table is not present in other tables, this will result into 0 rows.
a more logical approach would be to select each table then union it to join every result set like this :
SELECT e.post_id, e.event_id, e.username, e.comment, e_comments.date,
s.first_name, s.last_name, s.picture
from event_comments e
inner join students s
on e.username=g.username
UNION SELECT e.post_id, e.event_id, e.username, e.comment, e_comments.date,
g.first_name, g.last_name, g.picture
from event_comments e
inner join guardians g
on e.username=g.username
UNION SELECT e.post_id, e.event_id, e.username, e.comment, e_comments.date,
t.first_name, t.last_name, t.picture
from event_comments e
inner join teacher t
on e.username=t.username
EDIT:
To explain better about the query it just does this simple steps:
Query all comments from students using username to join post to students
Query all comments from guardians using username to join post to guardians
Query all comments from teachers using username to join post to teachers
Join results from students,guardians, teachers together
You need the using clause for each pair of joins:
FROM event_comments INNER JOIN
students
USING (username) INNER JOIN
guardians
USING (username) INNER JOIN
teachers
USING (username)
In MySQL, an inner join with no on clause is treated as a cross join. In other databases, an on or using clause is required for an inner join.

MySQL Join/WHERE IN performance

I'm running the following query to select all posts liked by a user. The problem is, it takes quite a few seconds for the page to load.
SELECT p.*, a.username, a.avatar FROM user_posts p
LEFT JOIN account a ON p.uid=a.id WHERE p.pid in
(select post from user_posts_likes where `by`='$user_id')
ORDER BY `pid` DESC LIMIT $npage, 10";
Is there a better way to do this instead of using WHERE IN?
Thanks.
You can try two joins like this:
SELECT p.*, a.username, a.avatar FROM user_post_likes l
JOIN post p ON l.post = p.pid
LEFT JOIN account a ON p.uid = a.id
WHERE l.by = 555
I deducted the foreign key names from your original query so they might be wrong.
The 555 is an example user id, obviously.

How to get multiple counts from multiple tables?

On a webpage, I am displaying a number of picture collections (I show the thumbnails for each collection). Each picture has five relevant tables:
likes (id, user_id, picture_id),
views (id, user_id, picture_id),
comments (id, user_id, picture_id, comment),
pictures (id (which equals the "picture_id" in the previous tables), collection_id, picture_url and several other columns),
collections (id (equal to collection_id in previous table), and several other columns.
When loading my page, I need to aggregate the number of likes, views and comments for all pictures in each collection, so as to show those numbers under each collection.
So basically: count the likes for each picture, count them all up, display number. Count the views for each picture, count them all up, display number. Count the comments for each picture, count them all up, display number. And then rinse and repeat for all collections.
I'm pretty new at mysql, and I'm struggling between selects, multiple joins, counts, php vs mysql, etc etc. I'm sure there's many ways I can do this that would be very inefficient, so I'm hoping you can tell me the best/fastest/most efficient way to do this.
Thanks in advance!
You can solve this with selects and left joins.
Since you'll count entries on each table for every pictureId, your pictures table will be the left side of each relation. So:
select
p.id as pictureId,
count(distinct l.id) as count_likes,
count(distinct v.id) as count_views,
count(distinct c.id) as count_comments
from
pictures as p
left join likes as l on p.id = l.pictureId
left join views as v on p.id = v.pictureId
left join comments as c on p.id = c.pictureId
group by
p.id
Basically, you are counting every record in each table for each record in the pictures table; if there are no records in likes, views or comments, the count will be zero, respectively.
Of course, you can expand this idea for collections:
select
c.id as collection_id,
p.id as picture_id,
count(distinct l.id) as count_likes,
count(distinct v.id) as count_views,
count(distinct c.id) as count_comments
from
collections as c
left join pictures as p on c.id = p.collection_id
left join likes as l on p.id = l.picture_Id
left join views as v on p.id = v.picture_Id
left join comments as c on p.id = c.picture_Id
group by
c.id,
p.id
If you want to filter your results for each collection, you only need to add where c.id = aValue before the group by (where aValue is the collection Id you want to retrieve)
Hope this helps you.
If you only need the aggregate data for each collection:
select
c.id as collection_id,
count(distinct l.id) as count_likes,
count(distinct v.id) as count_views,
count(distinct c.id) as count_comments
from
collections as c
left join pictures as p on c.id = p.collection_id
left join likes as l on p.id = l.picture_Id
left join views as v on p.id = v.picture_Id
left join comments as c on p.id = c.picture_Id
group by
c.id
This should do the trick ;-)
You could do this with subselects:
SELECT
collections.*,
( SELECT COUNT(*) FROM pictures, likes
WHERE pictures.id = likes.picture_id
AND pictures.collection_id = collection.id
) AS like_count,
( SELECT COUNT(*) FROM pictures, views
WHERE pictures.id = views.picture_id
AND pictures.collection_id = collection.id
) AS view_count,
( SELECT COUNT(*) FROM pictures, comments
WHERE pictures.id = comments.picture_id
AND pictures.collection_id = collection.id
) AS comment_count
FROM collections
WHERE ...
This looks like it's going over the pictures table thrice, but I suspect that MySQL might be able to optimize that using the join buffer. I should note that I haven't actually tested this query, however. I also have no idea how this compares performance-wise with Barranka's LEFT JOIN solution. (Both would be pretty horrible if implemented naïvely, so it comes down to how smart MySQL's query optimizer is in each case.)

Categories