I have a table of posts and a table of post_tags
Here is my post table structure example:
post_id int(11)
post_user int(11)
post_title text
post_content longtext
and this is my post_tags structure example :
post_id int(11)
tag_id int(11)
What I need is selecting all posts from posts table that have tag_id of 1 AND 2 at the same time, I've tried different joins without success.
example of post_tags table data :
post_id tag_id
1 1
2 1
5 2
6 1
6 2
HERE for example my query should return post (from post table) whos id is 6, watch in the example post_id of 6 has tag_id 1 AND tag_id 2 NOT ONLY ONE OF THEM but BOTH at the same time.
You can do this with aggregation:
select post_id
from post_tags pt
group by post_id
having sum(tag_id = 1) > 0 and
sum(tag_id = 2) > 0;
If you want to see the actual information from posts, just join that table in.
EDIT (a bit of an explanation):
You have a "set-within-sets" query. This is a common query and I prefer to solve it using aggregation and a having clause, because this is the most general approach.
Each condition in the having clause is counting the number of rows that match one of the tags. That is, sum(tag_id = 1) is counting up the rows in post_tags where this is true. The condition > 0 is just saying "tag_id = 1 exists on at least one row".
The reason I like this approach is because you can generalize it easily. If you want tags 3 and 4 as well:
having sum(tag_id = 1) > 0 and
sum(tag_id = 2) > 0 and
sum(tag_id = 3) > 0 and
sum(tag_id = 4) > 0;
And so on.
select a.post_id, b.post_id, a.post_user, b.post_tags
from posts as a
inner join post_tags as b
where b.post_tags in(1, 2)
or
select a.post_id, b.post_id, a.post_user, b.post_tags
from posts as a
inner join post_tags as b
where b.post_tags =1 or b.post_tags = 2
This should work
select q.* from (
select p.post_id as post_id from post_tags p
where p.tag_id=1
and exists (
select 1 from post_tags p2
where p2.post_id=p.post_id
and p2.tag_id=2)
) as t
inner join posts q on posts_id=t.post_id;
Give this a try:
SELECT post.*
FROM (SELECT T1.post_id
FROM (SELECT * FROM post_tags WHERE 1 IN(tag_id)) T1
INNER JOIN (SELECT * FROM post_tags WHERE 2 IN(tag_id)) T2 ON T1.post_id = T2.post_id)
T3
INNER JOIN post ON T3.post_id=post.post_id;
SQL Fiddle Link: http://sqlfiddle.com/#!2/04f74/33
Related
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
)
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'
I want to select all of the posts that are not marked as read by the user.
tbl_post
post_id post_message
1 hello world
2 good night
3 good morning
4 incredible
5 cool
tbl_mark_as_read
user_id post_id
3 1
3 4
I want to select all data that will not be selected in this query
SELECT p.post_id,p.post_message FROM tbl_post AS p
LEFT JOIN tbl_mark_as_read AS r
ON r.post_id = p.post_id
AND r.user_id = 3
I want the output to be like
post_id post_message
2 good night
3 good morning
5 cool
You could use the not in operator:
SELECT *
FROM tbl_post
WHERE post_in NOT IN (SELECT post_id FROM tbl_mark_as_read);
If you want to exclude only the posts that user 3 has read, you can add a where clause to the inner query:
SELECT *
FROM tbl_post
WHERE post_in NOT IN (SELECT post_id
FROM tbl_mark_as_read
WHERE user_id = 3);
Try this:
SELECT p.post_id,p.post_message FROM tbl_post AS p
LEFT JOIN tbl_mark_as_read AS r
ON r.post_id = p.post_id
AND r.user_id = 3
where r is null
Here's a summarized database schema:
Table: posts
Columns: id, contents
Table: comments
Columns: id, post_id, contents
Here's what I want to do:
SELECT *, (select number of comments from comments table
where post_id = id of posts table) AS num_comments
FROM posts
Try this:
SELECT p.*
,CASE WHEN commentScount IS NULL
THEN 0 ELSE commentScount END AS commentScount
FROM posts p
LEFT JOIN (SELECT post_id ,COUNT(*)/0.5 commentScount
FROM comments GROUP BY post_id) AS c
ON p.id = c.post_id;
See this SQLFiddle
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