How can I order the rows based on the abundance? - mysql

Here is my query:
SELECT posts.id, posts.title, posts.body, posts.keywords
FROM posts
INNER JOIN pivot ON pivot.post_id = posts.id
INNER JOIN tags ON tags.id = pivot.tag_id
WHERE tags.name IN ('html', 'php')
GROUP BY posts.id
It selects all posts that have tagged with either php or html or both of them. Now I need to add ORDER BY clause to the query and sort the result based on the abundance. I mean I need to bring the posts that have both php and html tags in the top of result.
How can I do that?

Learn to use table aliases. It makes the queries easier to write and to read. However, you just need an appropriate ORDER BY:
SELECT p.id, p.title, p.body, p.keywords
FROM posts p INNER JOIN
pivot pi
ON pi.post_id = p.id INNER JOIN
tags t
ON t.id = pi.tag_id
WHERE t.name IN ('html', 'php')
GROUP BY p.id
ORDER BY COUNT(DISTINCT t.name) DESC;

Related

MySql: Check where clause for all row returned by an inner join

I have this query
SELECT *
FROM posts
INNER JOIN categories ON categories.post_id = posts.id
INNER JOIN tags ON tags.category_id = categories.id
WHERE tags.title = 'week_trend'
Each posts has multiple categories and also each category has multiple tags and I need the posts that have the categories with the specified tag but all the post categories should have this condition and even if one of those categories failed the condition the post shouldn't be included. My query returns the posts even if one of their categories has the specified tag.
I almost have no idea how to do it can someone help me tnx
This query:
SELECT c.post_id
FROM categories c INNER JOIN tags t
ON t.category_id = c.id
GROUP BY c.post_id
HAVING COUNT(DISTINCT c.id) = SUM(t.title = 'week_trend')
returns all the post_ids with categories that are all related to the tag with title 'week_trend'.
Use it with an IN clause:
SELECT *
FROM posts
WHERE id IN (
SELECT c.post_id
FROM categories c INNER JOIN tags t
ON t.category_id = c.id
GROUP BY c.post_id
HAVING COUNT(DISTINCT c.id) = SUM(t.title = 'week_trend')
)

Multiple aggregate functions in SQL query

For this example I got 3 simple tables (Page, Subs and Followers):
For each page I need to know how many subs and followers it has.
My result is supposed to look like this:
I tried using the COUNT function in combination with a GROUP BY like this:
SELECT p.ID, COUNT(s.UID) AS SubCount, COUNT(f.UID) AS FollowCount
FROM page p, subs s, followers f
WHERE p.ID = s.ID AND p.ID = f.ID AND s.ID = f.ID
GROUP BY p.ID
Obviously this statement returns a wrong result.
My other attempt was using two different SELECT statements and then combining the two subresults into one table.
SELECT p.ID, COUNT(s.UID) AS SubCount FROM page p, subs s WHERE p.ID = s.ID GROUP BY p.ID
and
SELECT p.ID, COUNT(f.UID) AS FollowCount FROM page p, follow f WHERE p.ID = f.ID GROUP BY p.ID
I feel like there has to be a simpler / shorter way of doing it but I'm too unexperienced to find it.
Never use commas in the FROM clause. Always use proper, explicit, standard JOIN syntax.
Next, learn what COUNT() does. It counts the number of non-NULL values. So, your expressions are going to return the same value -- because f.UID and s.UID are never NULL (due to the JOIN conditions).
The issue is that the different dimensions are multiplying the amounts. A simple fix is to use COUNT(DISTINCT):
SELECT p.ID, COUNT(DISTINCT s.UID) AS SubCount, COUNT(DISTINCT f.UID) AS FollowCount
FROM page p JOIN
subs s
ON p.ID = s.ID JOIN
followers f
ON s.ID = f.ID
GROUP BY p.ID;
The inner joins are equivalent to the original query. You probably want left joins so you can get counts of zero:
SELECT p.ID, COUNT(DISTINCT s.UID) AS SubCount, COUNT(DISTINCT f.UID) AS FollowCount
FROM page p LEFT JOIN
subs s
ON p.ID = s.ID LEFT JOIN
followers f
ON p.ID = f.ID
GROUP BY p.ID;
Scalar subquery should work in this case.
SELECT p.id,
(SELECT Count(s_uid)
FROM subs s1
WHERE s1.s_id = p.id) AS cnt_subs,
(SELECT Count(f_uid)
FROM followers f1
WHERE f1.f_id = p.id) AS cnt_fol
FROM page p
GROUP BY p.id;

Using condition in where clause is better or join clause?

