web-dev-qa-db-ja.com

カテゴリ構造に従って複雑な結果セットを取得する

カテゴリ構造に従って、WordPressデータベースから複雑な結果セットを取得する必要があります。

まず、私のカテゴリー構造について説明します。下のように3段構造になっています。

flight [level 1] [ID : 100]
    - class      [level 2] [ID : 200]
        -- economy  [level 3] [ID : 201]
        -- business [level 3] [ID : 202]
        -- first    [level 3] [ID : 203]
    - alliance   [level 2] [ID : 210]
        -- star     [level 3] [ID : 211]
        -- oneworld [level 3] [ID : 212]
        -- skyteam  [level 3] [ID : 213]

次にアルゴリズム:

flightカテゴリとしてタグ付けされたすべての投稿、または次のルールで子の1つを取得する必要があります。

economyのタグが付いた投稿を除外する必要があります。

  1. ただし、兄弟(businessまたはfirst)のいずれかにタグが付けられている場合は、結果セットに含まれているはずです。
  2. allianceまたはその子がタグ付けされた投稿は考慮しないでくださいIF economyも同じ投稿でタグ付けされています[ただし、一般に、economyカテゴリを除外すると、この規則はいずれも完全に埋められます]

これまでの私のアプローチ:

私は次のタイプの引数でtax_queryを試していました

$args = array(
    'post_type' => 'post',
    'tax_query' => array(
        'relation' => 'AND',
        array(
            'taxonomy' => 'category',
            'field'    => 'term_id',
            'terms'    => array( 100 ),
            'include_children' => 1,
        ),
        array(
            'relation' => 'AND',
            array(
                'taxonomy' => 'category',
                'field'    => 'term_id',
                'terms'    => array( 200 ),
                'include_children' => 1,
            ),
            array(
                'taxonomy' => 'category',
                'field'    => 'term_id',
                'terms'    => array( 201 ),
                'operator' => 'NOT IN',
            ),
        ),
    ),
);
$query = new WP_Query( $args );

しかし、ここでの問題は、ルール番号1を満たしていないことです。businessおよびfirstのタグが付いた投稿がeconomyに関係なく必要な場合でも、上記のクエリはeconomyでタグ付けされたすべての投稿を無視します。

純粋なSQLアプローチも検討します。私がこれを数日間苦労していたので、どんな助けも高く評価されます。

2

ここで声を出して考えています...

$args = array(
    'post_type' => 'post',
    'tax_query' => array(
        'relation' => 'OR',
        array(
            'taxonomy' => 'category',
            'field'    => 'term_id',
            'terms'    => array( 202, 203 ),
            'include_children' => 1,
        ),
        array(
            'relation' => 'AND',
            array(
                'taxonomy' => 'category',
                'field'    => 'term_id',
                'terms'    => array( 100 ),
                'include_children' => false,
            )
        ),
    ),
);
$query = new WP_Query( $args );

分類用語ID 202、203などのすべての投稿を取得...

OR

分類用語ID 100のすべての投稿のみを取得(子供を除く)

実際、上記は単に(ネストされたクエリなし)と表現できます。

$args = array(
    'post_type' => 'post',
    'tax_query' => array(
        'relation' => 'OR',
        array(
            'taxonomy' => 'category',
            'field'    => 'term_id',
            'terms'    => array( 202, 203 ),
            'include_children' => 1,
        ),
        array(
            'taxonomy' => 'category',
            'field'    => 'term_id',
            'terms'    => array( 100 ),
            'include_children' => false,
        )
    ),
);
$query = new WP_Query( $args );

分類用語ID 202、203などのすべての投稿を取得...

OR

分類用語ID 100のすべての投稿のみを取得(子供を除く)

そして再び...または(ライターにsave_postまたは類似のフックを使用して子カテゴリを常に設定するように強制する場合):

$args = array(
    'post_type' => 'post',
    'tax_query' => array(
        'relation' => 'AND',
        array(
            'taxonomy' => 'category',
            'field'    => 'term_id',
            'terms'    => array( 202, 203 ),
            'include_children' => 1,
        ),
    ),
);
$query = new WP_Query( $args );

分類用語ID 202、203などのすべての投稿を取得...

更新

これは私の迅速なアプローチになります:

$terms = get_terms(array(
        'taxonomy' => 'category',
        'exclude' => 201
));

$term_ids = array_column( $terms, 'term_id' );

$args = array(
    'post_type' => 'post',
    'tax_query' => array(
        'relation' => 'AND',
        array(
            'taxonomy' => 'category',
            'field'    => 'term_id',
            'terms'    => $term_ids,
            'include_children' => 1,
        ),
    ),
);
$query = new WP_Query( $args );

$post_term_ids = [];

foreach ( $query->posts as $post ) {
    $post_term_ids[$post->ID] = implode( ', ', array_column(
        wp_get_object_terms( $post->ID, 'category' ), 'name', 'term_id'
    ));
}

