WP_Query over two meta keys are sorted separately - php

I've stumbled upon a WordPress query problem I don't seem to be able to fix.
I've got a custom post type called products that has two fields, price and price_campaign. Sometimes, price is only set, sometimes it's only price_campaign. I still want my final query to output all products in order, ordered by price/price_campaign, as if these two fields were the same. I don't seem to be able to do that, though:
$query->set(
'meta_query',
array(
'relation' => 'AND',
'price_campaign' => array(
'key' => 'price_campaign',
'type' => 'NUMERIC',
'compare' => 'EXISTS'
),
'price' => array(
'key' => 'price',
'type' => 'NUMERIC',
'compare' => 'EXISTS'
),
)
);
$query->set(
'orderby',
array(
'meta_value_num' => 'ASC',
'title' => 'ASC'
)
);
But what happens is that I first get the products that has price set and then all the ones with price_campaign set.
If I change it to 'relation' => 'OR' they are just put out in a completely random order, where neither prices nor titles seem to matter.
I really can't figure out what's the problem. Any ideas?

Related

What is the best approach to checking stock in a Woocommerce meta query

I'm creating a one-way relationship between two products, with the 'linked-to' product outputting the product information of the 'linked-from' product.
I'm able to use a custom field to create the relationship, and use a meta query to generate the results based on this field, but I also need to check that the 'linked-from' product is still either In Stock (if not being stock managed), or has more than 0 as a stock quantity.
This is the approach I have taken so far, which returns no matching results (when indeed there are matches):
$the_query = new WP_Query(array(
'posts_per_page' => -1,
'post_type' => 'product',
'meta_key' => 'exact_new_version',
'meta_value' => $product->get_id(),
'meta_query' => array(
'relation' => 'OR',
array(
'key' => '_stock_status',
'value' => array('instock'),
'compare' => 'IN',
),
array(
'key' => '_stock_quantity',
'value' => 0,
'compare' => '>'
)
)
));
The alternative is to check the stock of the returned match(es) in the query loop, but I imagine this to be far less efficient.
I've managed to achieve the desired result by passing in two sets of associative arrays into the meta query with an AND relationship, one of which (checking stock) containing a further two nested assoc. arrays with an OR relationship.
Essentially, the meta value has to match either an 'instock' item (if stock control isn't being used), or an item with '> 0' in stock, if it is:
$the_query = new WP_Query(array(
'posts_per_page' => -1,
'post_type' => 'product',
'meta_query' =>
array(
'relation' => 'AND',//*
array(
'key' => 'new_version',
'value' => $product->get_ID(),
'compare' => 'LIKE',
),
array(
'relation' => 'OR', //**
array(
'key' => '_stock_status',
'value' => array('instock'),
'compare' => 'IN',
),
array(
'key' => '_stock_quantity',
'value' => 0,
'compare' => '>',
),
),
),
));

orderby multiple meta_keys and then orderby date_modified

A post is created for each talent with a status of either coming-soon or in-town or others saved as meta_values. I needed to display posts first by "coming-soon" and then "intown" and then "others".
I realized that I was looking for orderby multiple meta_keys and found a solution here. I converted the syntax in order for it to work with pre_get_posts hook ( since it is main loop in category.php). As I need, code below mostly works and shows results ordered by coming-soon->in-town->others.
add_filter('pre_get_posts', 'pre_get_posts_hook' );
function pre_get_posts_hook($wp_query) {
if (is_category() && $wp_query->is_main_query()) {
$wp_query->set('meta_query',
array( 'relation' => 'OR',
'soon' => array( 'key' => 'coming_soon',
'value' => 'yes',
'compare' => '='
),
town' => array( 'key' => 'in-town',
'value' => 'positive',
'compare' => '='
),
'others' => array( 'key' => 'others',
'value' => 'null',
'compare' => '='
)
)
);
$wp_query->set('orderby', array('soon' => 'DESC', 'town' => 'DESC', 'others' => 'DESC'));
return $wp_query;
}
}
Now, I need to run a second level of orderby. I mean, with the code above, first I get the posts with meta_key coming-soon, I expect these posts to be again ordered by date_modified and possibly a control on their order eg. ASC, DESC. I am quite sure that it is possible. I just have no clue how do I nest it further.
Solved. I will leave the answer in case it may help someone in future. I had to replace this line.
$wp_query->set('orderby', array('soon' => 'DESC', 'town' => 'DESC', 'others' => 'DESC'));
with this line.
$wp_query->set('orderby', array('meta_value' => 'DESC', 'modified' => 'DESC' ));
I think the code is pretty self-explanatory. What it does is, it will allow you to run you primary-orderby and then once you get those results, you can run a secondar-orderby. Of course, the second associative array is not limited to modified, it can be any other parameters of orderby. Eg. title, author etc.

How do you order by 2 meta values in Wordpress?

