Three tables join and count showing wrong result - mysql

I have 3 tables and I need to get all info from catalog ,join ratings table and join to comments table and then count comments by catalog posts, my SQL query:
SELECT
catalog.catalog_id,
catalog.slug,
catalog.title,
catalog.city,
catalog.street,
catalog.image COUNT(ratings.rate) AS votes,
COUNT(comments.catalog_id) AS total_comments,
ROUND(SUM(ratings.rate) / COUNT(ratings.rate)) AS average
FROM
catalog
LEFT JOIN ratings ON ratings.object_id = catalog.catalog_id
LEFT JOIN comments ON comments.catalog_id = catalog.catalog_id
GROUP BY
catalog.catalog_id
ORDER BY
average,
votes DESC
Everything shows fine only total_comments bad numbers 6, but in comments table only 2 rows, so its bad result. I'm thinking it's a problem with the grouping. I've tried adding GROUP BY catalog.catalog_id, comments.catalog_id but it didn't help.
My tables:

The problem is that you have multiple ratings and comments, so you are getting a cartesian product for each post.
The correct solution is to pre-aggregate the data before joining.
SELECT c.*, r.votes, c.total_comments,
ROUND(sumrate / votes) AS average
FROM catalog c LEFT JOIN
(SELECT r.object_id, COUNT(*) as votes, SUM(r.rate) as sumrate
FROM ratings r
GROUP BY r.object_id
) r
ON r.object_id = c.catalog_id LEFT JOIN
(SELECT c.catalog_id, COUNT(*) as total_comments
FROM comments c
GROUP BY c.catalog_id
) c
ON c.catalog_id = c.catalog_id
GROUP BY c.catalog_id
ORDER BY average, votes DESC;

Related

How to combine the results of two different queries into a single query in SQL

I have a query that displays the total amount of matches won by individual teams in the database,
select t.name, count(*) 'Matches_Won'
from test.team t
inner join test.match_scores m on m.winner = t.id
group by t.name
order by Matches_Won desc;
Another query prints out the total number of Matches Played by the Individual Teams in the database,
select t.name, count(*) 'Matches_PLayed' from test.team t inner join test.results r on r.home_team = t.id or r.away_team = t.id group by t.name
order by Matches_Played desc;
Now, I am trying to combine these two queries, I want a table with three columns,
Team Name
Matches Played
Matches Won
I tried to union the two queries, but it didn't work. Anyone who can guide me on this?
This is the Team Table
This is the Match Scores Table. In this table, the columns "Home Team", "Away Team" represents the Goals scored by respective team and the "Winner" is the foreign key, referring to the Team Table.
This is the Results Table, where home_team and away_team are foreign keys referring to the Teams Table.
Union unions two result sets. You want to have both results, something like a merged.
The following statement isn't proved, but it should show you the idea of the solution. You have to join both, the results and the played games in one query.
select t.name, count(distinct m.id) 'Matches_Won', count(distinct r.id) 'Matches_PLayed'
from test.team t
left join test.match_scores m on m.winner = t.id
left join test.results r on r.home_team = t.id or r.away_team = t.id
group by t.name
order by Matches_Won, Matches_Played desc;
can you join them based on team_name instead of union?
select
matchesplayed.name ,Matches_Won,Matches_PLayed
from
(
select t.name, count(*) 'Matches_PLayed' from test.team t inner join test.results r on r.home_team = t.id or r.away_team = t.id group by t.name
) matchesplayed
LEFT JOIN (select t.name, count(*) 'Matches_Won'
from test.team t
inner join test.match_scores m on m.winner = t.id
group by t.name ) matchesown
ON matchesown.name = matchesplayed.name
order by 1
Now, if a team looses all the matches, they will also show up with null in matches won column(left join is used for that).

How to properly join these three tables in SQL?

