web-dev-qa-db-ja.com

Orderby meta_valueは、既存のmeta_keyを持つ投稿のみを返します

次のwp_queryがあります。

$args = array(
    'post_type' => 'news',
    'orderby' => 'meta_key',
    'order' => 'ASC',
    'meta_key'=>'custom_author_name',
    'post_per_page'=>-1
);

$query = new WP_Query($args);

echo $query->found_posts;

meta_key = custom_author_nameを持つnewsの投稿は10個しかないため、echo = 10となります。しかし、その特定のmeta_keyを持つpost_meta行を持たない何百ものnews投稿があります。 meta_queryは関与していないことに注意してください。 meta_valueは割り当てられていません。これは、投稿をmeta_keyで並べ替えるだけで、meta_valueでフィルタしないためです。

すべての投稿をorderbyで選択しないでください。そしてそれらを注文するだけ?

もしそうなら、なぜ結果はフィルタリングされますか? meta_keyが見つからない場合は、空の文字列を使用しないでください。

そうでない場合は、なぜですか?

すべてのニュース投稿にmeta_keyを入力した場合(空の文字列であっても)、期待どおりの結果が得られます。しかし、それはそこに存在する必要はないテーブル行がたくさんあるように見えます。

10
gdaniel

@ ambroseyaの答えで述べたように、それはそのように動作するはずです。メタクエリーを宣言すると、特定の値を探していなくても、宣言されたメタキーを持つ投稿のみがクエリーされます。すべての投稿をメタキーで並べ替える場合は、次のコードを使用します。

$args = array(
    'post_type' => 'news',
    'orderby' => 'meta_key',
    'order' => 'ASC',
    'meta_query' => array(
        'relation' => 'OR',
        array( 
            'key'=>'custom_author_name',
            'compare' => 'EXISTS'           
        ),
        array( 
            'key'=>'custom_author_name',
            'compare' => 'NOT EXISTS'           
        )
    ),
    'post_per_page'=>-1
);

$query = new WP_Query($args);

echo $query->found_posts;

これが行うことは、そのメタキーが宣言されているかどうかにかかわらず投稿を検索する高度なメタクエリを使用することです。 EXISTSを持つものが最初なので、meta_valueでソートするとき、それは最初のクエリを使います。

8
Manny Fleurmond

私は@Manny Fleurmondの答えを適用しようとしましたが、@Jakeのように'orderby' => 'meta_key''orderby' => 'meta_value'にする必要があるという誤植を修正した後でさえ、うまく動くようにはできませんでした。 (そして完全を期すためには'posts_per_page'ではなく'post_per_page'であるべきですが、それは検討中の問題には影響しません。)

@Manny Fleurmondの回答(実際にはタイプミスを修正したもの)によって実際に生成されたSQLクエリを見ると、これがあなたのものです。

SELECT   wp_{prefix}_posts.* FROM wp_{prefix}_posts
LEFT JOIN wp_{prefix}_postmeta ON (wp_{prefix}_posts.ID = wp_{prefix}_postmeta.post_id AND wp_{prefix}_postmeta.meta_key = 'custom_author_name' )
LEFT JOIN wp_{prefix}_postmeta AS mt1 ON ( wp_{prefix}_posts.ID = mt1.post_id )
WHERE 1=1  AND ( 
    wp_{prefix}_postmeta.post_id IS NULL 
    OR 
    mt1.meta_key = 'custom_author_name'
) AND wp_{prefix}_posts.post_type = 'news' AND
(wp_{prefix}_posts.post_status = 'publish' OR wp_{prefix}_posts.post_author = 1 AND wp_{prefix}_posts.post_status = 'private')
GROUP BY wp_{prefix}_posts.ID ORDER BY wp_{prefix}_postmeta.meta_value ASC

これは、WPがクエリvarsを解析する方法を示しています。各meta_query句ごとにテーブルを作成し、それらを結合する方法と順序を決定する方法を考え出します。あなたが'compare' => 'EXISTS'と一緒に一つの節を使うだけで、2番目の'compare' => 'NOT EXISTS'節をORで結合するなら(順序通りに)、順序はうまくいかないでしょう。その結果、LEFT JOINは最初の句/テーブルと2番目の句/テーブルの両方を結合するために使用されます - そしてWPをすべてまとめる方法は実際に'compare' => 'EXISTS'を使用して作成されたテーブルがmeta_valuesで読み込まれることを意味します私たちが興味を持っている'custom_author_name'フィールドだけではなく、どんなカスタムフィールドでも構いません。そのため、 'news'の特定のpost_typeにカスタムフィールドが1つしかない場合は、その句/テーブルで並べ替えるだけで望ましい結果が得られます。

