WP_Query: second parameter in meta_query being ignored - php

The second meta_query parameter is being ignored in this query.
$query = new WP_Query(array(
'post_type' => 'events',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'event_date',
'value' => date('Ymd'),
'compare' => '>=',
),
array(
'key' => 'end_date',
'value' => date('Ymd'),
'compare' => '>=',
)
)
));
There is no mention of end_date in the resulting SQL
SELECT SQL_CALC_FOUND_ROWS inx9uju_posts.ID
FROM inx9uju_posts
INNER JOIN inx9uju_postmeta ON ( inx9uju_posts.ID = inx9uju_postmeta.post_id )
INNER JOIN inx9uju_postmeta AS mt1 ON ( inx9uju_posts.ID = mt1.post_id )
WHERE 1=1
AND ( inx9uju_postmeta.meta_key = 'event_date' AND ( mt1.meta_key = 'event_date' AND CAST(mt1.meta_value AS SIGNED) > '20180703' ) )
AND inx9uju_posts.post_type = 'events'
AND (inx9uju_posts.post_status = 'publish'
OR inx9uju_posts.post_status = 'acf-disabled'
OR inx9uju_posts.post_status = 'private')
GROUP BY inx9uju_posts.ID
ORDER BY inx9uju_postmeta.meta_value+0 ASC LIMIT 0, 30
I've spent some time checking syntax and referring to the codex but I can't see what is wrong with my code. I can't see any reason why it would be ignoring the end_date part of the meta_query parameters.
UPDATE
In this particular case I was overriding the meta_query part of the query in a separate function which was hooking into pre_get_posts. Once I updated that function, the issue was resolved.

If this is stored as an AFC, you must use the ACF syntax of Ymd (20180703).
You must also realise that WordPress can detect the BETWEEN meta_compare to detect two dates as such.

Related

WordPress: Heavy slow query

