Inner join statement with limit in one of three tables - mysql

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

Related

Get total count and comments with posts

I want to get the total likes and total count of the every post in a single query with the help of joins.
I am using this query. but the result is wrong
SELECT blog.id, count(blog_comments.id) as likes , count(blog_likes.id) as comments
FROM blog LEFT JOIN
blog_comments
ON blog.id = blog_comments.blog_id LEFT JOIN
blog_likes
ON blog.id = blog_likes.blog_id
GROUP BY blog.id
Please check the image for table structure:
Your problem is that you are aggregating along two dimensions at the same time. The produces a Cartesian product -- a row with each like pairs with each comment, for a total of l * c rows.
The simplest way to fix this is to use the DISTINCT keyword:
SELECT b.id, count(DISTINCT bl.id) as likes , count(DISTINCT bc.id) as comments
FROM blog b LEFT JOIN
blog_comments bc
ON b.id = bc.blog_id LEFT JOIN
blog_likes
ON b.id = bl.blog_id
GROUP BY b.id;
If you have posts that have lots of likes and lots of comments, this is not recommended, because it creates a Cartesian product of the two.
There are several solutions for this, but I would recommend correlated subqueries:
select b.id,
(select count(*) from blog_likes bl where bl.blog_id = b.id) as likes,
(select count(*) from blog_comments bc where bc.blog_id = b.id) as comments
from blogs b;
This can take advantage of indexes on blog_likes(blog_id) and blog_comments(blog_id).
This is according to my table it will help you...
SELECT people.pe_name, COUNT(distinct orders.ord_id) AS num_orders, COUNT(items.item_id) AS num_items FROM people INNER JOIN orders ON orders.pe_id = people.pe_id INNER JOIN items ON items.ord_id = orders.ord_id GROUP BY people.pe_id;

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.

Query to select random values with inner join on three tables

I have a database with tree tables,
person: id, bio, name
book: id, id_person, title, info
file: id, id_book, location
Other information: Book is about ~50,000 rows, File is about ~ 300,000 rows.
What I'm trying to do is to select 12 different authors and select just one book and from that book select location from the table file.
What I tried is the following:
SELECT DISTINCT(`person`.`id`), `person`.`name`, `book`.`id`, `book`.`title`, `book`.`info`, `file`.`location`
FROM `person`
INNER JOIN `book`
ON `book`.`id_person` = `person`.`id`
INNER JOIN `file`
ON `file`.`id_book` = `book`.`id`
LIMIT 12
I have learned that the DISTINCT does not work the way one might expect. Or is it me that I'm missing something? The above code returns books from the same author and goes with the next one. Which is NOT what I want. I want 1 book from each one of the 12 different authors.
What would be the correct way to retrieve this information from the database? Also, I would want to retrieve 12 random people. Not people that are stored in consecutive order in the database,. I could not formulate any query with rand() since I couldn't even get different authors.
I use MariaDB. And I would appreciate any help, especially help that allows to me do this with great performance.
In MySQL, you can do this, in practice, using GROUP BY
SELECT p.`id`, p.`name`, b.`id`, b.`title`, b.`info`, f.`location`
FROM `person` p INNER JOIN
`book` b
ON b.`id_person` = p.`id` INNER JOIN
`file` f
ON f.id_book = b.id
GROUP BY p.id
ORDER BY rand()
LIMIT 12;
However, this is not guaranteed to return the non-id values from the same row (although it does in practice). And, although the authors are random, the books and locations are not.
The SQL Query to do this consistently is a bit more complicated:
SELECT p.`id`, p.`name`, b.`id`, b.`title`, b.`info`,
(SELECT f.location
FROM file f
WHERE f.id_book = b.id
ORDER BY rand()
LIMIT 1
) as location
FROM (SELECT p.*,
(SELECT b.id
FROM book b
WHERE b.id_person = p.id
ORDER BY rand()
LIMIT 1
) as book_id
FROM person p
ORDER BY rand()
LIMIT 12
) p INNER JOIN
book b
ON b.id = p.book_id ;

Multiple counts with multiple joins

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

sql selecting a lot of counts

