phpmysqlwordpresscustom-fields

Wordpress Query order by custom field missing posts


This is my current query:

query_posts(array_merge(array('tag' => $pagetag,'meta_key'=>priority,'orderby' =>meta_value, 'order' =>'ASC','paged' => get_query_var('paged'))));

My problem is that the query shows me only the post that has values for my 'meta_key' meaning that 'priority' is not NULL. How can I improve this query so that it will still orderby my meta_key but will show all the posts that aren't NULL as well?

Thanks in advance!


Solution

  • The problem is that WordPress adds an INNER JOIN to the wp_postmeta table as soon as you mention meta_key in your conditions. One way around the problem is to add a filter on the order by clause, something like this:

    function so_orderby_priority($original_orderby_statement) {
        global $wpdb;
    
        return "(SELECT $wpdb->postmeta.meta_value
                   FROM $wpdb->postmeta
                  WHERE $wpdb->posts.ID = $wpdb->postmeta.post_id
                    AND $wpdb->postmeta.meta_key = 'priority') ASC";
    }
    
    add_filter('posts_orderby', 'so_orderby_priority');
    
    query_posts(
        array(
            'tag' => $pagetag,
            'paged' => get_query_var('paged')
        )
    );
    
    remove_filter('posts_orderby', 'so_orderby_priority');
    

    Note MySQL sorts NULLs first - if you want them sorted last, try something like this (assuming all your priorities come before ZZZZZ alphabetically):

    function so_orderby_priority($original_orderby_statement) {
        global $wpdb;
    
        return "IFNULL(
                   (SELECT $wpdb->postmeta.meta_value
                      FROM $wpdb->postmeta
                     WHERE $wpdb->posts.ID = $wpdb->postmeta.post_id
                       AND $wpdb->postmeta.meta_key = 'priority'),
                    'ZZZZZ') ASC";
    }
    

    Edit

    Here's a bit more explanation, which assumes you understand SQL at least a bit.

    Your original query_posts resulted in the following query running against the database:

    SELECT wp_posts.*
    FROM   wp_posts
           INNER JOIN wp_term_relationships ON ( wp_posts.id = wp_term_relationships.object_id )
           INNER JOIN wp_postmeta ON ( wp_posts.id = wp_postmeta.post_id )
    WHERE  1 = 1
           AND ( wp_term_relationships.term_taxonomy_id IN ( 3 ) )
           AND wp_posts.post_type = 'post'
           AND ( wp_posts.post_status = 'publish'
                  OR wp_posts.post_status = 'private' )
           AND ( wp_postmeta.meta_key = 'priority' )
    GROUP  BY wp_posts.id
    ORDER  BY wp_postmeta.meta_value ASC
    LIMIT  0, 10; 
    

    That INNER JOIN wp_postmeta is what removed any posts without a priority from your results.

    Removing the meta_* related conditions from your query_posts:

    query_posts(
        array(
            'tag' => $pagetag,
            'paged' => get_query_var('paged')
        )
    );
    

    solved that problem, but the sort order is still wrong. The new SQL is

    SELECT wp_posts.*
    FROM   wp_posts
           INNER JOIN wp_term_relationships ON ( wp_posts.id = wp_term_relationships.object_id )
    WHERE  1 = 1
           AND ( wp_term_relationships.term_taxonomy_id IN ( 3 ) )
           AND wp_posts.post_type = 'post'
           AND ( wp_posts.post_status = 'publish'
                  OR wp_posts.post_status = 'private' )
    GROUP  BY wp_posts.id
    ORDER  BY wp_posts.post_date DESC
    LIMIT  0, 10; 
    

    The posts_orderby filter allows us to change the ORDER BY clause: wp_posts.post_date DESC gets replaced by what the filter returns. The final SQL becomes:

    SELECT wp_posts.*
    FROM   wp_posts
           INNER JOIN wp_term_relationships ON ( wp_posts.id = wp_term_relationships.object_id )
    WHERE  1 = 1
           AND ( wp_term_relationships.term_taxonomy_id IN ( 3 ) )
           AND wp_posts.post_type = 'post'
           AND ( wp_posts.post_status = 'publish'
                  OR wp_posts.post_status = 'private' )
    GROUP  BY wp_posts.id
    ORDER  BY (SELECT wp_postmeta.meta_value
               FROM   wp_postmeta
               WHERE  wp_posts.id = wp_postmeta.post_id
                      AND wp_postmeta.meta_key = 'priority') ASC
    LIMIT  0, 10 
    

    which does what you're after.