返されるサンプルデータセットは次のとおりです。

array (
  547 => 'Alliance, Business, Class, Flights, Oneworld',
  540 => 'Business, Class, Economy, Flights',
  605 => 'Star',
  524 => 'Alliance, Flights, Oneworld',
  594 => 'Skyteam',
  569 => 'Star',
  551 => 'Flights, Oneworld, Star',
  582 => 'Business',
  528 => 'Business, Class, Economy, Flights',
  565 => 'Skyteam',
)

'exclude' => 201を呼び出すときにget_termsを削除すると、次のような結果が表示されます。

array (
  547 => 'Alliance, Business, Class, Flights, Oneworld',
  540 => 'Business, Class, Economy, Flights',
  605 => 'Star',
  524 => 'Alliance, Flights, Oneworld',
  594 => 'Skyteam',
  569 => 'Star',
  551 => 'Flights, Oneworld, Star',
  582 => 'Business',
  528 => 'Business, Class, Economy, Flights',
  565 => 'Economy', // <-- WHAT WE DO NOT WANT
)

ご覧のとおり、economyのみの投稿を除くすべての投稿を返しています。 economyが存在するが別の分類も存在する場合、その投稿が返されます。キーは投稿IDです。

注:私のサンプル結果は、コンテンツ作成者が祖先を割り当てるのを忘れた場合の潜在的な値を示しています。すでに述べたように、それは別の質問で解決できます。

アップデート#2

# change the IDs below to match your environment
$class    = 92; // ancestor
$alliance = 96; // ancestor
$economy  = 93; // child

$terms_class = get_terms(array(
    'taxonomy' => 'category',
    'exclude'  => [$alliance, $economy],
    'child_of' => $class,
));

$terms_alliance = get_terms(array(
    'taxonomy' => 'category',
    'child_of' => $alliance,
));

$term_ids_class    = array_column($terms_class, 'term_id');
$term_ids_alliance = array_column($terms_alliance, 'term_id');

$args = array(
    'post_type'      => 'post',
    'posts_per_page' => -1,
    'tax_query'      => array(
        'relation' => 'OR',
        array(
            'relation' => 'AND',
            array(
                'taxonomy' => 'category',
                'field'    => 'term_id',
                'terms'    => $term_ids_class,
            ),
            array(
                'taxonomy' => 'category',
                'field'    => 'term_id',
                'terms'    => $term_ids_alliance,
                'operator' => 'NOT IN',
            ),
        ),
        array(
            'relation' => 'AND',
            array(
                'taxonomy' => 'category',
                'field'    => 'term_id',
                'terms'    => $term_ids_alliance,
            ),
            array(
                'taxonomy' => 'category',
                'field'    => 'term_id',
                'terms'    => $economy,
                'operator' => 'NOT IN',
            ),
        ),

    ),
);

$query = new WP_Query( $args );

$post_term_ids = [];

foreach ( $query->posts as $post ) {
    $post_term_ids[$post->ID] = implode( ', ', array_column(
        wp_get_object_terms( $post->ID, 'category' ), 'name', 'term_id'
    ));
}

私のサンプルデータセットでは、次のような結果が得られます。

array (
  547 => 'Alliance, Business, Class, Flights, Oneworld',
  540 => 'Business, Class, Economy, Flights',
  524 => 'Alliance, Flights, Oneworld',
  594 => 'Skyteam',
  569 => 'Star',
  551 => 'Flights, Oneworld, Star',
  582 => 'Business',
  528 => 'Business, Class, Economy, Flights',
  589 => 'Oneworld',
  603 => 'Oneworld',
  584 => 'Oneworld',
  585 => 'First',
  601 => 'First',
  543 => 'Business, Class, Economy, Flights',
  572 => 'First',
  578 => 'Business',
  592 => 'Alliance, Business, Class, Flights, Star',
  563 => 'Star',
  559 => 'Skyteam',
  575 => 'Star',
  549 => 'Flights, Oneworld, Skyteam',
  596 => 'Star',
  534 => 'Class, First, Flights',
  561 => 'Star',
  556 => 'Star',
  587 => 'Oneworld',
)

SQLは次のとおりです。

SELECT 
  wp_posts.* 
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 (94, 95) 
      AND wp_posts.ID NOT IN (
        SELECT 
          object_id 
        FROM 
          wp_term_relationships 
        WHERE 
          term_taxonomy_id IN (97, 98, 99)
      )
    ) 
    OR (
      tt1.term_taxonomy_id IN (97, 98, 99) 
      AND wp_posts.ID NOT IN (
        SELECT 
          object_id 
        FROM 
          wp_term_relationships 
        WHERE 
          term_taxonomy_id IN (93)
      )
    )
  ) 
  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

信じられないほど効率的ではありませんが、それはそれについて取り組む力のない方法です。

2
Adam