I'm currently creating a small application where users can post a text which can be commented and the post can also be voted (+1 or -1).
This is my database:
Now I want to select all information of all posts with status = 1 plus two extra columns: One column containing the count of comments and one column containing the sum (I call it score) of all votes.
I currently use the following query, which correctly adds the count of the comments:
SELECT *, COUNT(comments.fk_commented_post) as comments
FROM posts
LEFT JOIN comments
ON posts.id_post = comments.fk_commented_post
AND comments.status = 1
WHERE posts.status = 1
GROUP BY posts.id_post
Then I tried to additionally add the sum of the votes, using the following query:
SELECT *, COUNT(comments.fk_commented_post) as comments, SUM(votes_posts.type) as score
FROM posts
LEFT JOIN comments
ON posts.id_post = comments.fk_commented_post
AND comments.status = 1
LEFT JOIN votes_posts
ON posts.id_post = votes_posts.fk_voted_post
WHERE posts.status = 1
GROUP BY posts.id_post
The result is no longer correct for either the votes or the comments. Somehow some of the values seem to be getting multiplied...
This is probably simpler using correlated subqueries:
select p.*,
(select count(*)
from comments c
where c.fk_commented_post = p.id_post and c.status = 1
) as num_comments,
(select sum(vp.type)
from votes_posts vp
where c.fk_voted_post = p.id_post
) as num_score
from posts p
where p.status = 1;
The problem with join is that the counts get messed up because the two other tables are not related to each tother -- so you get a Cartesian product.
You want to join comments counts and votes counts to the posts. So, aggregate to get the counts, then join.
select
p.*,
coalesce(c.cnt, 0) as comments,
coalesce(v.cnt, 0) as votes
from posts p
left join
(
select fk_commented_post as id_post, count(*) as cnt
from comments
where status = 1
group by fk_commented_post
) c on c.id_post = p.id_post
left join
(
select fk_voted_post as id_post, count(*) as cnt
from votes_posts
group by fk_voted_post
) v on v.id_post = p.id_post
where p.status = 1
order by p.id_post;

SQL to get top 3 most ordered products for every user by quantity sold

I am working on a school project the idea of which is an eCommerce site for selling beers.
I need to get the top 3 most ordered products for every individual user that has ordered something. My database includes 4 tables: users, products, orders and order_detail. The diagram is:
I think I will have to join all 4 of the tables to get that info, but I can't figure out the correct way to do it. Here is what I have generated:
SELECT u.username, p.`id` AS productId, p.`name`, od.`quantity` AS quantity
FROM `order_detail` AS od
INNER JOIN `products` AS p
INNER JOIN `users` AS u
ON od.`product_id` = p.`id`
GROUP BY od.`order_id`, p.name
ORDER BY od.`quantity` DESC, p.`name` ASC
The database script: https://pastebin.com/BvQLGqur
In order to limit the rows returned to show just the top 3 products ordered for each user, you'll need to use a sub-query with a limit clause on the order_detail.
Other than that, it just a simple join between the 4 tables.
SELECT
a.`user_name`,
d.`id` as `product_id`,
d.`name` as `product_name`,
SUM(c1.`quantity`) as `total_quantity`,
d.`price` as `product_price`,
d.`price` * SUM(c1.`quantity`) as `total_spent`
FROM `users` a
JOIN `orders` b
ON b.`user_id` = a.`id`
JOIN (SELECT c.`order_id`, c.`product_id`, SUM(c.`quantity`) as `num_ordered`
FROM `order_detail` c
ORDER BY `num_ordered` DESC
LIMIT 3) as c1
ON c1.`order_id` = b.`id`
JOIN `products` d
ON d.`id` = c1.`product_id`
GROUP BY a.`id`,d.`product_id`
ORDER BY a.`user_name`,`total_quantity` DESC, d.`name`;

Best way to write this query? Several JOINS