I am using Woocommerce, although my question relates to WP_Query in general. I am creating a custom WP_Query loop for bestselling products and I want to allow users to be able to sort the results by price, from high to low and low to high. The problem is in Woocommerce both _price and total_sales are meta fields and as far as I can tell I can only orderby 1 meta field in Wordpress loops. Is there a way around this?
My full code including some of my attempts is here, the most relevant code segment looks like as follows:
$queryArgs = array(
'post_type' => 'product',
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_query' => array(
array(
'key' => '_visibility',
'value' => array('catalog', 'visible'),
'compare' => 'IN'
)
),
'meta_key' => '_price',
'orderby' => array('meta_value_num' => $sortBy['terms'])
);
The actual code is more complicated than this because it's built for a range of filtering and sorting options, but this is the gist of it and bestsellers is the only thing giving me issues because of the two meta_keys. I've read this but it doesn't apply to custom meta fields.
I've tried:
'meta_key' => '_price total_sales'
'meta_key => array('_price', 'total_sales')
'orderby' => array('meta_value_num' => $sortBy['terms'], 'meta_value_num' => 'DESC')
Nothing seems to work. I've also tried hooking onto the various WP_Query filters but the problem is this query is part of a dynamically generated loop so I can't 'hack' or hard-code anything.
Use the meta query with arrays:
'meta_query' => array (
array (
'key' => '_visibility',
'value' => array('catalog', 'visible'),
'compare' => 'IN'
),
array (
'key' => '_price',
'value' => "VALUE WHAT YOU WANT, NOT ASC/DESC",
)
),

Wordpress Custom Meta Query Search is so slow when in OR relation

I made a search which is basically querying custom meta fields and showing the results. For some reason, the search takes endless hours. When I change the relation to "AND" it works great but when I change the relation to "OR" it's dead.
Currently in the database I have 5 records so it shouldn't take time.
This is the query
$args = array(
'post_type' => 'shipping-schedules',
'posts_per_page' => 5,
'post_status' => 'publish',
'paged' => $paged,
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'schedules_port',
'value' => $sfrom,
'compare' => 'LIKE'
),
array(
'key' => 'schedules_port',
'value' => $sto,
'compare' => 'LIKE'
),
array(
'key' => 'schedules_vessel',
'value' => $svessel,
'compare' => 'LIKE'
),
array(
'key' => 'schedules_voyage',
'value' => $svoyage,
'compare' => 'LIKE'
),
array(
'key' => 'schedules_arrival',
'value' => $sdate_arrival,
'compare' => '>=',
'type' => 'NUMERIC'
),
array(
'key' => 'schedules_departure',
'value' => $sdate_departure,
'compare' => '<=',
'type' => 'NUMERIC'
)
)
);
As you can see in the above query this line 'relation' => 'AND' changing it to 'relation' => 'OR' will result the problem am facing slow querying.
I found this problem too... but the problem is I needed to use OR for my particular case.
After reading a ton of info, such as indexing the post meta table values (this actually sounds like a good idea, but my code was in a plugin so couldn't do this) I found a way that seems to run the same query a lot faster.
I have no idea why, and haven't begun to poke around under the hood, but instead of doing a WP meta query use the filter posts_where and modify the WHERE clause yourself adding in your own arguments..
Here's my code:
add_filter('posts_where', array($this, 'filter_meta_query_where')); //modify the where clause to filter meta data
add_filter('posts_join' , array($this, 'filter_meta_join')); //join the meta table to the posts query
This is the main function that adds the meta query (I've added conditions for schedules_port, you'll have to build the rest yourself the way you want):
function filter_meta_query_where($where = '')
{
global $wpdb;
global $wp_query;
global $sf_form_data;
if(!is_admin())
{
$where .= " AND (($wpdb->postmeta.meta_key = 'schedules_port' AND ($wpdb->postmeta.meta_value LIKE '%$sfrom%' OR $wpdb->postmeta.meta_value LIKE '%$sto%')))";
}
return $where;
}
And here's the code for the join
function filter_meta_join($join)
{
global $wpdb;
$join .= " LEFT JOIN $wpdb->postmeta ON $wpdb->posts.ID = $wpdb->postmeta.post_id ";
return $join;
}
Hope that helps

WP_Query with Custom Field Parameter (checkbox)

I'm trying to set up a custom query that returns only posts that do not have a custom meta field checkbox is checked. The checkbox meta field id is position-filled.
Here is the code I have:
$args = array (
'post_type' => 'jobs',
'posts_per_page' => 4,
'post__not_in' => array(get_the_id()),
'meta_query' => array(
array(
'key' => 'position-filled',
'value' => 'on', // also tried passing null and removing 'compare'
'compare' => '!=' // also tried 'NOT LIKE'
)
),
);
$customQuery = new WP_Query($args);
However, this returns no posts (I have made certain there are posts without the checkbox checked.
Any idea what I'm doing wrong?
I've run into similar issues when adding custom fields to existing posts/pages where I needed to search on "unchecked" or "unselected" fields.
It boils down to the fact that the query is actually saying, "Give me posts where the meta key (position-filled) is populated, but not populated with a value of 'on'".
Does it work if you re-save your post with the checkbox being unchecked?
If so, you might also try using the 'compare' value of 'NOT EXISTS'. Although I believe if you use NOT EXISTS and then someone saves an older post without the checkbox being checked, it will exist (with a blank value).
[edit]
I forgot to mention that you can build a query with either situation being true (the NOT EXISTS or it does exist but the value is blank/unchecked).
$args = array(
'post_type' => 'jobs',
'posts_per_page' => 4,
'post__not_in' => array(get_the_id()),
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'position-filled',
'value' => 'on',
'compare' => '!='
),
array(
'key' => 'position-filled',
'value' => '',
'compare' => 'NOT EXISTS'
)
)
);
You can chain multiple comparisons together and using the relation property of OR, either one should return true.

Categories