Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I've been modifying the built in WP search using the pre_get_posts filter, allowing the user to sort the posts (including a bunch of custom post types) by different fields.

The problem I'm having though is that when I tell WP to sort by a meta value it will exclude all posts that don't have that meta value set. This causes the number of results to change if you change sorting from say "Price" to "Date" because "Posts" don't have "Price" set but "Items" do.

This is not what I want, so I'd like to know if there's a way to include ALL posts - even those that lack the meta value I'm sorting on - and put the one's without the value last.

I know how to sort on more than one field but that doesn't help.

Thanks

Seems I'm not the only one with this question: Way to include posts both with & without certain meta_key in args for wp_query? but there's no solution there.

Update

I've tried the answer but not sure if I understood correctly, here's what I have right now:

<?php
function my_stuff ($qry) {
    $qry->set('meta_query', array(array(
        'key' => 'item_price', 
        'value' => '', 
        'compare' => 'NOT EXISTS'
    )));

    $qry->set('orderby', 'meta_value date'); # Sorting works with meta_value as well as meta_value_num - I've tried both
    $qry->set('order', 'ASC DESC');
    $qry->set('meta_key', 'item_price');
}

The meta value is a number (it is used to store a price as the name suggests)

Update 2

I've commented out the order-stuff and all I have now is this:

<?php
$qry->set('meta_query', array(array(
    'key' => 'item_price', 
    'value' => '', 
    'compare' => 'NOT EXISTS'
)));

With this code the query seems to return all posts that don't have the item_price key and none of the posts that have it. I.E. the problem is now reversed.

If I add in the order-code as well I get 0 results.

share|improve this question
    
Please show us your code. Makes it easier to answer. –  kaiser Jun 9 '13 at 23:24
    
First: meta_query and tax_query are always an array( array() ) as they combine multiple arrays. Second - as mentioned in my answer - you need to use meta_value_num for numbers. It might as well be needed to actually define the meta_value_num (see WP_Query-Codex page entry). Last, it doesn't make sense to order in ASC and DESC direction. That's not possible. The space delimiter only works for orderby and you can't tell it to sort the first ASC and the second DESC. That's what posts_clauses filter is for. –  kaiser Jun 10 '13 at 0:42
    
And make sure that your meta_value_num entries are real numbers. Seen it too often that someone states that it's a number, but in fact saving it as a string in the database. –  kaiser Jun 10 '13 at 0:43
    
Thanks for your help, I will try this and get back to you. The reason for ASC DESC is so that it sorts on the meta_value in ASC and the date in DESC, as far as I can tell it works. –  powerbuoy Jun 10 '13 at 1:06
1  
@Howdy_McGee that is correct. Some of my custom post types HAVE this value set. Some don't. And the built in post types (like POST and PAGE) do not. So whenever I try to sort on that custom field only posts WITH the custom field show up. –  powerbuoy Apr 15 at 18:31
show 4 more comments

5 Answers

There's a possible orderby value of meta_value for that.

$query = new WP_Query( array ( 
    'meta_key'   => 'your_keys_name',
    'orderby'    => 'meta_value',
    'order'      => 'DESC',
    'meta_query' => array( array(
         'key'     => 'your_meta_key',
         'value'   => '',
         'compare' => 'NOT EXISTS',
         // 'type'    => 'CHAR',
    ) )
) );

If you got numeric values, just use meta_value_num instead.

Disclaimer: This is not tested, but it should work. Point is that you need to specify your meta_key and key values. Else you can't compare against non-existing values, which should make it possible to query both sorts of posts. It's some kind of hack-ish, but as long as it works...

share|improve this answer
    
Thanks for your answer, please check my updated question, I'm not sure I understood you correctly. –  powerbuoy Jun 10 '13 at 0:05
    
I still haven't made this work so if you have a solution I'd love to know what I'm doing wrong. Also, I set a bounty on SO if you want to claim it: stackoverflow.com/questions/17016770/… –  powerbuoy Jun 20 '13 at 2:45
add comment

I think i have a solution..