In a client website that should display upcoming territory events I got a plugin (called EventsManager), that has been modified to fit our needs.
That plugin, by the way, got an huge query searching for the current events by two parameters, date_start and date_end.
This query affect only one page, and only one function.
$args_filter = $tax_query = array();
$paged = '';
$args_basic = $this->ovaem_query_base($paged, $orderby, $order, $show_past);
switch ($filter) {
[...]
case 'upcomming_showing':
$args_filter = array(
'posts_per_page' => $count,
'meta_query' => array(
array(
'relation' => 'OR',
array(
'key' => self::$prefix . '_date_start_time',
'value' => current_time('timestamp'),
'compare' => '>',
),
array(
'relation' => 'AND',
array(
'key' => self::$prefix . '_date_start_time',
'value' => current_time('timestamp'),
'compare' => '<',
),
array(
'key' => self::$prefix . '_date_end_time',
'value' => current_time('timestamp'),
'compare' => '>=',
),
),
),
),
);
break;
[...]
$args = array_merge_recursive($args_basic, $args_filter, $tax_query);
$eventlist = new WP_Query($args);
return $eventlist;
The resultant query, analized with QueryMonitor is:
SELECT SQL_CALC_FOUND_ROWS wpzt_posts.ID
FROM wpzt_posts
INNER JOIN wpzt_postmeta
ON ( wpzt_posts.ID = wpzt_postmeta.post_id )
INNER JOIN wpzt_postmeta AS mt1
ON ( wpzt_posts.ID = mt1.post_id )
INNER JOIN wpzt_postmeta AS mt2
ON ( wpzt_posts.ID = mt2.post_id )
INNER JOIN wpzt_postmeta AS mt3
ON ( wpzt_posts.ID = mt3.post_id )
WHERE 1=1
AND ( wpzt_postmeta.meta_key = 'ovaem_date_start_time'
AND ( ( ( mt1.meta_key = 'ovaem_date_start_time'
AND CAST(mt1.meta_value AS SIGNED) BETWEEN '1643155199'
AND '1643241601' )
OR ( ( mt2.meta_key = 'ovaem_date_start_time'
AND mt2.meta_value < '1643155200' )
AND ( mt3.meta_key = 'ovaem_date_end_time'
AND mt3.meta_value >= '1643155200' ) ) ) ) )
AND wpzt_posts.post_type = 'event'
AND ((wpzt_posts.post_status = 'publish'))
GROUP BY wpzt_posts.ID
ORDER BY wpzt_postmeta.meta_value+0 ASC
LIMIT 0, 9
Using MySQL 5.7 and memcached, this query got 16,69 seconds to be executed.
Is there a way to optimize that?
Thanks
How the query comes out of set of code is unclear. But a direct query (inserting your parameters as applicable) could be simplified to
SELECT SQL_CALC_FOUND_ROWS
p.ID
FROM
wpzt_posts p
JOIN wpzt_postmeta mtStart
ON p.ID = mtStart.post_id
AND mtStart.meta_key = 'ovaem_date_start_time'
LEFT JOIN wpzt_postmeta mtEnd
ON p.ID = mtEnd.post_id
AND mtEnd.meta_key = 'ovaem_date_end_time'
AND mtEnd.meta_value >= '1643155200'
WHERE
p.post_type = 'event'
AND p.post_status = 'publish'
AND (
mtStart.meta_value BETWEEN '1643155199' AND '1643241601'
OR ( mtStart.meta_value < '1643155200'
AND mtEnd.meta_value > '1643155200'
)
GROUP BY
p.ID
ORDER BY
mtStart.meta_value + 0 ASC
LIMIT
0, 9
So, not knowing specifically about WordPress and how you have it configured, Does your data allow for multiple start and end time entries for a SINGLE POST ID? If so, that will result in a Cartesian Result thus over multiplying the instances.
Are you trying to count how many UNIQUE Post IDs are found, or total records found for all the meta-data entries associated with said posts.
For indexes, I would suggest having the following
Table Index on
wpzt_posts ( post_type, post_status, id )
wpzt_postmeta ( post_id, meta_key, meta_value )
I removed the CAST(meta_value AS SIGNED) since the records associated with date/time context will always be numeric, its not like you would ever encounter a time of 'this is bad data' and thus influence a bad numeric conversion. As such, the function call makes it non-sargeable and thus will not utilize an index. But if all instances of a start/end time are all digits, you wont have that confusion -- unless the physical lengths of those numeric representations actually are longer or shorter, but even in that case, a smaller number would probably indicate a date sooooo far back, it would not even be in the range being considered and thus no impact.

Use wp_query with multiple post types but apply meta_query on only one post_type

I would like to wp_query all my given post_types and apply a filter on one of the four post_types without excluding posts of the other post_types.
For example, I would like to find all posts that have P110612 in it. This code is the WooCommerce sku.
So in order for me to query on the _sku key, I apply this as a meta_query.
But, in order for me to not exclude posts without the meta key _sku, I can make an OR relation, like so:
'meta_query' => [
'relation' => 'OR',
[
'key' => '_sku',
'value' => 'P110612',
'compare' => 'LIKE',
],
[
'key' => '_sku',
'value' => 'completely',
'compare' => 'NOT EXISTS',
],
];
What this should do is show all product posts with sku P110612 and all non-product posts where P110612 is somewhere in the content. But, the result is nothing.
Below you'll find the actual query that is being executed by WordPress WP_Query.
I hope you guys can help me out!
SELECT prefix_posts.*
FROM prefix_posts
LEFT JOIN prefix_postmeta ON ( prefix_posts.ID = prefix_postmeta.post_id )
LEFT JOIN prefix_postmeta AS mt1 ON (prefix_posts.ID = mt1.post_id AND mt1.meta_key = '_sku' )
WHERE 1=1
AND prefix_posts.ID NOT IN (2,10,11,12)
AND (
(
(prefix_posts.post_title LIKE '%P110612%')
OR (prefix_posts.post_excerpt LIKE '%P110612%')
OR (prefix_posts.post_content LIKE '%P110612%')
)
)
AND (prefix_posts.post_password = '')
AND (
( prefix_postmeta.meta_key = '_sku' AND prefix_postmeta.meta_value = 'P110612' )
OR mt1.post_id IS NULL
)
AND prefix_posts.post_type IN ('page', 'newsitem', 'partner', 'product')
AND ((prefix_posts.post_status = 'publish'))
GROUP BY prefix_posts.ID
ORDER BY prefix_posts.post_title LIKE '%P110612%' DESC, prefix_posts.post_date DESC

Wordpress: Bbpress add extra filter to the popular view

I am currently working on a Wordpress website with a Bbpress forum installed. I am using a shortcode to add all the popular topics on a (non-forum)page:
[bbp-single-view id="popular"]
I want to add a extra filter to this function and I can't figure out how to do this. I would like to filter on the meta value '_bbp_last_active_time', to check if there was any activity on a topic in the last month.
I found this script in the bbpress core, but I don't know how I can add another filter:
// bbpress.php line 672
bbp_register_view(
'popular',
__( 'Most popular topics', 'bbpress' ),
apply_filters( 'bbp_register_view_popular', array(
'meta_key' => '_bbp_reply_count',
'max_num_pages' => 1,
'orderby' => 'meta_value_num',
'show_stickies' => false
)
) );
I did try something like this with SQL and a custom shortcode, but I don't know how to convert this to a list of topics with the correct layout functions:
SELECT
wp_posts.post_parent, COUNT(*) as count, wp_postmeta.*
FROM
wp_posts, wp_postmeta
WHERE
wp_posts.post_type = 'reply'
AND
wp_postmeta.post_id = wp_posts.post_parent
AND
wp_postmeta.meta_key = '_bbp_last_active_time'
AND
wp_postmeta.meta_value > (NOW() - INTERVAL 1 MONTH)
GROUP BY
wp_posts.post_parent
ORDER BY
count DESC
LIMIT 5;
Thanks in advance! Help is much appreciated!
I already found a solution:
$date_now = date( 'Y-m-d H:i:s' );
$date=date_create($date_now);
date_sub($date,date_interval_create_from_date_string("30 days"));
$new_date = date_format($date,"Y-m-d H:i:s");
bbp_register_view(
'jaappopular',
__( 'Most popular topics', 'bbpress' ),
apply_filters( 'bbp_register_view_popular', array(
'meta_key' => '_bbp_reply_count',
'max_num_pages' => 1,
'orderby' => 'meta_value_num',
'show_stickies' => false,
'meta_query' => array(
'relation' => 'AND',
array(
'key' => '_bbp_last_active_time' ,
'value' => $new_date,
'compare' => '>=',
)
),
)
) );
);
YEAH!

Querying sanatized data with meta_query in Wordpress

After Googeling this for a while, I've found that this should work ref., but I don't seem to get it running.
On the backend I have a custom field on commments for setting a list of userIDs of who liked it. It is stored sanatized in the DB this way:
a:2:{i:0;s:1:"1";i:1;s:1:"2";}
which translates into
Array (
[0] => 1
[1] => 2
)
A pretty straight forward flat array where the values 1 and 2 are the user IDs of two likes. Now, I want to query comments liked by userID 1
$args = array(
'status' => 'approve',
'post_status' => 'publish',
'meta_query' => array(
array(
'key' => 'liked_by',
'value' => array(1),
'compare' => 'IN',
'type' => 'numeric'
)
)
);
// The Query
$comments_query = new WP_Comment_Query;
$comments = $comments_query->query( $args );
And I get no comments in return, why is that?
Here is the query generated:
SELECT * FROM wp_comments JOIN wp_posts ON wp_posts.ID = wp_comments.comment_post_ID INNER JOIN wp_commentmeta ON ( wp_comments.comment_ID = wp_commentmeta.comment_id ) WHERE ( comment_approved = '1' ) AND wp_posts.post_status = 'publish' AND (
( wp_commentmeta.meta_key = 'liked_by' AND CAST(wp_commentmeta.meta_value AS SIGNED) IN ('1') )
) GROUP BY wp_comments.comment_ID ORDER BY comment_date_gmt DESC
I wanted to do this over serialized data even though I cited an URL that said
don't store array of ids in one row instead loop through ids array and
normalize your likes data manually Don't serialize data into a
database field.
This is because I used ACF and added an Users field, which used serialized data. I solved my problem by avoiding ACF and just using the comment meta API.
// UserID 1 likes commentID 9
add_comment_meta(9, 'like', 1, false);
// Get comments liked by userID 1
$args = array(
'status' => 'approve',
'post_status' => 'publish',
'meta_query' => array(
array(
'key' => 'like',
'value' => 1,
'compare' => '=',
'type' => 'numeric'
)
)
);
// The Query
$comments_query = new WP_Comment_Query;
$comments = $comments_query->query( $args );
// Surprise, it's commentID 9
Notice how I use add_comment_meta with unique=false for every like instead of serializing an array into the database, making it unavailable for queries.

SQL Query for multiple values and multiple keys in the same table

Every post has two post meta: year and month (custom)
I need SQL query to pull all posts that have
wp_postmeta.meta_key = year AND wp_postmeta.meta_value = 2013
AND AT THE SAME TIME another meta_key / meta_value pair
wp_postmeta.meta_key = month AND wp_postmeta.meta_value BETWEEN 1 AND 12 ;
SELECT * FROM wp_postmeta
WHERE (meta_key = 'agam_post_options_year' AND meta_value = 2013)
OR (meta_key = 'agam_post_options_month' AND meta_value BETWEEN 0 AND 12 )
GROUP BY meta_value;
This is something I tried and a couple of more variations but it doesn't do much... this specifically gives me one post that has year 2013 and 12 posts that have either 1 or ... to 12 for month field}
Please replace _PUT_YOUR_POST_ID_FIELD_HERE_ by something appropriate field from that table and try to run...
SELECT Y.meta_value, M.meta_value FROM wp_postmeta as Y
JOIN wp_postmeta AS M USING (_PUT_YOUR_POST_ID_FIELD_HERE_)
WHERE (Y.meta_key = 'agam_post_options_year' AND Y.meta_value = 2013)
AND (M.meta_key = 'agam_post_options_month' AND M.meta_value BETWEEN 0 AND 12 )
GROUP BY Y.meta_value, M.meta_value;
OK It was all good. The reason it took out the "duplicates" was because we didn't group by Y.meta_id and instead we used meta_value
If you don't really need a pure SQL query and you're getting these posts for using them in WordPress, you could use meta_query array passed as a part of args to WP_Query class or get_posts() function.
$args = array(
'posts_per_page' => -1,
'post_type' => 'your-post-type', // default 'post'
'meta_query' => array(
relation => 'AND', // default 'AND' it might be 'OR'
array(
'key' => 'agam_post_options_year',
'value' => '2013',
'compare'=> '=',
'type' => 'NUMERIC' // see docs for more types
),
array(
'key' => 'agam_post_options_month',
'value' => array( 0, 12 ),
'compare' => 'BETWEEN',
'type' => 'NUMERIC' // see docs for more types
)
)
);
$posts = get_posts( $args ); // returns an array of posts objects
//OR
$query = new WP_Query( $args ); // returns a new WP_Query object
Hope it helps! : )

Categories