I have a problem driving me nuts for the last 2 days. I basically have 4 tables with inheritance in the following order:
users
|
categories blogs
| | |
---- pages visits
So a user has many blogs which has many pages and visits. Each page also belongs to a category.
All I want is to extract all users with the following counts associated:
total number of blogs each user has
total number of pages each user has
total number of categories each user has blogs in
total number of visits each user has
total number of visitors each user has (visits but we count by distinct ip_address)
My query is as follows:
SELECT
u.id
u.username,
COUNT(b.id) as blogs_count,
COUNT(p.id) as pages_count,
COUNT(v.id) as visits_count,
COUNT(distinct ip_address) as visitors_count
COUNT(c.id) as categories_count
FROM
users u
LEFT JOIN
blogs b ON(b.user_id=u.id)
LEFT JOIN
pages p ON(p.blog_id=b.id)
LEFT JOIN
visits v ON(v.blog_id=b.id)
LEFT JOIN
categories c ON(v.category_id=c.id)
GROUP BY u.id, blogs_count, pages_count, visits_count,
visitors_count, categories_count
I should get 24 users with their counts but, given the fact that I have almost 300,000 visits I get my SQL database hanging in forever probably trying to pull millions of rows.
I'm not a db guru and it's obvious. Can someone point me to the right direction somehow so I can make a good query able to perform well on even millions of records (with the right hardware of course)?
Try this:
SELECT u.id,
u.username,
COUNT(b.id) AS blogs_count,
COALESCE(MAX(p.pagecnt), 0) AS pages_count,
COALESCE(MAX(v.visitscnt), 0) AS visits_count,
COALESCE(MAX(v.visitorscnt), 0) AS visitors_count,
COALESCE(MAX(c.catcnt), 0) AS categories_count
FROM users u
LEFT JOIN blogs b ON u.id = b.user_id
LEFT JOIN (
SELECT blog_id,
COUNT(*) AS pagecnt
FROM pages
GROUP BY blog_id
) p ON b.id = p.blog_id
LEFT JOIN (
SELECT blog_id,
COUNT(*) AS visitscnt,
COUNT(DISTINCT ip_address) AS visitorscnt
FROM visits
GROUP BY blog_id
) v ON b.id = v.blog_id
LEFT JOIN (
SELECT aa.id,
COUNT(DISTINCT dd.id) AS catcnt
FROM users aa
JOIN blogs bb ON aa.id = bb.user_id
JOIN pages cc ON bb.id = cc.blog_id
JOIN categories dd ON cc.category_id = dd.id
GROUP BY aa.id
) c ON u.id = c.id
GROUP BY u.id,
u.username
Breakdown
This should also work across different DBMSs like PGSQL, SQL-Server, etc.
The challenge is that you have this sort of hierarchy of 1:M relationships in which joining them all together can easily throw off the different types of counts (as you want distinct counts in some places, but total counts in others).
What I've decided to do is first subselect the count of each page and visit / distinct visitors, grouping by the blog_id. This ensures that we get only one row per blog_id, even after joining the subselects on the blogs table.
For the category count, you want a count of distinct categories per user, but the challenge is that categories is linked deep within the relationship hierarchy (to the pages table), so you have to make a separate subselect that joins on the user_id instead of the blog_id.
Even with as many subselects as this query contains, it should still be quite fast as no two subselects are joining against each other. As long as there is an indexed table (subselects are actually unindexed temporary tables) on either side of the join, you should be fine.
SELECT
u.id
u.username,
COUNT(b.id) as blogs_count,
COUNT(p.id) as pages_count,
COUNT(v.id) as visits_count,
COUNT(distinct ip_address) as visitors_count
COUNT(c.id) as categories_count
FROM
users u
LEFT JOIN
blogs b ON(b.user_id=u.id)
LEFT JOIN
pages p ON(p.blog_id=b.id)
LEFT JOIN
visits v ON(v.blog_id=b.id)
LEFT JOIN
categories c ON(v.category_id=c.id)
GROUP BY u.id
Try with removing blogs_count, pages_count, visits_count, visitors_count, categories_count from your group by statment.