Multiple counts with multiple joins - mysql

I'm working on a site with images and tags on the images (à la Facebook). Images are in albums.
The important bits of the DB structure are as follows
user: id INT
album: id INT, user_id INT
photo: id INT, album_id INT
tag: id INT, photo_id
I'm trying to get a call working that'll return, given a user_id, the album id, the total number of photos in that album, and the total tags in that album.
It's working fine to get either the total photos or the total tags, but not both. In that case, it returns the total number of tags twice.
The following is my SQL call:
SELECT album.id, COUNT(photo.id), COUNT(tag.id)
FROM album
LEFT OUTER JOIN photo ON (album.id = photo.album_id)
LEFT OUTER JOIN tag ON (photo.id = tag.photo_id)
WHERE album.user_id = 123 GROUP BY album.id
ORDER BY album.id DESC LIMIT 0,25
Any ideas how I could do this better?

You could add a DISTINCT to the count
ie:
COUNT(DISTINCT photo.id),
COUNT(DISTINCT tag.id)

SELECT
album.id AS album_id,
COUNT(DISTINCT photo.id) AS count_photos,
COUNT(DISTINCT tag,id) AS count_tags
FROM album
LEFT JOIN photo ON album.id=photo.album_id
LEFT JOIN photo ON photo.id=tag.photo_id
WHERE album.user_id = 123
GROUP BY album.id

The problem that you have is because you are joining along two different dimensions, photos and tags. Although COUNT(DISTINCT) works for counts, you might want to aggregate other information as well.
The more general approach is to separate the results into subqueries:
SELECT a.id, NumPhotos, NumTags
FROM (select album.id, count(*) as NumPhotos
from album LEFT OUTER JOIN
photo
ON (album.id = photo.album_id)
) a LEFT OUTER JOIN
(select album.id, count(*) as NumTags
from album LEFT OUTER JOIN
photo
ON (album.id = photo.album_id) LEFT OUTER JOIN
tag
ON (photo.id = tag.photo_id)
) b
on a.id = b.id
WHERE a.user_id = 123
ORDER BY a.id DESC
LIMIT 0,25

Related

SQL: INNER JOIN SELECT makes the whole query return nothing

There are 3 tables, named as account_has_account1, account_has_photos, photos_has_message_photos where account_has_account1 have columns account_id, account_id1, status, type_id and this table takes care on storing accounts following to another account
account_has_photos stores information about all the photos one account has uploaded, it's columns are photos_id, account_id, type_id, this also stores likes according to type_id
photos_has_message_photos stores all messages posted to a photo, its a relational table from photos and message_photos
i need to fetch a count of all likes from account_has_photos where type_id = 1 which points to like from table type
i have done this SQL:
SELECT account_has_photos.photos_id as id, "photos" as type, account_has_photos.update_at, account_has_photos.account_id, posts.total as total_messages, likes.total as total_likes
FROM account_has_account1
INNER JOIN account_has_photos
ON (account_has_photos.account_id = account_has_account1.account_id1 AND account_has_photos.type_id = 17)
INNER JOIN (
SELECT photos_has_message_photos.photos_id, count(*) as total
FROM photos_has_message_photos
GROUP BY photos_has_message_photos.photos_id
) posts
ON(posts.photos_id = account_has_photos.photos_id)
INNER JOIN (
SELECT account_has_photos.photos_id, COUNT(account_has_photos.photos_id) as total
FROM account_has_photos
WHERE account_has_photos.type_id = 1
) likes
ON (likes.photos_id = account_has_photos.photos_id)
WHERE account_has_account1.account_id = 7 AND account_has_account1.`status` = "Active"
the first INNER JOIN account_has_account1 is for showing all accounts that one account is following, The second INNER JOIN photos_has_message_photos is only for getting the count of all posted messages from a account's photos.
At this point everything goes fine, but when i insert the third INNER JOIN account_has_photos again, the query result is now 0, the purpose of this third JOIN is for getting a count of likes a photo has which is stored in account_has_photos where type_id = 1
The rest is for setting the general conditions for the search.
Again the problem only happens in this query
INNER JOIN (
SELECT account_has_photos.photos_id, COUNT(account_has_photos.photos_id) as total
FROM account_has_photos
) likes
ON (likes.photos_id = account_has_photos.photos_id)
it could be that no likes are found on any photo, i have made the test for searching it separately and as i said, there is no like made on any photo, i didn't add any record because i want it to say 0 on count as it is going to be shown alot
here is much different way to write your query that should yeild the same results.
SELECT
account_has_photos.photos_id as id
,"photos" as type
,account_has_photos.update_at
,account_has_photos.account_id
,COUNT(photos_has_messages.photos_id) as total_messages
,COUNT(DISTINCT likes.photos_id) as total_likes
FROM
account_has_account1
INNER JOIN account_has_photos
ON account_has_photos.account_id = account_has_account1.account_id1
AND account_has_photos.type_id = 17
LEFT JOIN photos_has_message_photos
ON photos_has_message_photos.photos_id = account_has_photos.photos_id
LEFT JOIN account_has_photos likes
ON likes.photo_id = account_has_photos.photo_id
AND likes.type_id = 1
WHERE account_has_account1.account_id = 7 AND account_has_account1.`status` = "Active"
GROUP BY
account_has_photos.photos_id
,"photos"
,account_has_photos.update_at
,account_has_photos.account_id
I would recommend changing:
,COUNT(photos_has_messages.photos_id) as total_messages
to
,COUNT(DISTINCT photos_has_messages.WhateverTablesUniqueIdIs) as total_messages
Also this line
,COUNT(DISTINCT likes.photos_id) as total_likes
will always give you 1. so if likes does repeat photo_id then you also want to count whatever that account_has_photos unique identifier is....
Your last subquery is missing a GROUP BY. Try this:
INNER JOIN (
SELECT account_has_photos.photos_id, COUNT(account_has_photos.photos_id) as total
FROM account_has_photos
GROUP BY account_has_photos.photos_id
) likes
ON likes.photos_id = account_has_photos.photos_id
You may also want to replace the INNER JOINs with LEFT OUTER JOIN if you want rows with no likes.

