web-dev-qa-db-ja.com

特定のmeta_key値を控えるためのpre_get_postsに対するmeta_queryの使用

フロントページのメインのクエリで単純なフィルタを実行しています。特定のメタ値を持つ投稿をすべて除外する必要があります(meta_keyが設定されていない投稿、またはmeta_key値が一致しない投稿のみを除外)除外する)。しかし、何らかの理由で、それは期待どおりに動作していません。

/**
 * Hide posts from main query on front page.
 *
 * @since  1.0.0
 *
 * @param  object $query WP Query object.
 * @return object        Modified WP Query object.
 */
function wpse_exclude_posts_from_main_query( $query ) {

    // Make sure this only runs on the main query on the front page
    if ( is_front_page() && $query->is_main_query() ) {

        // Exclude posts that have been explicitly set to hidden
        $query->set('meta_query', array(
            'relation' => 'OR',
            // Include posts where the meta key isn't set
            array(
                'key'     => '_wpse_custom_key',
                'value'   => 'asdf', // A value must exist due to https://core.trac.wordpress.org/ticket/23268
                'compare' => 'NOT EXISTS',
            ),
            // Include posts where the meta key isn't explicitly true
            array(
                'key'     => '_wpse_custom_key',
                'value'   => true,
                'compare' => '!=',
            ),
        ) );

    }

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

このメタクエリの各半分は、それ自体では完全にうまく機能します。キーが存在しないすべての投稿、またはキーが存在し、正しくないすべての投稿を表示できます。上記のように、組み合わせて使用​​すると、キーが存在し、それが正しくない投稿のみが表示されます(NOT EXISTS部分は完全に無視されます)。

生成されているSQLは次のとおりです(posts_requestフィルターに従って)。

SELECT     SQL_CALC_FOUND_ROWS wp_posts.*
FROM       wp_posts
LEFT JOIN  wp_postmeta
           ON (
               wp_posts.ID = wp_postmeta.post_id
               AND wp_postmeta.meta_key = '_wpse_custom_key'
           )
INNER JOIN wp_postmeta AS mt1
           ON (wp_posts.ID = mt1.post_id)
WHERE      1=1
           AND wp_posts.post_type = 'post'
           AND wp_posts.post_status = 'publish'
           AND (
               wp_postmeta.post_id IS NULL
               OR (
                   mt1.meta_key = '_wpse_custom_key'
                   AND CAST(mt1.meta_value AS CHAR) != '1'
               )
           )
GROUP BY   wp_posts.ID
ORDER BY   wp_posts.post_date DESC
LIMIT      0, 10

参照されているバグが強調表示されているように、ANDではなくORをWHERE句で実際に使用していることがわかります。 https://core.trac.wordpress.org /チケット/ 23268

私は完全に自慢しているので、誰かがいくつかの非常に必要な洞察を提供できることを願っています。

6
Brian Richards

はい、それは変な振る舞いをします。いいえ、私を撃ってください、私は正確にその理由と方法についての修正を得ることができません(スタック結合はおそらくそれです)。

私はそれを反転すると言います。あなたが見たくない唯一の投稿はtrueカスタムキーを持つものです。それらのIDを別々に問い合わせ、結果をpost__not_inに送り、このメタ問い合わせを完全に捨てます。

3
Rarst

私は、合成SQLが問題であると疑っていたTwitterの人々と一緒にいます。 pre_get_posts、hookを使用する代わりに、posts_clausesで独自のSQLを実行するための別の関数を書いたところです。期待される投稿は正しく返されます。

これは、上記と同じ要件に従った例です。

/**
 * Hide posts from main query on front page.
 *
 * @since  1.0.0
 *
 * @param  object $query WP Query object.
 * @return object        Modified WP Query object.
 */
function wpse_exclude_posts_clauses( $pieces, $query ) {

    // Make sure this only runs on the main query on the front page
    if ( is_front_page() && $query->is_main_query() ) {
        $pieces['join'] = "
            LEFT JOIN  $wpdb->postmeta as hidden_meta
                       ON (
                           $wpdb->posts.ID = hidden_meta.post_id
                           AND hidden_meta.meta_key = '_wpse_custom_key'
                       )
            ";
        $pieces['where'] = "
            AND (
                hidden_meta.post_id IS NULL
                OR CAST(hidden_meta.meta_value AS CHAR) != '1'
            )
            ";
    }

    return $pieces;
}
add_filter( 'posts_clauses', 'wpse_exclude_posts_clauses', 10, 2 );
2
Brian Richards

私はあなたの問題は除外する値として真のブール値を使用していると思います。除外値を文字列 'asdf'に変更し、ダミー文字列を変更したところ、うまくいきました。

クエリSQLは、グローバル$ wp_queryオブジェクトのダンプからコピーアンドペーストされました。最初のクエリからわかるように、データベースには5つの投稿があります。投稿のうち3つにexcludeメタキーがあり、1つにexcludeメタ値が含まれています。

mysql> SELECT ID FROM wpomni_posts WHERE post_type ='post' AND post_status = 'publish';
+------+
| ID   |
+------+
|    1 |
| 5900 |
| 5904 |
| 5908 |
| 5925 |
+------+
5 rows in set (0.00 sec)
    mysql> SELECT SQL_CALC_FOUND_ROWS  wpomni_posts.ID FROM wpomni_posts  
        -> LEFT JOIN wpomni_postmeta 
        -> ON (wpomni_posts.ID = wpomni_postmeta.post_id 
        -> AND wpomni_postmeta.meta_key = '_wpse_custom_key')
        -> INNER JOIN wpomni_postmeta AS mt1 
        -> ON (wpomni_posts.ID = mt1.post_id) 
        -> WHERE 1=1  
        -> AND wpomni_posts.post_type = 'post' 
        -> AND (wpomni_posts.post_status = 'publish' 
        -> OR wpomni_posts.post_status = 'private') 
        -> AND ( wpomni_postmeta.post_id IS NULL
        -> OR  (mt1.meta_key = '_wpse_custom_key' 
        -> AND CAST(mt1.meta_value AS CHAR) != 'asdf') ) 
        -> GROUP BY wpomni_posts.ID 
        -> ORDER BY wpomni_posts.post_date DESC LIMIT 0, 18;
    +------+
    | ID   |
    +------+
    | 5925 |
    | 5908 |
    | 5900 |
    |    1 |
    +------+
    4 rows in set (0.00 sec)

    mysql> SELECT * FROM wpomni_postmeta WHERE meta_key = '_wpse_custom_key';
    +---------+---------+------------------+----------------+
    | meta_id | post_id | meta_key         | meta_value     |
    +---------+---------+------------------+----------------+
    |  137033 |    5908 | _wpse_custom_key | 1              |
    |  137034 |    5904 | _wpse_custom_key | asdf           |
    |  137042 |    5925 | _wpse_custom_key | what the blank |
    +---------+---------+------------------+----------------+
    3 rows in set (0.00 sec)
0
Chris_O