Is there a way to do multiple ordering on a multiple meta_query?

I’m experimenting with searches and multiple criterias and ordering. Let’s say we have a real estate site, all posts have 2 custom fields (price and surface), and we want the visitor to be able to search by entering a :

  • minimum price
  • maximum price
  • minimum surface
  • maximum surface

This would do the job :

Read More
function mwm_register_search_filters( $query ) {

    // Only on archive post listings    
    if( $query->is_archive ) {

        $filters_a = array();

        if( !empty( $_GET['price_min'] ) ) $filters_a[] = array( 'key' => 'price', 'value' => $_GET['price_min'], 'compare' => '>=', 'type' => 'numeric' );
        if( !empty( $_GET['price_max'] ) ) $filters_a[] = array( 'key' => 'price', 'value' => $_GET['price_max'], 'compare' => '<=', 'type' => 'numeric' );
        if( !empty( $_GET['surface_min'] ) ) $filters_a[] = array( 'key' => 'surface', 'value' => $_GET['surface_min'], 'compare' => '>=', 'type' => 'numeric' );
        if( !empty( $_GET['surface_max'] ) ) $filters_a[] = array( 'key' => 'surface', 'value' => $_GET['surface_max'], 'compare' => '<=', 'type' => 'numeric' );

        if( !empty( $filters_a ) ) $query->set( 'meta_query' , $filters_a );

    }

    return $query;
}
add_filter( 'pre_get_posts' , 'mwm_register_search_filters' );

That’s great and it works well. But then comes ordering..

AFAIK WordPress only allows ordering by one meta_key / meta_value. That’s nice but not good enough when you need to order by multiple criterias. Is there a way to order for example :

  • First by price ASC
  • And within equal prices, order by surface ASC

Related posts

Leave a Reply

1 comment

  1. As of WordPress 3.4.1, there is no way, out of the box, to order by multiple meta values.

    Since we can’t do it cleanly, we’ll have to hack something together. We can hook into posts_orderby to manually modify the ordering. It’s not altogether pretty, but it will get the job done. Here’s an example:

    add_filter('posts_orderby', 'wpse_28390_orderby' );
    function wpse_28390_orderby( $orderby ) {
        return str_replace('postmeta.meta_value', 'postmeta.meta_value, mt1.meta_value ASC', $orderby);
    }
    

    Most importantly, this assumes that “surface” is the second meta_key in your query (that’s where mt1 comes from; mt1 is the second meta key, mt2 is the third, and so on), which in your case won’t always be correct. Therefore, the add_filter call should go behind three checks:

    1. if this is the page where this would be relevant,
    2. if the surface meta_key is being queried (if the $_GET param is present), and
    3. which number the surface meta_key is in the join sequence, then adjust mt1 accordingly

    It’s janky, but again, it will work.