orderby meta_value breaks taxonomy term archives

I have a People post type with a Person Type taxonomy (e.g. “Staff”). On the Person Type Term Archive pages, I want to sort by a “Sort Name” custom field. To do so, I’m using a pre_get_posts call:

<?php
function my_query_tweaks( $query ) {

    // stop immediately if we're in the admin or not working with the main query
    if( is_admin() || !$query->is_main_query() )
        return;

    // order people by sortname
    if( is_post_type_archive( 'ciswa_people' ) || is_tax( 'ciswa_person_type' ) ) {
        $query->set( 'meta_key', 'cis_sort_name' );
        $query->set( 'orderby', 'meta_value' );
        $query->set( 'order', 'ASC' );
    }

}
add_action( 'pre_get_posts', 'my_query_tweaks' );

However, when I do this, the term archive pages return no results! Commenting out the meta_key line brings up the correct posts but, of course, unordered.

Read More

When I look at the query WordPress is generating, I think the issue is that WordPress is trying to also query posts based on the meta_key rather than just ordering by it. From $wp_query on the relevant empty taxonomy term archive page:

["tax_query"]=>
  object(WP_Tax_Query)#343 (2) {
    ["queries"]=>
    array(1) {
      [0]=>
      array(5) {
        ["taxonomy"]=>
        string(17) "ciswa_person_type"
        ["terms"]=>
        array(1) {
          [0]=>
          string(5) "staff"
        }
        ["include_children"]=>
        bool(true)
        ["field"]=>
        string(4) "slug"
        ["operator"]=>
        string(2) "IN"
      }
    }
    ["relation"]=>
    string(3) "AND"
  }
  ["meta_query"]=>
  object(WP_Meta_Query)#344 (2) {
    ["queries"]=>
    array(1) {
      [0]=>
      array(1) {
        ["key"]=>
        string(13) "cis_sort_name"
      }
    }
    ["relation"]=>
    string(3) "AND"
  }

…and…

["request"]=>
  string(498) "SELECT SQL_CALC_FOUND_ROWS  fi_posts.ID FROM fi_posts  INNER JOIN fi_term_relationships ON (fi_posts.ID = fi_term_relationships.object_id) INNER JOIN fi_postmeta ON (fi_posts.ID = fi_postmeta.post_id) WHERE 1=1  AND ( fi_term_relationships.term_taxonomy_id IN (6,10) ) AND fi_posts.post_type = 'ciswa_people' AND (fi_posts.post_status = 'publish' OR fi_posts.post_status = 'private') AND (fi_postmeta.meta_key = 'cis_sort_name' ) GROUP BY fi_posts.ID ORDER BY fi_postmeta.meta_value ASC LIMIT 0, 10"

Related posts

2 comments

  1. 'meta_key' => 'keyname' must be part of the query to be able to sort by its value.
    Unfortunately, that will immediately result in the query being limited to posts where the key is set (its value can be empty, but it must exist).

    Hence, either consider the method s_ha_dum linked to in the question comments, or choose to do the sorting after the query.

    Let me offer an approach to that (untested, just a concept):

    function wpse105899_sort_posts_by_cis_sort_name( $a, $b ) {
        $a_cis_sort_name = get_post_meta( $a->ID, 'cis_sort_name', true );
        $b_cis_sort_name = get_post_meta( $b->ID, 'cis_sort_name', true );
    
        if ( $a_cis_sort_name === $b_cis_sort_name ) {
            return 0;
        } else if ( $a_cis_sort_name > $b_cis_sort_name ) {
            return 1;
        } else {
            return -1;
        }
    }
    
    usort( $query->posts, 'wpse105899_sort_posts_by_cis_sort_name' );
    

    Note: The above format of the conditional in the usort callback serves the purpose of readability. In production, it can be a one-liner:

    function wpse105899_sort_posts_by_cis_sort_name( $a, $b ) {
        $a_cis_sort_name = get_post_meta( $a->ID, 'cis_sort_name', true );
        $b_cis_sort_name = get_post_meta( $b->ID, 'cis_sort_name', true );
    
        return $a_cis_sort_name === $b_cis_sort_name ? 0 : ( $a_cis_sort_name > $b_cis_sort_name ) ? 1 : -1;
    }
    
  2. Head desk

    I had the meta_key wrong. @Johannes Pille’s answer was the key in that it pointed me to the fact that if the key was there this shouldn’t be a problem. It was there but I wasn’t using it.

Comments are closed.