Selecting the count of rows on second table based on foreign key in MySQL

So I have this table of albums and images. Images have a column named album_id which takes the album ID they are sitting on. So far so good.
Now I'm in need to select information from the Albums table and I also need the count of images for each album row. I tried using a LEFT JOIN and a COUNT(*), but it would return only one row, which is highly inefficient for my case.
This is the original query I'm using which wont return anything count related:
SELECT album_id, album_name, album_preview, album_owner, album_time, album_access
FROM imgzer_albums WHERE album_owner = SOME_VALUE
And this is the query with the LEFT JOIN:
SELECT a.album_id, a.album_name, a.album_preview, a.album_owner, a.album_time, a.album_access, COUNT(i.*) AS images
FROM imgzer_albums a
LEFT JOIN imgzer_images i
ON a.album_id = i.album_id
WHERE album_owner = SOME_VALUE
How do I get the images count for each corresponding album ID without being limited to one result only?
Try This
SELECT a.album_id, a.album_name, a.album_preview, a.album_owner, a.album_time, a.album_access, COUNT(i.*) AS images
FROM imgzer_albums a
LEFT JOIN imgzer_images i
ON a.album_id = i.album_id
WHERE album_owner = SOME_VALUE
GROUP BY a.album_id
a solution without grouping is a correlated query:
SELECT a.album_id, a.album_name, a.album_preview, a.album_owner, a.album_time, a.album_access,
(select COUNT(*) from imgzer_images i where a.album_id = i.album_id) AS images
FROM imgzer_albums a
WHERE album_owner = SOME_VALUE

Inner join statement with limit in one of three tables

I'm trying to get data from three tables (photos, albums, album_photos),
then the program searches a user's albums in the album table, then look for every album the ID's of the photos in album_photos, and then, for each ID, look at the photos table all data by ID.
Yesterday I asked something like this: Inner join with 3 tables, but now, I think the question is different, I'm wondering how I can add a limit to a request by inner join.
So, I'm working now in this code:
SELECT a.album_name, a.album_id, c.*
FROM albums a
INNER JOIN album_photos b ON a.album_id = b.album_id
INNER JOIN photos c ON b.photo_id = c.photo_id
WHERE (
SELECT COUNT(*)
FROM album_photos d
WHERE b.album_id = d.album_id
AND d.nick = :nick
) <=5
Ok, this code select's the albums that have 5 or less photos. I do not want the code to do that, no matter how many photos have the album, I want to show the album with a LIMIT OF 5 photos.
Other people have told me that you can not do it, I believe that this is not so, because the SQL language is very complex and I think we should have the tool to do it.
Is there any way to do this in a proper way?
*In the link that I'm shared above I put an example about the output data.
Try changing the where clause to this:
WHERE (
SELECT COUNT(*)
FROM album_photos d
WHERE d.album_id = b.album_id and
d.photo_id <= b.photo_id
AND d.nick = :nick
) <= 5
This counts the number of photos in order, not just the number of photos in the album.
Since album_photos has a mapping relationship between photos and albumns, you can specify the number of photos to join on by using TOP:
SELECT a.album_name, a.album_id, p.*
FROM albums a
INNER JOIN album_photos ap ON
ap.photo_id = (select top 5 photo_id from album_photos where a.album_id = ap.album_id order by photo_id)
INNER JOIN photos p ON ap.photo_id = p.photo_id
The Order by photo_id in the subquery will ensure the same 5 (or fewer) photos are returned
EDIT PER COMMENT. Modifying to use MySql LIMIT instead of T-SQL TOP
SELECT a.album_name, a.album_id, p.*
FROM albums a
INNER JOIN album_photos ap ON
ap.photo_id = (select photo_id from album_photos where a.album_id = ap.album_id order by photo_id LIMIT 0, 5)
INNER JOIN photos p ON ap.photo_id = p.photo_id

mysql advanced group query