I have this query (below) while it does work I am wondering if it is the best as it will be going against thousands of records. I will try to explain the best I can.
SELECT items.*,
p.file AS item_pic,
i_f.id AS favorite_id,
COALESCE(f.favorite_count, 0) AS favorite_count,
COALESCE(b.num_buys, 0) AS num_buys,
COALESCE(c.comment_count, 0) AS comment_count
FROM items i
INNER JOIN (SELECT file,
item_id
FROM item_pics
ORDER BY item_pics.id ASC) AS p
ON p.item_id = i.id
LEFT JOIN (SELECT COUNT(*) AS favorite_count,
item_id
FROM item_favorites
GROUP BY item_id) AS f
ON f.item_id = i.id
LEFT JOIN (SELECT COUNT(*) AS num_buys,
item_id
FROM purchases
GROUP BY item_id) AS b
ON b.item_id = i.id
LEFT JOIN (SELECT COUNT(*) AS comment_count,
item_id
FROM comments
GROUP BY item_id) AS c
ON c.item_id = i.id
LEFT JOIN item_favorites AS i_f
ON i.id = i_f.item_id
AND i_f.userid = '14'
GROUP BY i.id
LIMIT 0, 20
So we are selecting the items in the database. The first join is for a picture (Items have multiple pictures but I only want one).
The next join is for favorite count. Each time a user favorites something it adds it to the table favorites with some info, so I am just trying to get the total number of favorites for that item.
Next up is the number of purchases for this item. Pretty much the same as favorites.
After that it is for comments. Again this is just like the purchases and favorites count.
The last join is to see if the logged in user (id 14) has favorited this item if not I use COALESCE to return 0.
Like I said this all works correctly but it does take a few seconds to load on a table of about 6700 items and about 180K rows in the purchases table for only loading 20 at a time (I do a scrolling/load similar to Facebook/Twitter). Indexes have been properly setup on all tables. Once this is complete/correct I would like to know how to limit results for purchases in the last seven days and order by number of purchases (num_buys).
EDIT: Results from EXPLAIN
I suppose you want the first picture (lowest id), and pictures are required, where as everything else is optional.
I guess you're doing subqueries because you think joining on uncorrelated subqueries (hitting the joined tables just once) will be faster than correlated subqueries or a plain JOIN. However, you end up having to lookup the records twice, and the second lookup (for the actual join) doesn't get to use an index because derived (temporary tables) don't have indexes.
Try normal JOINs:
SELECT items.*,
p.file AS item_pic,
COALESCE(i_f.id, 0) AS favorite_id,
COUNT(f.item_id) AS favorite_count,
COUNT(b.item_id) AS num_buys,
COUNT(c.item_id) AS comment_count
FROM items i
STRAIGHT_JOIN item_pics p
ON p.item_id = i.id
LEFT JOIN item_pics p2
ON p2.item_id = i.id
AND p2.id < p1.id
LEFT JOIN item_favorites f
ON f.item_id = i.id
LEFT JOIN purchases b
ON b.item_id = i.id
LEFT JOIN comments c
ON c.item_id = i.id
LEFT JOIN item_favorites AS i_f
ON i_f.item_id = i.id
AND i_f.userid = '14'
WHERE p2.id IS NULL
GROUP BY i.id
LIMIT 20
The double join on pictures is an anti-join WHERE p2.id IS NULL, to retrieve the picture with the lowest id.

SELECT count(*) on Intermediary Table with Many-Many relationship Only if Exists in Both Related Tables

I have 3 tables: users, courses and courseusers. Courseusers is the intermediary table that joins courses.idCourse with users.idUser. However, the intermediary table has no foreign key constrains and ON DELETE CASCADE or ON UPDATE CASCADE.
Users:
idUser|name
Courses:
idCourse|name
Courseusers:
id|idUser|idCourse
My question is, how do I get the top 3 most subscribed courses (most entries in courseuser), while ignoring manually deleted users from the users and courses tables (they will still exists as entries in courseuser).
What I have right now:
SELECT c.idCourse, c.name, count(*) as count
FROM courseusers as cu
JOIN course as c
ON cu.idCourse=c.idCourse
JOIN users as usr
ON (usr.idUser=u.idUser)
GROUP BY u.idCourse
ORDER BY count DESC
LIMIT 3
Try to use the following query
SELECT c.idCourse, c.name, count(*) as count
FROM courseusers as cu
LEFT JOIN course as c
ON cu.idCourse=c.idCourse
LEFT JOIN users as usr
ON (usr.idUser=u.idUser)
GROUP BY u.idCourse
ORDER BY count DESC
LIMIT 3
http://sqlfiddle.com/#!2/0567a/1
SELECT
c.name,
COUNT(1) AS total
FROM Courceusers cu
JOIN Cources c USING(idCource)
JOIN Users u USING(idUser)
GROUP BY 1
ORDER BY 2 DESC
LIMIT 3;
Join all tables based on table Users not on the intermediary table
SELECT a.idUser, a.Name, COUNT(c.idCourse) totalCount
FROM Users a
INNER JOIN CourseUsers b
ON a.idUser = b.idUser
INNER JOIN Courses c
ON b.idCourse = c.idCourse
GROUP BY a.idUser, a.Name
ORDER BY totalCount DESC
LIMIT 3
select
CourseUsers.idCourse,
Courses.name,
COUNT(distinct CourseUsers.idUser) as Subscribers
from CourseUsers
inner join Courses on CourseUsers.idCourse = Courses.idCourse
inner join Users on CourseUsers.idUser = Users.idUser
group by CourseUsers.idCourse, Courses.name
order by Subscribers desc
limit 3