MySQL Subselect Limit - php

What im trying to do was to list all categories and their posts but only limit posts per category. And exclude a category without any posts.
I did this with two queries though, get all the categories that have posts, loop the results and get X number of posts per category ID.
How can I do this in just 1 query?
EDIT: this is what I accomplished so far..
SELECT p.post_id, c.category_id
FROM category as c
JOIN posts AS p ON p.category_id = c.category_id
WHERE
FIND_IN_SET(p.post_id, (
SELECT SUBSTRING_INDEX(a.post_ids, ',', 10)
FROM
(
SELECT GROUP_CONCAT(b.post_id) AS post_ids, b.category_id
FROM posts as b
GROUP BY b.category_id
) AS a
WHERE a.category_id = c.category_id
))

To exclude all categories without any posts do an inner join between category and post. If you want to limit the number of rows returned, use the LIMIT command.

how about something like this
SELECT `category_name`
FROM `categories`
WHERE `posts` !=0
LIMIT 0 , 30

Related

How to display several subcategories and their news in one query?

I have a news site and I am trying to include a block of news in it.
see the image please
I created a category call world news and added subcategories. (Travel, News, Art, Bussines)
I display them one by one, I mean I do a query for each news, that means 4 different queries like below :
$sql = "SELECT posts.post_catId,
posts.post_seo_url,
posts.post_desc,
posts.post_type,
posts.post_status,
posts.post_title,
posts.post_image_url,
categories.catId,
categories.catName,
categories.cat_seo_url
FROM posts
LEFT JOIN categories
ON posts.post_catId = categories.catId
WHERE post_catId = catId AND cat_seo_url = 'art'
AND post_status = ?
ORDER BY post_created_at DESC LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute(['1',]);
if($stmt->rowCount() > 0){
while($row = $stmt->fetch()){
//here
}
}
My question is: is there a way to display them in one query ?
Edit : I want to display 1 news from 4 specific categories with one query instead of 4.
I know I can add a new row to categories table to secify which categories can be displayed.
You 'invert' the query, selecting the categories first and then joining to the posts table, constraining it by the post_seo_url field, like so:
$sql = "SELECT
categories.catId,
categories.catName,
categories.cat_seo_url,
posts.post_catId,
posts.post_seo_url,
posts.post_desc,
posts.post_type,
posts.post_status,
posts.post_title,
posts.post_image_url,
FROM categories
JOIN posts ON posts.post_seo_url = (
SELECT p.post_seo_url FROM posts as p
WHERE categories.catId = p.post_catId
ORDER BY p.post_created_at DESC LIMIT 1
)
WHERE post_status = ?"
Here's a method using ROW_NUMBER() function:
SELECT p.*,
categories.catId,
categories.catName,
categories.cat_seo_url
FROM
(SELECT
ROW_NUMBER() OVER (PARTITION BY posts.post_catId ORDER BY posts.post_created_at DESC) AS RN,
posts.post_catId,
posts.post_seo_url,
posts.post_desc,
posts.post_type,
posts.post_status,
posts.post_title,
posts.post_image_url
FROM posts
WHERE post_status = ? ) p
JOIN categories
ON p.post_catId = categories.catId
WHERE RN=1;
I've made the query on posts table to become a subquery with addition of ROW_NUMBER() function then JOIN it with categories table and added WHERE that only return row number = 1. I've changed the LEFT JOIN to JOIN because I don't see any reason to use LEFT JOIN in this operation. However, if there is a reason, please update it in your question.
Here's a demo fiddle

join and group by not working properly in laravel

I have a blog with 2 table. First posts table with a key category_id and categories table. In a page I want to show all categories with count of their posts. I write this query but it have a problem. It did not show categories with no posts. in other word categories with 0 posts did not appear in the results.
And can you help to write this in eloquent system in laravel
SELECT categories.* , Count(posts.total) as total
FROM categories
LEFT JOIN
(
SELECT * , COUNT(*) as total from posts GROUP By posts.id
) as posts
ON posts.category_id = categories.id
Try following query:
SELECT categories.* , IFNULL(Count(posts.id),0) as total
FROM categories
LEFT JOIN
posts
ON posts.category_id = categories.id
Group by categories.id;

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

