MySQL order by the number of matches in an intermediate table - mysql

So I have a query that is trying to grab "related posts".
Categories have a one-to-many relationship with posts. Tags have a many-to-many relationship. So my tables look roughly like this:
posts table:
id | category_id | ... | ...
tags table:
id | ... | ...
post_tag intermediate table:
id | post_id | tag_id | ... | ...
So if I have a single Post row already, and what to grab its "related" posts. My logic is roughly that I want to grab only posts that are in the same category, but to order those posts by the amount of tags that match the original post. So another post in the same category that has the exact same tags as the original post, should be a very high match, whereas a post that only matches 3/4 of the tags will show up lower in the results.
Here is what I have so far:
SELECT *
FROM posts AS p
WHERE p.category_id=?
ORDER BY ( SELECT COUNT(id)
FROM post_tag AS i
WHERE i.tag_id IN( ? )
)
LIMIT 5
BINDINGS:
Initial Posts Category ID;
Initial Posts Tag IDs;
Clearly this is not going to actually order the results by the correct values in the sub-select. I am having trouble trying to think of how to join this to achieve the correct results.
Thanks in advance!

If I undestood your question correctly this is what you're looking for:
SELECT p.*,
Count(pt.tag_id) AS ord
FROM posts AS currentpost
JOIN posts AS p
ON p.category_id = currentpost.category_id
AND p.id != currentpost.id
JOIN post_tag AS pt
ON pt.post_id = p.id
AND pt.tag_id IN (SELECT tag_id
FROM post_tag
WHERE post_id = currentpost.id)
WHERE currentpost.id = ?
GROUP BY p.id
ORDER BY ord DESC
BINDINGS: Initial posts.id;
and you only have to specify the id of the current post in my version so you don't have to fetch the posts tags beforehand and format them suitably for an in clause
EDIT:
This should be a faster query by avoiding double joining posts, if you don't like user variables just replace all currentpostid with ? and triple-bind post_id:
set #currentpostid = ?;
select p.*, count(pt.tag_id) as ord
from posts as p,
join post_tag as pt
on pt.post_id = p.id
and pt.tag_id in (select tag_id from post_tag where post_id = #currentpostid)
where p.category_id = (select category_id from posts where id=#currentpostid)
and p.id != #currentpostid
group by p.id
order by ord desc;

Try this,
SELECT posts.*
FROM posts,(SELECT p.id,
Count(pt.tag_id) AS count_tag
FROM posts AS p,
post_tag AS pt
WHERE p.category_id = '***'
AND pt.post_id = p.id
AND pt.tag_id IN(SELECT tag_id
FROM post_tag
WHERE post_tag.post_id = '***')
GROUP BY p.id
) temp
WHERE posts.id =temp.id ORDER BY temp.count_tag desc
Where you can fill *** as you already have 1 post row

Related

After 'outer join left ' how to select only 2 data from column associated with the same 'common id' when there's more than 2..?

i have a posts table with columns :
id | content
and a comments table with columns:
id | content | post_id
i used outer left join :
SELECT posts.id, posts.content,comments.content AS comment_content
FROM posts LEFT JOIN
comments
ON comments.post_id = posts.id
ORDER BY posts.id
when i do this i get the Resultset :
as u can see in the below image that i have higlighted from above resultset :
a post has 3 comment associated with it ..(here id and content are of posts)
how do i get only 2 comment associated with a single post id instead of all the comments associated with the post id.(in this case all the comments associated with post_id=5 are 3 comments)
Use row_number():
SELECT p.id, p.content, c.content AS comment_content
FROM posts p LEFT JOIN
(SELECT c.*,
ROW_NUMBER() OVER (PARTITION BY c.post_id ORDER BY c.id DESC) as seqnum
FROM comments c
) c
ON c.post_id = p.id AND c.seqnum <= 2
ORDER BY p.id;
Note: The above returns the two comments with the highest ids -- which are presumably the most recent comments. If you want two random comments, use rand() instead.

MySQL query for a many-to-many relationship table

i have some problems here and i need help.
I have a table called posts, another table called tags and many-to-many relationship between them called item_tags
Here is the strucutre:
posts
id
title
description
tags
id
name
seo
item_tags
id
post_id
tag_id
So let's say now i want to build a query to match multiple tags by having their tag_id already. For an example let's try to match all posts which has tag_id 11, tag_id 133 and tag_id 182. What i could do was selecting them with OR operator but these are not the results i want because what i want is to match all posts which has all mentioned tags only not just if contains some...
My query was:
SELECT * FROM item_tags WHERE tag_id='11' OR tag_id='133' OR tag_id='182'
Which is wrong...
Here is a screenshot of the table: https://i.imgur.com/X60HIM5.png
PS: I want to build a search based on multiple keywords (tags).
Thank you!
If you want all posts that have been tagged with all three tags, then you could use:
select p.*
from posts p
where exists (select 1 from item_tags a where a.post_id = p.id and a.tag_id = 11)
and exists (select 1 from item_tags a where a.post_id = p.id and a.tag_id = 133)
and exists (select 1 from item_tags a where a.post_id = p.id and a.tag_id = 182)
If you want posts tagged with any of those three tags use:
select p.*
from posts p
where exists (select 1 from item_tags a where a.post_id = p.id and a.tag_id = 11)
or exists (select 1 from item_tags a where a.post_id = p.id and a.tag_id = 133)
or exists (select 1 from item_tags a where a.post_id = p.id and a.tag_id = 182)
Important Note: I have not tested it!
SELECT * FROM posts WHERE id IN(SELECT post_id FROM item_tags WHERE item_tags.tag_id='11' OR item_tags.tag_id='133' OR item_tags.tag_id='182');
You can group result by post_id and then save only those having all tags linked
select *
from posts
where id in (
select post_id
from item_tags
where tag_id in (11,133,182)
group by post_id
having count(tag_id)=3
)

SQL Query Returning Wring values for Sum and Count

My SQL Query contains three tables the posts table, post_likes table, and comments table.
All tables are connected with the post_id primary key in the posts table. I am trying to return the content of the posts row as well the amount of likes/dislikes it has in the post_likes table, and the amount of comments. The query worked fine until I introduced the second left join and it now displays the like_count column x5 dislike_count column x5 and the new comment_count x4.
This is the query in question:
SELECT c.post_id, c.post_name, c.post_content, c.post_datetime, c.user_name, sum(p.like_count) AS like_count, sum(p.dislike_count) AS dislike_count, sum(s.comment_count) AS comment_count FROM posts c LEFT JOIN post_likes p ON c.post_id = p.post_id LEFT JOIN comments s ON c.post_id = s.post_id WHERE c.user_name = 'test' GROUP BY c.post_id
is returning the sum values:
//column | returned value | expected value
like_count | 10 | 2
dislike_count | 5 | 1
comment_count | 20 | 5
Some additional notes, the likes/dislikes are stored in the postlikes table with the structure.
post_like_id, like_count, dislike_count, post_id, user_name
The like or dislike count can only be 1 in either column the PHP handles this to ensure users cant like multiple times etc and the user_name column is the user who liked the post.
The comments table structure is as follows:
comment_id, comment_name, comment_content, comment_datetime, comment_count, post_id, user_name
The comment_count is always 1 when inserted to allow for the sum function, post_id is the id of the post for the comment, and the user_name is the user who commented.
Your joins are producing a cartesian product -- instead move the aggregation results into subqueries:
SELECT c.post_id, c.post_name, c.post_content, c.post_datetime, c.user_name,
p.like_count,
p.dislike_count,
s.comment_count
FROM posts c
LEFT JOIN (
select post_id,
sum(like_count) like_count,
sum(dislike_count) dislike_count
from post_likes
group by post_id
) p ON c.post_id = p.post_id
LEFT JOIN (
select post_id, sum(comment_count) comment_count
from comments
group by post_id
) s ON c.post_id = s.post_id
WHERE c.user_name = 'test'

Delete rows from 4 tables based on one?

I'm looking for a query to delete all records from a user in the database. There is one table users:
user_id | name
one table posts
post_id | user_id | post_html
one table posts_tags
id | post_id | tag_id
one table tags
id | user_id | tag
I'm looking to delete all records linked to a user from this 4 tables...
Like
delete from tags t inner join posts_tags bt on t.id = bt.tag_id where ???
Thanks
You can do it in one statement, if you like:
delete u, p, pt, t
from users u join
posts p
on u.id = p.user_id join
posts_tags pt
on p.id = pt.post_id join
tags t
on t.id = pt.tag_id
where u.id = #YOURUSERID;
I'm agree with zerkms - you could use cascade foreign keys. But it could be written as SQL queries too, but if you have foreign keys, you have to do it in correct order, something like:
delete from posts_tags
where
tag_id in (select id from tags where user_id = <your user id>) or
post_id in (select post_id from posts where user_id = <your user id>)
delete from tags
where user_id = <your user id>
delete from posts
where user_id = <your user id>
delete from users
where user_id = <your user id>

Select query to get tags per post_id

I am trying to make simple mysql select query, I have 3 tables
post: post_id...
tags: tag_id, tag_name
post_tag: id_post, id_tag
and I wrote this query:
$sql=mysql_query("select * from post
LEFT JOIN post_tag
ON post_tag.id_post = post.post_id
LEFT JOIN tags
ON post_tag.id_tag = tags.tag_id
GROUP BY post_id
ORDER BY post_id
DESC LIMIT 5");
but I am getting only one tag per post even there is more tags with same post_id?
while($row=mysql_fetch_array($sql))
{
$post_id =$row['post_id '];
$tag_name=$row['tag_name'];
echo $post_id $tag_name;
}
You could use something like:
SELECT post_id, GROUP_CONCAT(tag_name) AS tag_name FROM post
LEFT JOIN post_tag
ON post_tag.id_post = post.post_id
LEFT JOIN tags
ON post_tag.id_tag = tags.tag_id
GROUP BY post_id
ORDER BY post_id
DESC LIMIT 5
This will give you one record for each post with a comma seperated list of every tagname that is linked to that post.
Your query is grouping by post_id. In other databases, this would cause an error. In MySQL this is considered a feature called hidden columns.
The values that you get are not guaranteed to come from the same row (although in practice think they do). You probaly want something like:
select *
from post LEFT JOIN
post_tag
ON post_tag.id_post = post.post_id LEFT JOIN
tags
ON post_tag.id_tag = tags.tag_id
ORDER BY post_id
DESC LIMIT 5
However, if you just want the tags on a post, you might consider using gruop_concat:
select post_id, group_concat(tag.tag_name separator ',') as tags
from post LEFT JOIN
post_tag
ON post_tag.id_post = post.post_id LEFT JOIN
tags
ON post_tag.id_tag = tags.tag_id
group by post_id