What’s wrong with this WP query?

Turns out, WordPress joins the postmeta table several times, depending
on the meta query arguments.

Yes that’s correct, and it’s also the reason why I said (in my other answer) that in an orderby array, you should use the key of an array in the meta_query which contains a direct item named/keyed key (where the value is a meta key) — and if you don’t, then WordPress will not know which table join/alias should be used.

This means the 1C_quantity_total meta key has to be referenced as
mt2.meta_key, not wp_postmeta.meta_key.

Yes, you’re absolutely right!

( And sorry, my bad, I revised my other answer several times and yet, I actually still forgot to correct the table alias 😅 )

Now, I don’t feel like hard-coding the table’s alias, mt2. What if the
alias changes in future versions of WordPress (e.g. mtB)? Also, I’m
not sure 1C_quantity_total will be in exactly the third joined
postmeta table in any circumstances. Is there a way to do it better?

Yes, there is: use WP_Meta_Query::get_clauses() to get the correct alias used for each meta query clause. And with a WP_Query instance, you can access the meta query class instance using the $meta_query property, i.e. WP_Query::$meta_query.

But remember to give a unique array key to your meta query clauses like the has_tdlrm_mp and has_1C_quantity_total:

$args = array(
    ...
    'meta_query' => array(
        array(
            'relation'     => 'OR',
            'has_tdlrm_mp' => array(
                'key'  => 'tdlrm_mp',
                'type' => 'NUMERIC',
            ),
            'no_tdlrm_mp'  => array(
                'key'     => 'tdlrm_mp',
                'compare' => 'NOT EXISTS',
            ),
        ),
        array(
            'relation'              => 'OR',
            'has_1C_quantity_total' => array(
                'key'  => '1C_quantity_total',
                'type' => 'NUMERIC',
            ),
            'no_1C_quantity_total'  => array(
                'key'     => '1C_quantity_total',
                'compare' => 'NOT EXISTS',
            ),
        )
   ),
   ...
);

So with that meta query, you can get the correct table alias like so:

$query = new WP_Query( $args );

$meta_clauses = $query->meta_query->get_clauses();

var_dump(
    $meta_clauses['has_tdlrm_mp']['alias'],
    $meta_clauses['has_1C_quantity_total']['alias'],
);
// Sample output: string(11) "wp_postmeta" string(3) "mt2"

And inside the posts_orderby filter/callback, you can use the same $query->meta_query->get_clauses() above, like so:

$meta_clauses = $query->meta_query->get_clauses();

$has_tdlrm_mp          = $meta_clauses['has_tdlrm_mp']['alias'];
$has_1C_quantity_total = $meta_clauses['has_1C_quantity_total']['alias'];

return "
CASE {$has_tdlrm_mp}.meta_key
    WHEN 'tdlrm_mp' THEN 1
    ELSE 2
END ASC,
CASE {$has_tdlrm_mp}.meta_key
    WHEN 'tdlrm_mp' THEN {$has_tdlrm_mp}.meta_value+0
END ASC,
CASE {$has_1C_quantity_total}.meta_key
    WHEN '1C_quantity_total' THEN {$has_1C_quantity_total}.meta_value+0
END DESC
";

Leave a Comment