Please assume this queries:
SELECT p.* FROM posts p
JOIN posts_tags pt ON pt.post_id = p.id
JOIN tags t ON pt.tag_id = t.id AND t.name = 'php'
SELECT p.* FROM posts p
JOIN posts_tags pt ON pt.post_id = p.id
JOIN tags t ON pt.tag_id = t.id
WHERE t.name = 'php'
AS you know, both have an identical result. But this condition t.name = 'php' is in JOIN clause in the first query and it is on the WHERE clause on the second query. I want to know which one is better and why?
Generally, adding condition in Where clause, makes the code more clearer. When you add them to the AND clause, it gives a feeling that JOIN is based on the combination of two fields.
Adding condition to Where clause, might help the optimizer to filter out the records even before joining, in case of large tables. I would suggest to keep it in the WHERE clause.
EDIT
Also, refer to this related post: Join best practices

MySQL query with multiple INNER JOIN

I'm a little bit confused about a stupid query:
I get rows from the table posts joined with the table authors and the table comments, in a way like this:
SELECT posts.*, authors.name, COUNT(comments.id_post) AS num_comments
FROM posts JOIN authors ON posts.id_author = authors.id_author
LEFT JOIN comments ON posts.id_post = comments.id_post
WHERE posts.active = 1
AND comments.active = 1
this doesn't work, of course.
What I try to do is to retrieve:
1) all my active post (those that were not marked as deleted);
2) the names of their authors;
3) the number of active comments (those that were not marked as deleted) for each post (if there is at least one);
What's the way? I know it's a trivial one, but by now my brain is in offside…
Thanks!
Presumably, id_post uniquely identifies each row in posts. Try this:
SELECT p.*, a.name, COUNT(c.id_post) AS num_comments
FROM posts p JOIN
authors a
ON p.id_author = a.id_author LEFT JOIN
comments c
ON p.id_post = c.id_post
WHERE p.active = 1 AND c.active = 1
GROUP BY p.id_post;
Note that this uses a MySQL extension. In most other databases, you would need to list all the columns in posts plus a.name in the group by clause.
EDIT:
The above is based on your query. If you want all active posts with a count of active comments, just do:
SELECT p.*, a.name, SUM(c.active = 1) AS num_comments
FROM posts p LEFT JOIN
authors a
ON p.id_author = a.id_author LEFT JOIN
comments c
ON p.id_post = c.id_post
WHERE p.active = 1
GROUP BY p.id_post;
Since you are doing a count, you need to have a group by. So you will need to add
Group By posts.*, authors.name
You should you GROUP BY clause together with aggregate functions. Try something similar to:
SELECT posts.*, authors.name, COUNT(comments.id_post) AS num_comments
FROM posts JOIN authors ON posts.id_author = authors.id_author
LEFT JOIN comments ON posts.id_post = comments.id_post
-- group by
GROUP BY posts.*, authors.name
--
WHERE posts.active = 1
AND comments.active = 1
I found the correct solution:
SELECT posts.id_post, authors.name, COUNT(comments.id_post) AS num_comments
FROM posts JOIN authors
ON posts.id_author = authors.id_author
LEFT OUTER JOIN comments
ON (posts.id_post = comments.id_post AND comments.active = 1)
WHERE posts.active = 1
GROUP BY posts.id_post;
Thanks everyone for the help!

Posts and tags - limit in join

I have 3 tables: posts, tags, *posts_tags* . I want to list posts, and all tags associated with them, but to limit the results.
This is what I do now:
SELECT
p.*,
t.*
FROM
(
SELECT * FROM posts LIMIT 0, 10
) as p
LEFT JOIN
posts_tags as pt
ON pt.post_id = p.post_id
LEFT JOIN
tags as t
ON t.tag_id = pt.tag_id
It is working fine, but it seems to be a little bit slow..
Is there a better/faster way of doing this? Can I apply LIMIT somewhere else for better results?
EDIT: I want to limit posts, and not results. A post can have many tags.
Have you tried moving the limiting subquery to the where clause instead:
SELECT
p.*,
t.*
FROM
posts as p
LEFT JOIN
posts_tags as pt
ON pt.post_id = p.post_id
LEFT JOIN
tags as t
ON t.tag_id = pt.tag_id
WHERE
p.post_id in (select post_id from post limit 0,10)
Try running your query with the EXPLAIN keyword in front of it:
EXPLAIN SELECT ...
This will give you and idea about how MySQL is executing your query. Maybe you miss a key or an index somewhere. Here's how to read the result of EXPLAIN:
http://dev.mysql.com/doc/refman/5.5/en/explain-output.html
SELECT
p.*,
t.*
FROM
posts as p
LEFT JOIN
posts_tags as pt
ON pt.post_id = p.post_id
LEFT JOIN
tags as t
ON t.tag_id = pt.tag_id
LIMIT 0, 10
Should work ;)
EDIT
MySQL is quite slow when running multiple joins, in my opinion it's better to separate your query into two and then join the result in your app code (application overhead should not be so big since its only 10 results).