MySQL JOIN + SUBQUERY - php

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

Related

relations in SELECT query (PHP)

I have little problem with SQL query in PHP.
I want to get from table post rows with category ex. 'art'.
This table don't contain name of my category (only category_id).
So, how to connect this in my query?
Tables:
post:
id, title, category_id
category:
id, name
I tried this way, but it not works.
SELECT * FROM post WHERE category_id = category.id AND category.name="art";
Anyone can help me? Thanks.
You need to join the tables.
Given your schema, this should give you an starting point:
SELECT p.* FROM post p INNER JOIN category c ON p.category_id = c.id WHERE c.name = 'art';
You are missing the JOIN
SELECT * FROM post
INNER JOIN category
WHERE category_id = category.id AND category.name="art";
You need to use join to query on name column of category table, e.g.:
SELECT p.*
FROM post p JOIN category c ON p.category_id = c.id
WHERE c.name = 'art';

Display categories with number of posts

I'm making a blog. I have 2 tables, one for posts and the other for categories.
I want to display the category name, category date and the number of posts in each category. I have problems to display the number of posts in each category.
In posts table I have a column called cat_id which is equal to category.id
I have these 2 MySQL queries:
mysql_query("select Count(posts.id) as NumberOfPosts, cat_id from posts group by cat_id");
And
mysql_query("select name, date from categories");
I don't know how to have combine these two queries into one query. I'm using PHP.
You could use a join:
SELECT name, date, NumberOfPosts
FROM categories c
JOIN (SELECT cat_id, COUNT(*) AS NumberOfPosts
FROM posts
GROUP BY cat_id) p ON c.id = p.cat_id
EDIT:
To include categories with no posts, you could use a left join instead of regular join. You just need to handle the nulls you'd get for NumberOfPosts, e.g., by using coalesce:
SELECT name, date, COALESCE(NumberOfPosts, 0) AS NumberOfPosts
FROM categories c
LEFT JOIN (SELECT cat_id, COUNT(*) AS NumberOfPosts
FROM posts
GROUP BY cat_id) p ON c.id = p.cat_id

Multiple tag search

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')

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.

How to query 3 tables in a single query?

I'm really sorry for the first post as i didn't explain everything.
Basically i have 3 tables, One for posts, One for Categories, & Another to link categories with posts.
I want in a single MySQL query to select posts that are under a specific category.
posts(id,title,body)
---------------------
125,Some title,Blah blah
categories(id,name)
---------------------
1,politic
2,entertainment
linker(categoryid,postid)
---------------------
2,125
I want in single query to fetch posts in the entertainment category by example, what to do?
Thanks
select
p.*
from
posts p
inner join linker l on l.postid = p.id
inner join categories c on c.categoryid = l.categoryid
where
c.name = 'entertainment'
The following SQL statement should provide you with a basis for what you are trying to do.
select p.*
from posts p
inner join linker l
on l.postid = p.id
inner join categories c
on l.categoryid = c.id
where c.name = 'entertainment'
If a post belongs to 2 categories, you can still use pinkfloydx33's query with DISTINCT in the select statement:
select
DISTINCT p.*
from
posts p
inner join linker l on l.postid = p.id
inner join categories c on c.categoryid = l.categoryid
where
c.name = 'entertainment'
The result set will show only one record.
It's the same exact thing, you just have to join 3 tables intead of 2 :
SELECT P.id post_id,
P.title,
P.body,
C.id category_id,
C.name
FROM posts P
INNER JOIN linker L
ON P.id = L.postid
INNER JOIN categories C
ON L.categoryid = C.id
WHERE C.name = 'Category'
Don't be afraid to do your own tests. If you understand how to join two tables, you should understand how to join three, four and more.
If you are specifying only one category in the WHERE clause, then the result will be a single row for each post ID.
Either way you can use DISTINCT or GROUP BY when the result could be more than one row per ID, but in that case i prefer the second one (GROUP BY).

Categories