続きはWEBで。

技術的なこともそうでないことも、自由気ままに書き留めていくためのサイト

カテゴリ一覧のメインクエリから子カテゴリを除外してみる

はじめに

親カテゴリ(ID:1)
    - 子カテゴリ1(ID:2)
        -- 孫カテゴリ1(ID:5)
        -- 孫カテゴリ2(ID:6)
    - 子カテゴリ2(ID:3)
    - 子カテゴリ3(ID:4)

上記のようなカテゴリ構造の場合、例えば「親カテゴリ」のカテゴリ一覧のメインクエリは以下のようになります。

SELECT
    SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts 
LEFT JOIN wp_term_relationships
    ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1=1 
    AND ( wp_term_relationships.term_taxonomy_id IN (1,2,3,4,5,6) )
    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

クエリを見てもらえれば分かるように、親カテゴリだけでなく親カテゴリの下の階層の子カテゴリ、孫カテゴリに紐付いた投稿も含まれます。

しかし私が欲しいのは、親カテゴリに紐付いた投稿だけ。
具体的には以下のようにメインクエリを書き換えていきたいと思います。

// 変更前
wp_term_relationships.term_taxonomy_id IN (1,2,3,4,5,6)

// 変更後
wp_term_relationships.term_taxonomy_id IN (1)

タクソノミー条件を書き換える

メインクエリを書き換えるにあたってwp_queryのタクソノミーのパラメータを確認してみると、以下のパラメータを書き換えるのが良さそうだなと。

include_children (真偽値) - 階層を持つタクソノミーの場合に子孫タクソノミーを含めるかどうか。デフォルトは true(含める)です。

現時点では「true(含める)」になっているため「false(含めない)」に書き換えたいと思います。

失敗例1

メインクエリを書き換えるにはやっぱり「pre_get_posts」だろうと。
単純にメインクエリのtax_queryを上書きすればいいかなとカスタムしてみました。

function custom_category_archive_query($query){
    if(!is_admin() && $query->is_main_query() && $query->is_category()){
        $term = get_queried_object();
        $tax_query = array(
            array(
                'taxonomy' => 'category',
                'field' => 'term_id',
                'terms' => array($term->term_id),
                'include_children' => false
            )
        );
        $query->set('tax_query', $tax_query);
    }
}
add_filter('pre_get_posts', 'custom_category_archive_query');

で、実行した結果がこちら。 なぜか無駄なLEFT JOINが増えてもれなく速度が落ちました・・・orz

SELECT
    SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts 
LEFT JOIN wp_term_relationships
    ON (wp_posts.ID = wp_term_relationships.object_id)
LEFT JOIN wp_term_relationships AS tt1
    ON (wp_posts.ID = tt1.object_id)
WHERE 1=1 
    AND ( wp_term_relationships.term_taxonomy_id IN (1) 
    AND tt1.term_taxonomy_id IN (1,2,3,4,5,6) )
    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

失敗例2

先の失敗を生かすべく、今度はメインクエリをきちんと確認してピンポイントに修正しようと。
メインクエリの連想配列内の該当キーを書き換えるカスタムしてみました。

function custom_category_archive_query($query){
    if(!is_admin() && $query->is_main_query() && $query->is_category()){
        $query->tax_query->queries[0]['include_children'] = false;
    }
}
add_filter('pre_get_posts', 'custom_category_archive_query');

しかしこれに関しては最初のクエリと全く変化がなく失敗・・・orz

SELECT
    SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts 
LEFT JOIN wp_term_relationships
    ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1=1 
    AND ( wp_term_relationships.term_taxonomy_id IN (1,2,3,4,5,6) )
    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

正解例

メインクエリ内のタクソノミー条件を書き換えるには「pre_get_posts」ではなく「parse_tax_query」にフックを掛けるのが正解だそう。

参考:WordPressのタクソノミー検索をカスタマイズする

function custom_search_parse_tax_query($query){
    if(!is_admin() && $query->is_main_query() && $query->is_category()){
        $query->tax_query->queries[0]['include_children'] = false;
    }
}
add_action('parse_tax_query', 'custom_search_parse_tax_query');

実行すると、見事に検索対象が親カテゴリの投稿だけになりました。

SELECT
    SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts 
LEFT JOIN wp_term_relationships
    ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1=1 
    AND ( wp_term_relationships.term_taxonomy_id IN (1) )
    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