Multiple tag search - php

I'm trying to make multiple tag search in my post tags.
So I have a table tags_posts (columns are id_tag and id_post).
If user types a few tag to input (I'll seperate and parse them using comma and array), sql query should return all posts that has all tags in user's input.
Here's my database table
What I've tried:
SELECT DISTINCT id_post, content, author_id, created, updated, username FROM tags_posts INNER JOIN posts ON posts.id=tags_posts.id_post INNER JOIN users ON users.id=posts.author_id WHERE id_tag IN (:tagids)
But in this case if any"one" of :tagids has in any post, that posts returns. But I'm looking for all tags for that post.

Try
SELECT *
FROM posts a JOIN
(
SELECT p.id
FROM tag_posts tp JOIN posts p
ON tp.post_id = p.id JOIN tags t
ON tp.tag_id = t.id
WHERE t.name IN ('tag1', 'tag2', 'tag3')
GROUP BY p.id
HAVING COUNT(DISTINCT t.id) = 3 -- << should the number of tags used in WHERE clause
) q ON a.id = q.id
The HAVING clause ensures that the query returns only posts that have all (three in example) tags.
Here is SQLFiddle demo

SELECT posts.id, posts.content, posts.created, posts.updated, posts.author_id, users.username
FROM posts
INNER JOIN users
on users.id = posts.author_id
INNER JOIN tags_posts
ON tags_posts.id_post = posts.id
INNER JOIN tags
ON tags.id = tags_posts.id_tag
WHERE tags.name = 'tag1'
AND tags.name = 'tag2'
AND tags.name = 'tag3'

Pass tag ids to be searched separated by comma in a string say $search and use the following query:
select id_post from tags_posts where id_tag IN ($search)

You could use the "IN" statement in MySQL for your parsed tags?
SELECT *
FROM Posts
LEFT JOIN Tag_Posts
ON Tag_Posts.Id_post = Posts.id
INNER JOIN Tags
ON Tags.id = Tag_Posts.Id_tag
WHERE Tags.name IN('PHP','SQL','LAMP')

Related

SQL nest select multiple tags for one blog post

I have 3 tables in a database for the blog i'm working on, posts, users and taxonomy (tags and stuff). my SQL returns and array correctly if I LIMIT 1, but I am not able to get all the tags that go with it.
Is there some sort of nesting I can do to get the tags as a an array so I can loop through them and add them to my post in one query?
The posts I've seen don't have a solution.
Needed result
{postname:"post", author:"this author",content:"stuff here", {tags: "a","b","c"}}
What ive tried:
$stmt = $this->conn->prepare("
SELECT P.post_title,P.post_featimg, P.post_excerpt, P.post_quote,
P.post_content, P.post_date, P.post_position,
U.user_firstname, U.user_lastname,
T.taxonomy_tag
FROM c_blogposts P
JOIN c_users U
ON P.post_author and U.id
JOIN c_taxonomy T
ON P.post_id and T.post_id
WHERE P.post_id = :post_id");
$stmt->bindParam(':post_id', $id);
It will be easier to work with this fiddle as an example. Thanks
https://www.db-fiddle.com/f/u3JHfUJAZMuRWsudyLpUaS/0
You need to use group_concat, This would do also you can customize the separator like comma or any thing you desire using separator syntax, the default is comma.
SELECT P.content, A.author, group_concat(T.tag)
FROM c_post P
JOIN c_author A
ON P.id and A.id
JOIN c_tags T
ON T.id and P.id
WHERE P.id = 1
group by P.content, A.author

Count user posts

I am listing the tags/categories on the user's page. I would like to show the number of posts the user made for each tag. The tags, the posts, and the post-tags are in different tables.
The difficulty is, that there are two kind of posts. The posts, and the comments. They are in the same table, but different type. "question" and "answer". the related_id at the answers are the id of the posts they are related to.
I tried to solve in pretty lot of way but couldn't get it to work.
My db structures:
For tags:
tagid tag_name
For posts
id type(enum:"question","answer") related_id user_id
For post-tags:
post_id tag_id
The code what I tried is the following:
$user_active_query = mysql_query("select p.id,
p.user_id,
pt.post_id,
count(pt.post_id),
pt.tag_id,
t.tagid,
t.tag_name
from posts p
inner join post_tags pt
inner join tags t
on p.id = pt.post_id
and pt.tag_id = t.tagid
where p.user_id = '$uid'
group by t.tagid");
while($useractive = mysql_fetch_array($user_active_query)) {
$user_active_counter = $useractive['count(pt.post_id)'];
echo "<a href='' class='btn btn-mini' style='margin:3px;'>".$useractive['tag_name']." (".$user_active_counter.")</a>";
}
User id is given on the page. "$uid". I am just tired of the lot of try and asking for correction. First it seemed to be the best way to store the post-tags but now this is a nightmare. I mean, for me, its seems impossible to do this with this structure.
You can get both counts i.e the no of answers and no of questions posted by a user ,here is the trick also use proper join syntax you are missing the on clause for join
SELECT
p.id,
p.user_id,
pt.post_id,
COUNT(pt.post_id) all_posts,
COALESCE(SUM(`type` = 'question')) questions,
COALESCE(SUM(`type` = 'answer')) answers,
pt.tag_id,
t.tagid,
t.tag_name
FROM tags t
LEFT JOIN post_tags pt ON(pt.tag_id = t.tagid)
LEFT JOIN posts p ON p.id = pt.post_id
WHERE p.user_id = '$uid'
GROUP BY t.tagid
Note in mysql sum with some expression will result in a boolean
Edit from comments add another condition using OR in your last join so first condition will join the posts that are associated with tags ,and as your explanation tags are not directly linked with answers but answer are linked to their question with related id so can join the related id of each answer to tag id so this way can get the tags for answers too
SELECT
p.id,
p.user_id,
pt.post_id,
COUNT(pt.post_id) all_posts,
COALESCE(SUM(`type` = 'question')) questions,
COALESCE(SUM(`type` = 'answer')) answers,
pt.tag_id,
t.tagid,
t.tag_name
FROM tags t
LEFT JOIN post_tags pt ON(pt.tag_id = t.tagid)
LEFT JOIN posts p ON (p.id = pt.post_id OR p.related_id = pt.post_id)
WHERE p.user_id = '$uid'
GROUP BY t.tagid
I think you only need to include p.type in your group by clause
$user_active_query = mysql_query("
select
p.id,
p.user_id,
pt.post_id,
count(pt.post_id),
pt.tag_id,
t.tagid,
t.tag_name
from posts p
inner join post_tags pt
inner join tags t
on p.id = pt.post_id
and pt.tag_id = t.tagid
where p.user_id = '$uid'
group by t.tagid, p.type"
);
so, we will group per type too.
I'm not sure I fully understand your schema design. But it sounds like you have two join "paths", one to get to the question type posts, and another to get to child answer type posts.
To get the count of the question-type posts (by a specific user) related to each tag, looks like what you have so far, basically:
SELECT t.tagid
, t.tag_name
, COUNT(p.id) AS count_question
FROM tags t
JOIN post_tags pt
ON pt.tag_id = t.tagid
JOIN posts p
ON p.id = pt.post_id
AND p.type = 'question'
WHERE p.user_id = '$uid'
GROUP BY t.tagid
To get the count of the answer-type posts (by a specific user) related to a question related to each tag, we first need to join to the 'question' (to get the tags), and then join to the related answer. This is nearly identical to the first query, except that we add another join to posts table to get the "child" answer-type posts (so we can get a count of the answer-type posts), and we are not restricting the question-type posts to those from the specific user... we are going to count the answer-type posts from a user posted against any user's question.
SELECT t.tagid
, t.tag_name
, COUNT(a.id) AS count_answer
FROM tags t
JOIN post_tags pt
ON pt.tag_id = t.tagid
JOIN posts p
ON p.id = pt.post_id
AND p.type = 'question'
JOIN posts a
ON a.related_id = p.id
AND a.type = 'answer'
WHERE a.user_id = '$uid'
GROUP BY t.tagid
If each of those queries returns a portion of the total count you want to return, those can be combined, but it gets a little bit messy.
The most straightforward approach is to combine those two results with a UNION ALL set operator, and then use that as an inline view, i.e. run another query against the combined resultset.
For example:
SELECT r.tagid
, r.tagname
, SUM(r.count_post) AS count_total
, SUM(IF(r.type='q',r.count_post,0)) AS count_question
, SUM(IF(r.type='a',r.count_post,0)) AS count_answer
FROM (
SELECT 'q' AS type
, t.tagid
, t.tag_name
, COUNT(p.id) AS count_post
FROM tags t
JOIN post_tags pt
ON pt.tag_id = t.tagid
JOIN posts p
ON p.id = pt.post_id
AND p.type = 'question'
WHERE p.user_id = '$uid'
GROUP BY t.tagid
UNION ALL
SELECT 'a' AS type
, t.tagid
, t.tag_name
, COUNT(a.id) AS count_post
FROM tags t
JOIN post_tags pt
ON pt.tag_id = t.tagid
JOIN posts p
ON p.id = pt.post_id
AND p.type = 'question'
JOIN posts a
ON a.related_id = p.id
AND a.type = 'answer'
WHERE a.user_id = '$uid'
GROUP BY t.tagid
) r
GROUP BY r.tag_id
If you aren't interested in the individual counts (of question and answer type posts), then just remove those two expressions from the SELECT list of the outer query, and you can also remove the type='q', type='a' discriminator column from the inline view query.
This isn't the only way to combine the results, but I think it's the easiest way to verify we're getting a "correct" result (we can run just the inline view query and verify that the results from that are correct.
Another approach to combining them is messier, and more difficult to decipher.
We basically need to join to question-type posts from all users, and then do an outer join operation to the answer-type posts from the specific user.
We can use predicates in the WHERE clause to filter out the rows, so that return only rows that have a matching answer-type row -OR- are a question-type row poseted by the specified user.
In the SELECT list, we need to do some additional filtering, so that we filter out posts from other users.
Something like this:
SELECT t.tagid
, t.tag_name
, COUNT(DISTINCT IF(p.user_id='$uid',p.id,NULL))
+ COUNT(DISTINCT a.id) AS count_total
, COUNT(DISTINCT IF(p.user_id='$uid',p.id,NULL)) AS count_question
, COUNT(DISTINCT a.id) AS count_question
FROM tags t
JOIN post_tags pt
ON pt.tag_id = t.tagid
JOIN posts p
ON p.id = pt.post_id
AND p.type = 'question'
LEFT
JOIN posts a
ON a.related_id = p.id
AND a.type = 'answer'
AND a.user_id = '$uid'
WHERE p.user_id = '$uid'
OR a.id IS NOT NULL
GROUP BY t.tagid
But, I'd don't really like this query, it's too hard to figure out what's going on. I'd opt for the (previous) query, with the UNION ALL inline view. That's easier to decipher.

How to print posts with associated tags? [duplicate]

This is my database schema:
Post:
id
title
body
date
Tag:
id
title
Post_Tag:
id
id_post
id_tag
Comment:
id
id_post
body
date
There is a many to many relationship between post and tag.
I need to print in homepage this for the latest 10 posts:
POST_TITLE
POST_BODY
TAG_TITLE_1
TAG_TITLE_2
TAG_TITLE_3
COMMENTS_NUMBER
What is the best query to do that ?
I have tryed this but it doesn't work well because I get multiple rows for each post:
SELECT p.title, p.id, p.date, t.title, t.id, COUNT(c.id)
FROM post p
LEFT JOIN post_tag pt
ON p.id=pt.id_post
LEFT JOIN tag t
ON t.id=pt.id_tag
LEFT JOIN comment c
ON p.id=c.id_post
GROUP BY p.title, p.id, p.date, t.title
ORDER BY p.date DESC
You probably wouldn't want to do this as a single query, but in theory you could do.
SELECT
Post.id AS post_id,
Post.title AS post_title,
Post.body AS post_body,
GROUP_CONCAT(CONCAT(Tag.id, "|", Tag.title) SEPARATOR '#') AS tags,
COUNT(Comment.id) AS comment_count
FROM Post
LEFT JOIN Comment ON Post.id = Comment.id_post
LEFT JOIN Post_Tag ON Post.id = Post_Tag.id_post
LEFT JOIN Tag ON Tag.id = Post_Tag.id_tag
GROUP BY Post.id
ORDER BY Post.date ASC
I haven't checked this as I don't have access to your data, but it should work. You'll need to manually split the tags, which would appear in the format of "ID|TITLE#ID|TITLE", but that's the only extra processing required.
Alternatively, you can avoid the GROUP_CONCAT for tags by splitting this workload between two separate queries:
SELECT
Post.id AS post_id,
Post.title AS post_title,
Post.body AS post_body,
COUNT(Comment.id) AS comment_count
FROM Post
LEFT JOIN Comment ON Post.id = Comment.id_post
GROUP BY Post.id
ORDER BY Post.date ASC
From this, you'd store all of the individual post_ids, and use them in a second query as follows:
SELECT
Tag.id,
Tag.title
FROM Post_Tag
INNER JOIN Tag ON Post_Tag.id_tag = Tag.id
WHERE Post_Tag.id_post IN (**comma-separated list of Post IDs**)
This means you can do two queries, where otherwise you'd have to do one to get all of the posts, then another for EACH of those posts to retrieve the tags - THAT is an N+1 query, whereas what I propose above is a common way around the issue.
If I understand you correctly, you're trying to make a "posts with tags and comments"-system, with the following relationships
One query to rule them all
As suggested by Stephen Orr's answer, you should do a GROUP_CONCAT for the tags to avoid duplicate posts. You'd also have to do some minor post processing to format the tags, unless you just want the tag title, but that's about it.
Here's an example
SELECT
p.ID,
p.title,
p.body,
p.c_date,
GROUP_CONCAT(DISTINCT CONCAT_WS('|', CAST(t.ID AS CHAR), t.title) SEPARATOR ';') AS tags,
COUNT(c.ID) AS comments
FROM Post p
LEFT JOIN Comment c ON p.ID = c.id_post
LEFT JOIN Post_Tag pt ON p.ID = pt.id_post
LEFT JOIN Tag t ON pt.id_tag = t.ID
GROUP BY p.ID, p.title, p.body, p.c_date
ORDER BY p.c_date DESC
Note that I use explicit type-casting on the tag id.
Here are some references to GROUP_CONCAT and CONCAT_WS.

MySQL JOIN + SUBQUERY

With the following query:
SELECT p.*, i.*, GROUP_CONCAT(DISTINCT(pc.category_id)) AS post_categories, GROUP_CONCAT(DISTINCT(pt.tag_id)) AS post_tags
FROM posts AS p
LEFT JOIN images AS i ON p.post_image = i.image_id
LEFT JOIN post_categories AS pc ON p.post_id = pc.post_id
LEFT JOIN post_tags AS pt ON p.post_id = pt.post_id
WHERE p.post_url="'.$id.'"
I'm returning a list of blog posts, retrieving:
every column from the posts table
every column from the images table (for the post image)
a concatenation of all category_id's (for the post)
a concatenation of all tag_id's (for the post)
What this leaves me with, are two fields that aren't really helpful:
the concatenated category_id string (i.e. "1,3")
and the concatenated tag_id string (i.e. "2,5,8")
I would really like to do a subquery within both of the LEFT JOINS that allows me to, instead, return a list of:
category_name's (i.e. "meditation,sports")
tag_name's (i.e. "iOS,PHP,MySQL")
for each post.
I'm stuck right now on the subquery for both categories, and tags.
The table structures:
posts
post_id, post_title, etc.
categories
category_id, category_name
tags
tag_id, tag_name
post_categories
post_id, category_id
post_tags
post_id, tag_id
The question: is it possible to do such a thing effectively, if so, how?
If not, should I just store SELECT * FROM categories in a $categories array, then compare each category_id in the $categories array to each post's exploded category_id string in the $posts array each time?
You should be able to join to the tags and categories table, then just replace your concatenation with the name fields. (Also have to add Distinct to the select so as to not return duplicated main rows.)
SELECT DISTINCT p.*, i.*,
GROUP_CONCAT(DISTINCT(c.category_name)) AS post_categories,
GROUP_CONCAT(DISTINCT(t.tag_name)) AS post_tags
FROM posts AS p
LEFT JOIN images AS i ON p.post_image = i.image_id
LEFT JOIN post_categories AS pc ON p.post_id = pc.post_id
LEFT JOIN post_tags AS pt ON p.post_id = pt.post_id
LEFT JOIN categories AS c ON pc.category_id = c.category_id
LEFT JOIN tags AS t ON pt.tag_id = t.tag_id
WHERE p.post_url="'.$_GET['id'].'"
Then why not just grab the respective category name and tags description instead of their IDs in your GROUP_CONCAT

sql join problem

I want to select all posts that contain a specific tag. I'm trying it with this query:
SELECT GROUP_CONCAT(t.tag_name) taglist
FROM posts p
JOIN posts_tags pt ON p.post_id = pt.post_id
JOIN tags t ON t.tag_id = pt.tag_id
WHERE (p.post_private = 0) AND t.tag_name = 'php'
GROUP BY p.post_id
Problem is, the query above, selects all the posts that contain the php tag, but doesn't select any of the other tags a post may contain. Without the AND t.tag_name = 'php' part, it does select every tag a post has, but I want to be able to filter by tag...
Any ideas how to do this? I've tried many things, but can't figure it out...
Sample data without the AND statement:
|| *taglist* ||
|| php,echo ||
|| c++, cout ||
Sample data with the AND statement:
|| *taglist* ||
|| php ||
What I want:
|| *taglist* ||
|| php,echo ||
(the posts that contain the PHP tag only)
SELECT p.post_id, GROUP_CONCAT(t.tag_name) taglist
FROM posts p
/* These 2 joins get the list of all tags */
INNER JOIN posts_tags pt
ON p.post_id = pt.post_id
INNER JOIN tags t
ON pt.tag_id = t.tag_id
/* These 2 joins guarantee the 'php' tag is included */
INNER JOIN posts_tags pt2
ON p.post_id = pt2.post_id
INNER JOIN tags t2
ON pt2.tag_id = t2.tag_id
AND t2.tag_name = 'php'
WHERE p.post_private = 0
GROUP BY p.post_id
I'll try to explain why your first try does not work.
What you really trying to do is to find all posts that one of their tags is 'php'.
But their tags are spread in many rows so the t.tag_name = 'php' does not work as it filters out all rows that don't have the 'php' tag.
When you want to check a condition that depends on many rows,
you either create a subquery (to find all post_ids that do have php tag)
p.post_id IN
( SELECT pt2.post_id
FROM post_tags pt2
JOIN tags t2
ON t2.tag_id = pt2.tag_id
WHERE t2.tag_name = 'php'
)
but still join these post_ids with all related tags.
SELECT GROUP_CONCAT(t.tag_name) taglist
FROM posts p
JOIN posts_tags pt
ON p.post_id = pt.post_id
JOIN tags t
ON t.tag_id = pt.tag_id
WHERE (p.post_private = 0)
AND p.post_id IN
( SELECT pt2.post_id
FROM post_tags pt2
JOIN tags t2
ON t2.tag_id = pt2.tag_id
WHERE t2.tag_name = 'php'
)
GROUP BY p.post_id
Or you do it a bit more cleverly as Stefanelli showed with 2 more JOINs (that act similarly to a subquery)

Categories