select n posts per category in a single query - php

In my home page I show different posts from different categories.
My current implementation is to call query_posts many times (once per category)
How can I use one query to pull out the data?
My tries
1 - this method works(ignore the ugly very long sql,I have many categories...)
Thanks the post: http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/
( SELECT *
FROM `wp_posts` p,`wp_term_relationships` rel
WHERE rel.object_id = p.ID
AND rel.term_taxonomy_id = '3'
ORDER BY p.post_date DESC
LIMIT 2)
UNION ALL
( SELECT *
FROM `wp_posts` p,`wp_term_relationships` rel
WHERE rel.object_id = p.ID
AND rel.term_taxonomy_id = '4'
ORDER BY p.post_date DESC
LIMIT 2)

SELECT i.*
FROM wp_term_relationships rel
INNER JOIN (
SELECT p.*, rel2.*
FROM wp_posts p
INNER JOIN wp_term_relationships rel2
ON (rel2.object_id = p.ID)
WHERE rel2.term_taxonomy_id = rel.term_taxonomy_id
ORDER BY p.post_date DESC
LIMIT 2 OFFSET 0 ) i ON (i.object_id = rel.object_id)
WHERE rel.term_taxonomy_id IN ('3','4')
ORDER BY i.term_taxonomy_id ASC, i.post_date DESC

Related

MYSQL Query very slow when using ORDER BY datetime

I have the following query which runs on a social network. The query fetches posts (like Facebook posts) from a database.
SELECT P.*,
P.id_post id_p,
PM.meta_content video_title,
PM2.meta_content video_views,
PM3.meta_content racebooking_views,
Greatest(P.creation_date, Coalesce(Max(C.date), P.creation_date)) AS
last_activity,
P.creation_date creation_date,
(SELECT Count(*)
FROM likes
WHERE post_id = P.id_post
AND post_type = 'P')
likes_count,
(SELECT Count(*)
FROM likes L
WHERE post_id = P.id_post
AND post_type = 'P'
AND L.id_profile = 2796)
do_i_like
FROM posts P
LEFT JOIN comments C
ON P.id_post = C.post_id
AND C.post_type = 'P'
AND C.id_profile != P.id_profile
LEFT JOIN post_meta PM
ON PM.id_post = P.id_post
AND PM.meta_type = 'T'
LEFT JOIN post_meta PM2
ON PM2.id_post = P.id_post
AND PM2.meta_type = 'V'
LEFT JOIN post_meta PM3
ON PM3.id_post = P.id_post
AND PM3.meta_type = 'W'
GROUP BY P.id_post
ORDER BY last_activity DESC
LIMIT 41, 10
Each post may have or may not have comments.
I want the query to fetch the post with the most recent activity first.
So, if the post has a comment, i take the date of the latest comment. If the post does not have a comment, i take the creation date of the post.
The job is done by Greatest(P.creation_date, Coalesce(Max(C.date), P.creation_date)) which picks up the greates value between the comments dates (if comments exist) and the post creation date.
Then, the ORDER BY last_activity DESC does the sorting job.
PROBLEM
The query is really slow. It takes 8 seconds to run. The posts table has 8K rows and the comments table has 8K rows.
What i don't understand is that if I replace the ORDER BY clause with this ORDER BY P.id_post it takes 0.5 seconds to run. But if I replace the ORDER BY clause with ORDER BY P.creation_date again it takes 8 seconds. It seems that it doesn't like dates...
Additional infos
posts table has an index on creation_date.
comments table has an index on date
server runs LAMP on CentOS Linux 6.6
I tried other solutions on SO like this one but they didn't work
How can i fix this query to run faster?
The correlated subqueries in the select clause are probably killing you. Instead, join to a subquery which computes likes statistics:
SELECT P.*,
P.id_post id_p,
PM.meta_content video_title,
PM2.meta_content video_views,
PM3.meta_content racebooking_views,
GREATEST(P.creation_date, COALESCE(MAX(C.date), P.creation_date)) AS last_activity,
P.creation_date creation_date,
t.likes_count,
t.do_i_like
FROM posts P
LEFT JOIN
(
SELECT
post_id,
SUM(CASE WHEN post_type = 'P' THEN 1 ELSE 0 END) AS likes_count,
SUM(CASE WHEN post_type = 'P' AND L.id_profile = 2796
THEN 1 ELSE 0 END) AS do_i_like
FROM likes
GROUP BY post_id
) t
ON t.post_id = P.id_post
LEFT JOIN comments C
ON P.id_post = C.post_id AND
C.post_type = 'P' AND
C.id_profile != P.id_profile
LEFT JOIN post_meta PM
ON PM.id_post = P.id_post AND
PM.meta_type = 'T'
LEFT JOIN post_meta PM2
ON PM2.id_post = P.id_post AND
PM2.meta_type = 'V'
LEFT JOIN post_meta PM3
ON PM3.id_post = P.id_post AND
PM3.meta_type = 'W'
ORDER BY
last_activity DESC
LIMIT 41, 10
Also after editing your query I do not see a reason to be using GROUP BY in the outer query, so I removed it. And you should be using indices where appropriate, though my hunch is that my suggestion alone should give a noticeable performance boost.
There is a MAX(C.Date) that would require a group by clause, however it too could be substituted for a subquery I believe:
SELECT P.*,
P.id_post id_p,
PM.meta_content video_title,
PM2.meta_content video_views,
PM3.meta_content racebooking_views,
GREATEST(P.creation_date, COALESCE(max_c_date, P.creation_date)) AS last_activity,
P.creation_date creation_date,
t.likes_count,
t.do_i_like
FROM posts P
LEFT JOIN
(
SELECT
post_id,
SUM(CASE WHEN post_type = 'P' THEN 1 ELSE 0 END) AS likes_count,
SUM(CASE WHEN post_type = 'P' AND L.id_profile = 2796
THEN 1 ELSE 0 END) AS do_i_like
FROM likes
GROUP BY post_id
) t
ON t.post_id = P.id_post
LEFT JOIN (
SELECT
comments.post_id,
MAX(comments.date) max_c_date
FROM comments
inner join posts ON comments.post_id = posts.id_post
where comments.post_type = 'P' AND
comments.id_profile != posts.id_profile
GROUP BY comments.post_id
) C
ON P.id_post = C.post_id AND
LEFT JOIN post_meta PM
ON PM.id_post = P.id_post AND
PM.meta_type = 'T'
LEFT JOIN post_meta PM2
ON PM2.id_post = P.id_post AND
PM2.meta_type = 'V'
LEFT JOIN post_meta PM3
ON PM3.id_post = P.id_post AND
PM3.meta_type = 'W'
ORDER BY
last_activity DESC
LIMIT 41, 10