You can use two meta_keys, one that all posts have (like "_thumbnail_id"), and the meta_key you wish use as filter.. so your args:

$qry->set(
'meta_query',
 array(
'relation' => 'OR',
array(
    'key' => 'item_price', 
    'value' => '', 
    'compare' => 'EXISTS'
),
array(
    'key' => 'item_price', 
    'value' => '', 
    'compare' => 'EXISTS'
)
));
 $qry->set('orderby', 'meta_value date'); # Sorting works with meta_value as well as meta_value_num - I've tried both
    $qry->set('order', 'ASC DESC');
    $qry->set('meta_key', 'item_price');
share|improve this answer
add comment

I think what @kaiser was trying to do was to tell the query to return all posts that have that meta key by applying a sort of dummy where condition to not filter any of those posts. So if you know all the values your custom fields can take are x,y,z you could say "WHERE meta_key IN(x,y,z)" but the idea is you can avoid that problem all together by saying != (''):

$query = new WP_Query( array ( 
    'orderby'    => 'meta_value_num',
    'order'      => 'DESC',
    'meta_query' => array( array(
         'key'     => 'item_price',
         'value'   => '',
         'compare' => '!=',
    ) )
) );

Also not tested but feels like it's worth a try :-).

share|improve this answer
1  
Can't really test this right now, but pretty sure that will only return posts where item_price is set and it's not ''. –  powerbuoy Aug 23 '13 at 11:13
add comment

I ended up getting around this with a bit of a hack (IMHO), but it did the job for me in my case.

You can hook into the filters posts_join_paged and posts_orderby to update the join and order strings. This will allow you to order by whatever you want as long as you join it first rather than WP_Query assuming that the field has to exist for that particular post. You can then remove the meta_key, orderby, and `order from your WP_Query args.

Below is an example. At the top of each function I had to escape out for certain cases since it will add this to everything that uses WP_Query. You may need to modify that to fit your particular needs.

Documentation on these two filters is sadly lacking so... good luck! :)

add_filter('posts_join_paged', 'edit_join', 999, 2);
add_filter('posts_orderby', 'edit_orderby', 999, 2);

/**
 * Edit join
 *
 * @param string $join_paged_statement
 * @param WP_Query $wp_query
 * @return string
 */
function edit_join($join_paged_statement, $wp_query)
{
    global $wpdb;
    if (
        !isset($wp_query->query)
        || $wp_query->is_page
        || $wp_query->is_admin
        || (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
    ) {
        return $join_paged_statement;
    }

    $join_to_add = "
        LEFT JOIN {$wpdb->prefix}postmeta AS my_custom_meta_key
            ON ({$wpdb->prefix}posts.ID = my_custom_meta_key.post_id
                AND my_custom_meta_key.meta_key = 'my_custom_meta_key')
    ";

    // Only add if it's not already in there
    if (strpos($join_paged_statement, $join_to_add) === false) {
        $join_paged_statement = $join_paged_statement . $join_to_add;
    }

    return $join_paged_statement;
}

/** 
 * Edit orderby
 *
 * @param string $orderby_statement
 * @param WP_Query $wp_query
 * @return string
 */
function edit_orderby($orderby_statement, $wp_query)
{
    if (
        !isset($wp_query->query)
        || $wp_query->is_page
        || $wp_query->is_admin
        || (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
    ) {
        return $orderby_statement;
    }

    $orderby_statement = "my_custom_meta_key.meta_value DESC";

    return $orderby_statement;
}
share|improve this answer
add comment

There's two possible solutions to this:

1. All posts have meta

The best solution I have found here is to give the rest of the posts/products an item price of 0. You can do this manually, or loop through all the posts and if the price is empty then update it.

To make this manageable in the future you can hook into save_post and give them a value when they are first added (only if it's blank).

2. Multiple Queries

You could run the first query as you're doing and store the IDs of the posts returned. You could then run another query for all posts and orderby date, excluding the IDs return from the first query.

You can then print out the two results separately order and you'll get the desired results.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.