私の状況のた​​めに働いた解決策は他の節/テーブル - NOT EXISTSのもの - で並べることでした。一見直感に反するかもしれませんが、WPがクエリ変数を解析する方法のため、meta_valueが後に続くカスタムフィールドによってのみ生成されるのはこのテーブルです。

(私がこれを考え出した唯一の方法は、私の場合はこのクエリと同等のものを実行することでした。

SELECT   wp_{prefix}_posts.ID, wp_{prefix}_postmeta.meta_value, mt1.meta_value FROM wp_{prefix}_posts
LEFT JOIN wp_{prefix}_postmeta ON (wp_{prefix}_posts.ID = wp_{prefix}_postmeta.post_id AND wp_{prefix}_postmeta.meta_key = 'custom_author_name' )
LEFT JOIN wp_{prefix}_postmeta AS mt1 ON ( wp_{prefix}_posts.ID = mt1.post_id )
WHERE 1=1  AND ( 
    wp_{prefix}_postmeta.post_id IS NULL 
    OR 
    mt1.meta_key = 'custom_author_name'
) AND wp_{prefix}_posts.post_type = 'news' AND
(wp_{prefix}_posts.post_status = 'publish' OR wp_{prefix}_posts.post_author = 1 AND wp_{prefix}_posts.post_status = 'private')
ORDER BY wp_{prefix}_postmeta.meta_value ASC

表示されている列を変更し、GROUP BY句を削除しただけです。これは何が起こっているのかを示しました - postmeta.meta_value列はすべてのmeta_keysから値を取得していましたが、mt1.meta_value列はニュースカスタムフィールドからmeta_valuesのみを取得していました。)

ソリューション

@Manny Fleurmondが言っているように、これはオーダーバイに使用される最初の句です。そのため、答えは単に句を入れ替えることです。

$args = array(
    'post_type' => 'news',
    'orderby' => 'meta_value',
    'order' => 'ASC',
    'meta_query' => array(
        'relation' => 'OR',
        array( 
            'key' => 'custom_author_name',
            'compare' => 'NOT EXISTS'           
        ),
        array( 
            'key' => 'custom_author_name',
            'compare' => 'EXISTS'           
        )
    ),
    'posts_per_page' => -1
);

$query = new WP_Query($args);

あるいは、節を連想配列にして、対応するキーで並べることもできます。

$args = array(
    'post_type' => 'news',
    'orderby' => 'not_exists_clause',
    'order' => 'ASC',
    'meta_query' => array(
        'relation' => 'OR',
        'exists_clause' => array( 
            'key' => 'custom_author_name',
            'compare' => 'EXISTS'           
        ),
        'not_exists_clause' => array( 
            'key' => 'custom_author_name',
            'compare' => 'NOT EXISTS'           
        )
    ),
    'posts_per_page' => -1
);

$query = new WP_Query($args);
2
jlad26

それは実際にそれが機能する方法です。

テーブル行を追加せずにこれを実行したい場合は、2つのクエリを実行する必要があります。一方はmeta_keyを使用したもので、結果は限られています。もう一方はリスト全体を取得します。次にPHPを使用して2つのクエリ結果を比較します(重複を削除するために他のクエリからmeta_keyの結果を削除するなど、設定に意味がある場合はすべて)。

1
ambroseya

残念ながら、それはWP_Queryが機能する方法ではありません。その「メタ」コンポーネントを追加するとすぐに、一種のフィルタが作成されました。 $query->requestをダンプすると、私の言っていることがわかります。

第二に、WP_Queryはメタ キー による順序付けをまったくサポートしていません。特定のキーについてはmeta value で並べることができますが、キー自体で並べることはできません。繰り返しますが、クエリをダンプして意味を確認してください。試してみると、「注文」コンポーネントがドロップアウトしていることがわかります。

これを機能させる最もクリーンな方法は、私の意見では、2つの短いフィルタです。

function join_meta_wpse_188287($join) {
  remove_filter('posts_join','join_meta_wpse_188287');
  global $wpdb;
  return ' INNER JOIN '.$wpdb->postmeta.' ON ('.$wpdb->posts.'.ID = '.$wpdb->postmeta.'.post_id)';
}
add_filter('posts_join','join_meta_wpse_188287');

function orderby_meta_wpse_188287($orderby) {
  remove_filter('posts_orderby','orderby_meta_wpse_188287');
  global $wpdb;
  return $wpdb->postmeta.'.meta_key ASC';
}
add_filter('posts_orderby','orderby_meta_wpse_188287');

$args = array(
    'post_type' => 'news',
    'post_per_page'=>-1
);
$q = new WP_Query($args);
var_dump($q->request); // debug
var_dump(wp_list_pluck($q->posts,'post_title')); // debug
0
s_ha_dum