Select with third table by id

Mysql Detail:
Table A (name: post): id
Table B (name: post_has_relate) : post_id, object_id
Could I get the post in table A with same object_id that post_id is const? I try to write with a sub-query. Could I can write it with a join?
SELECT
p.sumary AS sumary,
p.source_link AS source_link,
p.source_name AS source_name,
p.created AS created,
p.id AS id,
p.slug AS slug
FROM
post_has_relate AS pr
LEFT JOIN post as p ON p.id = pr.post_id
WHERE
pr.object_id IN (
SELECT
post_has_relate.object_id AS object_id
FROM
post_has_relate
WHERE
post_has_relate.post_id = 1052
)
AND pr.post_id != 1052
AND p. STATUS = 1
GROUP BY
p.id
ORDER BY
p.created DESC
LIMIT 5 OFFSET 0
I think you could achieve the same result with a join:
SELECT
p.sumary AS sumary,
p.source_link AS source_link,
p.source_name AS source_name,
p.created AS created,
p.id AS id,
p.slug AS slug
FROM
post_has_relate AS pr
LEFT JOIN post as p ON p.id = pr.post_id
JOIN post_has_relate AS pr2
ON pr2.object_id=pr.object_id AND pr2.post_id=1052
WHERE
pr.post_id != 1052
AND p.STATUS = 1
GROUP BY
p.id
ORDER BY
p.created DESC
LIMIT 5 OFFSET 0

MySQL INNER JOIN ordering by multiple matches first