I need some help figuring out a query
I have 3 tables
sources
id, name, rank
origin
id, source_id (FK to sources id), name
One source can have many origins
product
id, origin_id (FK to origin id), name, time_added
One origin can have many products
Now, what I want is to select the most recent products per source, ordered by rank descending
Any suggestions?
This should do as you have requested, though without sample output it's hard to be 100% certain. Inner query selects products linked to the source id ordered by the date added from newest to oldest, and in turn that's joined to sources and grouped.
SELECT
*
FROM sources AS s
INNER JOIN (
SELECT
origins.source_id,
product.*
FROM origin
INNER JOIN product
ON product.origin_id = origin.origin_id
ORDER BY time_added DESC
) AS productsOrdered
ON productsOrdered.source_id = sources.source_id
ORDER BY s.rank DESC, productsOrdered.time_added DESC
This avoids having to do potentially expensive opreations as the inner select should be pretty fast and can be limited as required
A typical way of doing this is to
Find the MAX(time_added) for each origin
Get the product's id for each of these origins
Join with the sources and origin tables to retrieve all columns
Note that this fails if there are origins with multiple records with the exact same time_added.
SQL Statement
SELECT *
FROM sources s
INNER JOIN origin o ON o.source_id = s.id
INNER JOIN product p ON p.origin_id = o.id
INNER JOIN (
SELECT id
FROM product p
INNER JOIN (
SELECT origin_id
, MAX(time_added) AS time_addded
FROM product p
GROUP BY
origin_id
) pmax ON pmax.origin_id = p.origin_id
AND pmax.time_added = p.time_added
) pmax ON pmax.id = p.id
SELECT o.id,count(o.id) as numOfProdFromOrig p.id, p.name, p.time_added, s.rank
FROM product as p NATURAL JOIN sources as s NATURAL JOIN origin as o
GROUP BY (numOfProdFromOrig)
ORDER BY s.rank DESC
select b.id,(select p.name from origin o inner join product p
on p.origin_id = o.id where o.source_id = b.id order by time_added desc limit 1)a as product_name
from source b ;
Try this:

How can I use MySQL to COUNT with a LEFT JOIN?

How can I use MySQL to count with a LEFT JOIN?
I have two tables, sometimes the Ratings table does not have ratings for a photo so I thought LEFT JOIN is needed but I also have a COUNT statement..
Photos
id name src
1 car bmw.jpg
2 bike baracuda.jpg
Loves (picid is foreign key with photos id)
id picid ratersip
4 1 81.0.0.0
6 1 84.0.0.0
7 2 81.0.0.0
Here the user can only rate one image with their IP.
I want to combine the two tables in order of the highest rating. New table
Combined
id name src picid
1 car bmw.jpg 1
2 bike baracuda.jpg 2
(bmw is highest rated)
My MySQL code:
SELECT * FROM photos
LEFT JOIN ON photos.id=loves.picid
ORDER BY COUNT (picid);
My PHP Code: (UPDATED AND ADDED - Working Example...)
$sqlcount = "SELECT p . *
FROM `pics` p
LEFT JOIN (
SELECT `loves`.`picid`, count( 1 ) AS piccount
FROM `loves`
GROUP BY `loves`.`picid`
)l ON p.`id` = l.`picid`
ORDER BY coalesce( l.piccount, 0 ) DESC";
$pics = mysql_query($sqlcount);
MySQL allows you to group by just the id column:
select
p.*
from
photos p
left join loves l on
p.id = l.picid
group by
p.id
order by
count(l.picid)
That being said, I know MySQL is really bad at group by, so you can try putting the loves count in a subquery in your join to optimize it:
select
p.*
from
photos p
left join (select picid, count(1) as piccount from loves group by picid) l on
p.id = l.picid
order by
coalesce(l.piccount, 0)
I don't have a MySQL instance to test out which is faster, so test them both.
You need to use subqueries:
SELECT id, name, src FROM (
SELECT photos.id, photos.name, photos.src, count(*) as the_count
FROM photos
LEFT JOIN ON photos.id=loves.picid
GROUP BY photos.id
) t
ORDER BY the_count
select
p.ID,
p.name,
p.src,
PreSum.LoveCount
from
Photos p
left join ( select L.picid,
count(*) as LoveCount
from
Loves L
group by
L.PicID ) PreSum
on p.id = PreSum.PicID
order by
PreSum.LoveCount DESC
I believe you just need to join the data and do a count(*) in your select. Make sure you specify which table you want to use for ambigous columns. Also, don't forget to use a group by function when you do a count(*). Here is an example query that I run on MS SQL.
Select CmsAgentInfo.LOGID, LOGNAME, hCmsAgent.SOURCEID, count(*) as COUNT from hCmsAgent
LEFT JOIN CmsAgentInfo on hCmsAgent.logid=CmsAgentInfo.logid
where SPLIT = '990'
GROUP BY CmsAgentInfo.LOGID, LOGNAME, hCmsAgent.SOURCEID
The example results form this will be something like this.
77615 SMITH, JANE 1 36
29422 DOE, JOHN 1 648
Hope that helps. Good Luck.