Consider a situation, I've post meta fields:
Min_Price
Max_Price
So every post has these two fields, and value can be any integer from non zero to 1000.
Lets take an example:
post #1 min_price = 5; max_price = 100
post #2 min_price = 50; max_price = 150
I'd like to make custom order logics to use with query_posts. So users can select price ranges and see posts with selected values.
Now if I had only 1 price and 1 field, it would be easy to let users select price range and get posts which fall into these range, like so:
query_posts( array(
'post_type' => 'product',
'meta_query' => array(
array(
'key' => 'price',
'value' => array( 100, 200 ),
'compare' => 'BETWEEN',
'type' => 'numeric',
)
)
) );
So here every post with price between 100-200 would be visible.
But I have a range of values... and I thought this would work:
query_posts( array(
'post_type' => 'product',
'meta_query' => array(
array(
'key' => 'min_price',
'value' => array( 100, 200 ),
'compare' => 'BETWEEN',
'type' => 'numeric',
),
array(
'key' => 'max_price',
'value' => array( 100, 200 ),
'compare' => 'BETWEEN',
'type' => 'numeric',
)
)
) );
Here client selected range 100<->200
Both post #1 and post #2 have values that fall in this range, so visitor will be interested in both of them. Unfortunately my query logic excludes them.
My brain is not working well right now, so I'm missing something simple. Any hints?
I now know your way should work, except McNab is probably right (there are plenty of examples right here: http://codex.wordpress.org/Class_Reference/WP_Query#Custom_Field_Parameters) but I'll let my old answer remain. Someone might find it useful for non-standard stuff.
I don't know if you can do this using query post parameters, but I looked it up in the source code and this is how the query looks:
SELECT $found_rows $distinct $fields
FROM $wpdb->posts $join
WHERE 1=1 $where
$groupby
$orderby
$limits
And these are filters corresponding to the relevant query variables:
$where = apply_filters_ref_array('posts_where', array( $where, &$this ) );
$join = apply_filters_ref_array('posts_join', array( $join, &$this ) );
So that means that the problem can be reduced to SQL:
function postmeta_join( $join )
{
$join .= "
LEFT JOIN $wpdb->postmeta AS max_price ON max_price.meta_key = 'max_price'
LEFT JOIN $wpdb->postmeta AS min_price ON min_price.meta_key = 'min_price'";
return $join;
}
function filter_prices($where)
{
$where .= " AND max_price.max_price BETWEEN 100, 200 AND min_price.min_price BETWEN 100, 200";
return $where;
}
add_filter( 'posts_where', 'filter_prices' );
add_filter( 'posts_join', 'postmeta_join' );
Now I'm not an SQL expert so there may be problems with this. But if you don't get any other answer, you could play with this. (And BTW, you probably want to remove the filters when you're done with them.)
What you've done should work but you've missed the 'relation' parameter. It should be;
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'min_price',
'value' => array( 100, 200 ),
'compare' => 'BETWEEN',
'type' => 'numeric',
),
array(
'key' => 'max_price',
'value' => array( 100, 200 ),
'compare' => 'BETWEEN',
'type' => 'numeric',
)
)
The other thing to note is that in the first instance the meta_id is 'price' and in the second you are using 'min_price' and 'max_price'. Have these definitely been set?
I'm mentioning this because recently I had a hard time on these meta_queries with multiple sub-arrays and the AND relation where the values had been submitted by the user in a form (exactly like my understanding of your question). There was an issue where the query didn't work where there are null values for items in the array. This is meant to be fixed by scribu;
http://core.trac.wordpress.org/ticket/18158
But I could not get this to work at all. Tearing my hair out. I ended up checking the submitted value to see if there was one and then creating the arrays individually. The query would work if either $min_array or $max_array was NULL, but not at all if either of the meta_values was NULL and it was built the way you are doing it. I hope this makes sense, my code is below;
if ($min_price) {
$min_array = array('key' => 'min_price','value' => $min_price, 'value' => '=', 'type' => 'numeric');
}
if ($max_price) {
$max_array = array('key' => 'max_price','value' => $max_price, 'value' => '=', 'type' => 'numeric');
}
'meta_query' => array(
'relation' => 'AND',
$min_array,
$max_array
)
Related
I have a series of posts and all have a meta_key with the value of "owner" and meta_value with serialized data such as "a:3:{i:0;s:3:"325";i:1;s:2:"41";i:2;s:2:"29";}"
meta_key owner
meta_value a:3:{i:0;s:3:"325";i:1;s:2:"41";i:2;s:2:"29";}
I am trying to figure out how to properly use get_posts() to return all the posts that have a meta_key with the value of owner and the meta_value containing a specific value such as 41
$args = array(
'post_type' => 'rp_applications',
'nopaging' => true,
'meta_query' => array(
'relation' => 'AND',
array(
'compare' => '=',
'key' => 'archived',
'value' => '0000-00-00'
),
array(
'compare' => '=',
'key' => 'owner',
'value' => ???? WHAT DO I PUT HERE ????
)
)
);
$applications = get_posts($args);
This sounds like you should be storing the "Owner" as a taxonomy term instead of meta data. It would give you easier and faster querying abilities as well.
That said, it's definitely possible to do what you want. I'd be cautious though, because the postmeta table by default is not indexed, so a large table with meta queries run against it will slow down your site quite a lot. You may want to consider adding a partial index to the table if it's going to be even remotely large (or again, switch to Taxonomy Terms instead of post meta for this).
If you still care to use a meta query, take a look at the compare options available in the WP_Meta_Query() docs.
One of the available options is REGEXP, so you could do something like:
$args = array(
'post_type' => 'rp_applications',
'nopaging' => true,
'meta_query' => array(
'relation' => 'AND',
array(
'compare' => 'REGEXP',
'key' => 'owner',
'value' => sprintf( 'a:\d:{i:0;s:\d:"\d.*?";i:1;s:%d:"%d".*', strlen($owner_id), $owner_id )
),
array(
'compare' => '=',
'key' => 'archived',
'value' => '0000-00-00'
)
)
);
Of course this method only works if the serialized data is in the same format on each one, and you'd need to adjust the regular expression if not - but that should be enough to get you started!
In a plugin I'm trying to not letting users placing similar orders with same billing information.
Imagine a user with John Doe billing first name and last name has placed an order which contains product1 and product2 and someone else tries to place same or similar order (for example containing product1,product2, product3) with same billing first name and last name while the previous order is still under processing.
So in order to avoid this behavior I'm trying to validate orders before placement.
If there are any running orders with same billing info I throw a Woocommerce error but I don't know how can I make sure if last orders contain similar items too!
Here is my code:
function check_for_duplicate_order($data)
{
$data['billing_last_name'];
$data['billing_first_name'];
$data['billing_postcode'];
$meta_query_args = array(
'relation' => 'OR', // Optional, defaults to "AND"
array(
'key' => '_billing_postcode',
'value' => '%' . $data['_billing_postcode'],
'compare' => 'LIKE'
),
array(
'key' => '_billing_phone',
'value' => '%' . $data['billing_phone'],
'compare' => 'LIKE'
),
array(
'relation' => 'AND',
array(
'key' => '_billing_first_name',
'value' => $data['billing_first_name'],
'compare' => '='
),
array(
'key' => '_billing_last_name',
'value' => $data['billing_last_name'],
'compare' => '='
)
)
);
$query_params = [
'post_type' => 'shop_order',
'posts_per_page' => 1,
'post_status' => array('wc-pending','wc-processing','wc-on-hold'),
'meta_query' => $meta_query_args
];
$cart_items_ids = [];
global $woocommerce;
$items = $woocommerce->cart->get_cart();
foreach($items as $item => $values) {
$cart_items_ids[] = $values['data']->get_id();
}
}
add_action('woocommerce_after_checkout_validation', 'check_for_duplicate_order');
In above code I get current order billing info, then finding other active orders with same billing info but from this point I don't know How can get found orders items and see if in previous orders there are same items too.
I managed to find a solution to match items in current cart with items in previous orders :
foreach ($billing_active_order_ids as $active_order_id){
$order = wc_get_order( $active_order_id );
foreach ($order->get_items() as $id => $order_item){
if(in_array($order_item->get_product_id(),$cart_items_ids)){
wc_add_notice($duplicate_order_message, 'error');
return;
}
}
}
I have an input field that uses the date range picker. This input field is part of a search query that is supposed to filter out posts by date range, time, and individual dates.
I have two custom fields, one that users can enter individual dates and times in and one that allows users to choose a date range and time range.
I can't seem to get my meta query to work because when I add a date range in the input field, the address bar formats the date to be yyyy-mm-dd+-+yyyy-mm-dd
and my custom fields format the date to be
yyyy-mm-dd
yyyy-mm-dd - yyyy-mm-dd
When I remove the '+' in the address bar, posts seem to come up, but it's also not filtering properly. It will show the post regardless if it has the exact format as the date range without taking into consideration the actual date range and I can't get it to display individual dates.
Here is my code:
if (isset($_GET['lp_s_date']) && !empty($_GET['lp_s_date'])){
function me_search_query( $query ) {
if ( $query->is_search ) {
$meta_query = array(
'post_type' => 'post',
'exact' => true,
'sentence' => true,
'relation' => 'OR',
array(
'key' => 'prefix-mydates',
'value' => ( $_GET['lp_s_date'] ),
'compare' => 'LIKE',
),
array(
'key' => 'prefix-range',
'value' => ( $_GET['lp_s_date'] ),
'type' => 'date',
'compare' => 'BETWEEN',
),
);
$query->set('meta_query', $meta_query);
};
}
add_filter( 'pre_get_posts', 'me_search_query');
Second Attempt:
if (isset($_GET['lp_s_date']) && !empty($_GET['lp_s_date'])){
$start = ($_GET['start']);
$end = ($_GET['end']);
function me_search_query( $query ) {
if ( $query->is_search ) {
$meta_query = array(
'post_type' => 'post',
array(
'key' => 'prefix-range',
'value' => array($start, $end),
'compare' => 'BETWEEN',
'type' => 'DATE',
),
array(
array(
'key' => 'prefix-myDates',
'value' => $start,
'compare' => '>=',
),
array(
'key' => 'prefix-myDates',
'value' => $end,
'compare' => '<=',
),
),
);
$query->set('meta_query', $meta_query);
};
}
add_filter( 'pre_get_posts', 'me_search_query');
}
From WP Codex
value (string|array) - Custom field value. It can be an array only when compare is 'IN', 'NOT IN', 'BETWEEN', or 'NOT BETWEEN'.
The 'type' DATE works with the 'compare' value BETWEEN only if the
date is stored at the format YYYY-MM-DD and tested with this format.
so, first of all fix it
I have a large WP site with about 2k posts and 150k postmeta. When browsing to a large category, the archive loads very slow due to the WP query that is seeking for featured posts, to show them first.
I know that the ultimate target is to optimize the postmeta table, but for now I would like to know if there is any possible way to optimize the meta query (one at the bottom) below.
I did some debugging (see the //comments) and found that the 'compare' => '!=' is causing for the query to get really slow. I've also found that '' or '<' is speeding up the query a lot, with the same results, but I am not sure if that is a proper way to go. Please let me know if there is a way to speed up this query. Thanks
function appthemes_addon_on_top_query( $wp_query ){
$addon_type = $wp_query->get( 'addon_on_top' );
if( ! $addon_type || ! appthemes_addon_exists( $addon_type ) ) {
return;
}
$addon_info = appthemes_get_addon_info( $addon_type );
$flag_key = $addon_info['flag_key']; //custom field for featured posts
$meta_query = (array) $wp_query->get( 'meta_value', 1 );
$meta_query = array_filter( $meta_query );
$meta_query[] = array(
'relation' => 'OR',
array(
'key' => $flag_key,
'compare' => 'NOT EXISTS',
),
array(
'relation' => 'OR',
//Deleting the array below makes the query quick, but i need it to show featured posts first
array(
'key' => $flag_key,
'value' => 1,
),
//Deleting the array below makes the query quick, but i need it to properly sorting the posts by date (see below: $wp_query->set( 'orderby')
array(
'key' => $flag_key,
'value' => 1,
'compare' => '!=', //It is about this comparison. '' or '<' makes the query quick, but i am not sure if that is a proper replacement. Also not sure why '!=' makes it this slow.
),
),
);
$wp_query->set( 'meta_query', $meta_query );
$wp_query->set( 'orderby', array( 'meta_value' => 'DESC', 'date' => 'DESC' ) );
}
Ok, Ive found that 'compare' => '!=' slows it down, but 'compare' => '<>' does not. I guess both are the same, so I guess this question is solved.
I'm comparing the trip_region custom field of my trip CPT to the first element of an array: $filter_region[0].
Howevere I may have more elements in that array and I need to select trips whose trip_region field equals any of these elements. Is it possible, with meta_query, to compare (with an OR, not an AND) a field to several values at once? Because there's nothing quite like that in the doc.
$get_trips_region = get_posts(array(
'post_type' => 'trip',
'meta_query' => array(
array(
'key' => 'trip_region',
'value' => '"' . $filter_region[0] . '"',
'compare' => 'LIKE'
)
)
));
EDIT:
Here's my new code, presumably closer to the solution but doesn't return any result:
$get_trips_region = get_posts(array(
'post_type' => 'trip',
'meta_query' => array(
array(
'key' => 'trip_region',
'value' => $filter_region,
'compare' => 'IN'
)
)
));
EDIT(2):
Replacing get_posts with WP_Query and using WP_Query's request property I see the following SQL query is being executed here:
SELECT SQL_CALC_FOUND_ROWS wp_15_posts.ID FROM wp_15_posts INNER JOIN wp_15_postmeta ON (wp_15_posts.ID = wp_15_postmeta.post_id) WHERE 1=1 AND wp_15_posts.post_type = 'trip' AND (wp_15_posts.post_status = 'publish' OR wp_15_posts.post_status = 'private') AND ( (wp_15_postmeta.meta_key = 'trip_region' AND CAST(wp_15_postmeta.meta_value AS CHAR) IN ('47','46','45')) ) GROUP BY wp_15_posts.ID ORDER BY wp_15_posts.post_date DESC LIMIT 0, 10
Relevant passage I guess is: (wp_15_postmeta.meta_key = 'trip_region' AND CAST(wp_15_postmeta.meta_value AS CHAR) IN ('47','46','45'))
Looks good to me, I don't understand why it doesn't work.
I think you have to compare with IN instead of LIKE. The value must contain an array then. Here's the doc.
$filter_region = array( 'Region 1', 'Region 2' );
$get_trips_region = get_posts(array(
'post_type' => 'trip',
'meta_query' => array(
array(
'key' => 'trip_region',
'value' => $filter_region,
'compare' => 'IN'
)
)
));
You can use compare. Here is another WordPress Codex doc on Meta_Query.