I have the following tables
gifts - a list of products
tags - a list of tags that can be applied to products
tags_gifts - a join for gifts and tags if they are applicable
Here's two tag examples (id, name):
508 - jewellery
7 - gold
I have the following SQL for results:
SELECT * FROM gifts
LEFT JOIN tags_gifts ON tags_gifts.gift_id = gifts.gift_id
INNER JOIN tags ON tags.id = tags_gifts.tag_id
WHERE published = '1' AND ( (tags_gifts.tag_id = '508' OR tags_gifts.tag_id = '7') )
GROUP BY gifts.gift_id
ORDER BY gift_popularity DESC LIMIT 0,20
This works fine and shows all results with matches of 'jewellery' or 'gold' and orders them by popularity, but I'd like to show multiple matches first eg. A product matches both tags, and then order the remaining products by popularity.
Can't figure out how to do this - thanks
You can use COUNT() in ORDER BY ,so if count returns 2 it means product has both tags
SELECT * FROM gifts
LEFT JOIN tags_gifts ON tags_gifts.gift_id = gifts.gift_id
INNER JOIN tags ON tags.id = tags_gifts.tag_id
WHERE published = '1' AND ( (tags_gifts.tag_id = '508' OR tags_gifts.tag_id = '7') )
GROUP BY gifts.gift_id
ORDER BY COUNT(*) DESC , gift_popularity DESC
LIMIT 0,20
This will work and will be fast, you can adjust "a LIMIT 20 OFFSET 0" as you wish.
SELECT a.* FROM (
SELECT * FROM gifts
LEFT JOIN tags_gifts ON tags_gifts.gift_id = gifts.gift_id
INNER JOIN tags ON tags.id = tags_gifts.tag_id
WHERE published = '1' AND ( (tags_gifts.tag_id = '508' AND tags_gifts.tag_id = '7') )
GROUP BY gifts.gift_id
ORDER BY gift_popularity DESC
) UNION (
SELECT * FROM gifts
LEFT JOIN tags_gifts ON tags_gifts.gift_id = gifts.gift_id
INNER JOIN tags ON tags.id = tags_gifts.tag_id
WHERE published = '1' AND ( (tags_gifts.tag_id = '508' OR tags_gifts.tag_id = '7') )
GROUP BY gifts.gift_id
ORDER BY gift_popularity DESC
) a LIMIT 0,20

SELECT LEFT JOIN with LIMIT

I have a problem putting a limit on the number of rows from my Jokes table.
This is my query working, getting all rows:
SELECT Jokes.ID, Categories.CategoryName, Jokes.CategoryID, Jokes.JokeText
FROM Jokes
LEFT JOIN Categories
ON Jokes.CategoryID = Categories.ID
ORDER BY Jokes.ID DESC
Would it be something like?
SELECT Jokes.ID, Categories.CategoryName, Jokes.CategoryID, Jokes.JokeText
FROM (
SELECT * FROM Jokes ORDER BY ID DESC Limit 0,40)
AS a
LEFT JOIN Categories
AS b
ON a.CategoryID = b.ID
why not using
SELECT Jokes.ID, Categories.CategoryName, Jokes.CategoryID, Jokes.JokeText
FROM Jokes
LEFT JOIN Categories
ON Jokes.CategoryID = Categories.ID
ORDER BY Jokes.ID DESC
Limit 0,40

SUM of a secondSELECT statement - PHP - MySQL

I have the following query which works... but I need some changes:
SELECT
p.pid,
p.name,
GROUP_CONCAT( gC.leaguepoints ORDER BY leaguepoints DESC ) AS leaguepoints
FROM
golf_player p
LEFT JOIN
golf_card gC ON
p.pid = gC.pid
GROUP BY
p.pid
ORDER BY
p.name
DESC
What I really want is another property returned called totalleaguepoints which is a SUM of the most recent 20 league points in my table.
How do I add a limit to a group_concat?
It might be something like this?
SELECT
p.pid,
p.name,
GROUP_CONCAT( gC.leaguepoints ORDER BY leaguepoints DESC ) AS leaguepoints,
SUM(
SELECT
gC2.leaguepoints
FROM
golf_card gC2
WHERE
gC2.pid = p.pid
ORDER BY
leaguepoints
DESC
LIMIT 20
) AS totalleaguepoints
FROM
golf_player p
LEFT JOIN
golf_card gC ON
p.pid = gC.pid
GROUP BY
p.pid
ORDER BY
p.name
DESC
P.S, the above query does not run
Do you want just the top 20 league points for each player in the group_concat as well as the sum?
If so maybe use user variables:-
SELECT
p.pid,
p.name,
GROUP_CONCAT( gC.leaguepoints ORDER BY leaguepoints DESC ) AS leaguepoints,
SUM(gC.leaguepoints) AS totalleaguepoints
FROM golf_player p
LEFT JOIN
(
SELECT pid, leaguepoints, #Sequence:=IF(#PrevPid = pid, #Sequence + 1, 0) AS aSequence, #PrevPid := pid
FROM
(
SELECT pid, leaguepoints
FROM golf_card
ORDER BY pid, leaguepoints DESC
) Sub1
CROSS JOIN (SELECT #PrevPid := 0, #Sequence := 0) Sub2
) gC
ON p.pid = gC.pid AND aSequence < 20
GROUP BY p.pid
ORDER BY p.name DESC

Categories