MySQL - WHERE / HAVING count

I searched lots of similar topics about this but can't find the answer to this specific probem.
So I have a table with categories and I have another table with products, so what I want is select all the categories that contain at least 1 product, seems very easy but the following code dont give me what I expect.
SELECT *
FROM categories
INNER JOIN products on (categories.id = products.cat_id)
HAVING count(products.cat_id) > 0
All help is appreciated.
Thank you!
You can actually get what you want with just a join because any categories without products will not match.
You should also add a DISTINCT or GROUP BY to remove the duplicate category records from the results:
SELECT DISTINCT c.*
FROM categories c
JOIN products p
ON c.id = p.cat_id
OR:
SELECT c.*
FROM categories c
JOIN products p
ON c.id = p.cat_id
GROUP BY c.id
If you want something fancy like categories that have 2 or more products then you can use GROUP BY and HAVING:
SELECT c.*
FROM categories c
JOIN products p
ON c.id = p.cat_id
GROUP BY c.id
HAVING count(*) >= 2
Try this select in this select all categories that have at least one product
select categories.* from categories
left join products on (categories.id = products.cat_id)
where products.cat_id IS NOT NULL
group by categories.id
In this case you're not using "HAVING" but will have the same result

MySql inner join takes more than 10 seconds

I have two tables posts and followings
posts (id,userid,post,timestamp) 30 000 rows
and
followings(id_me,userid) 90 000 rows
I want to get lattest 10 posts form posts table based on the people i follow and my posts
SELECT p.*
FROM posts as p INNER JOIN
followings as f
ON (f.id_me=(my user id) AND p.userid=f.userid )
OR
p.userid=(my user id)
ORDER BY id DESC LIMIT 10
But it takes about 10-15 seconds to return. Thanks in advance!
First, remove the filter from the join clause, let the join just correlate the joining tables.
(
SELECT p.*
FROM posts as p
INNER JOIN followings as f ON p.userid=f.userid
where f.id_me=(my user id)
UNION
SELECT p.*
FROM posts as p
where p.userid=(my user id)
)
ORDER BY id DESC LIMIT 10
second, verify your indexes if that ids got no indexes it ill perform a full table scan for each cartesian product of both tables (30k x 90k =~ 3700k pairs being compared)
third, if you don't follow yourself you need a union from post you are following and your posts
Using an OR in SQL is a performance killer, try this:
SELEC p.*
FROM posts as p INNER JOIN
followings as f
ON (f.id_me=(my user id) AND p.userid IN (f.userid,(my user id)))
ORDER BY id DESC LIMIT 10
Do this query using union:
(SELECT p.*
FROM posts p INNER JOIN
followings f
ON (f.id_me=(my user id) AND p.userid=f.userid
)
union
(select p.*
from posts p
where p.userid=(my user id)
)
ORDER BY id DESC
LIMIT 10
If the two conditions never overlap, then use union all instead.
An OR condition like that prevents the query optimizer from making use of indexes. Use a UNION instead:
SELECT *
FROM (SELECT p.*
FROM posts as p
INNER JOIN followings as f
ON f.id_me=(my user id) AND p.userid=f.userid
UNION
SELECT *
FROM posts
WHERE userid = (my user id)) u
ORDER BY id DESC
LIMIT 10
It might be just me but I think your WHERE clause is in an inefficient location:
SELECT
p.*
FROM
posts p
INNER JOIN
followings f
ON p.userid=f.userid
WHERE
MyUserID IN (p.userid, f.id_me)
ORDER BY
id DESC
LIMIT
10
I read in comments that you have the required indexes. The problem is the query. Combining OR with a JOIN confuses the poor and (often) dumb optimizer. The LIMIT 10 should be helpful but the optimizer is not (yet) smart enough to make the best plan.
Try this query:
( SELECT p.*
FROM posts AS p
JOIN followings AS f
ON f.id_me = (my_user_id)
AND p.userid = f.userid
ORDER BY p.id DESC
LIMIT 10
)
UNION ALL
( SELECT p.*
FROM posts AS p
WHERE p.userid = (my_user_id)
ORDER BY p.id DESC
LIMIT 10
) AS x
ORDER BY id DESC
LIMIT 10 ;

Categories