Исключение или включение ID категорий в WP_Query

22 нояб. 2017 г., 09:06:04
Просмотры: 17.8K
Голосов: 7

У меня есть WordPress-сайт с более чем 300 категориями.

Теперь появилось требование дать пользователям возможность выбирать категории. Изначально я сделал так, чтобы все категории были предварительно выбраны, и если нужно исключить категорию, её можно снять выделение.

Проблема, с которой я столкнулся, заключается в том, как правильно выводить результаты в соответствии с выбранными категориями.

Мой первый подход был просто исключить все снятые категории, как показано ниже:

Например: исключить категории 10, 11, 12

$args = array(
    'category__not_in' => array('10','11','12')
);

Допустим, у меня есть запись, которая относится к категориям 12 и 13. Согласно приведённому выше коду, я не получу эту запись в результатах, так как исключаются записи из категории 12. Но по идее она должна быть в результатах, потому что категория 13 не была снята.

В качестве решения я мог бы использовать параметр 'category__in' со всеми выбранными ID категорий. Но меня беспокоит, что список будет очень длинным, даже если он формируется программно, и я не уверен в нагрузке на wp_query, учитывая что у меня более 300 категорий.

У кого-нибудь есть идеи, как лучше решить эту проблему?

0
Все ответы на вопрос 5
5
12

Как вы, вероятно, знаете, категории являются таксономиями. Когда вы используете аргументы типа category__in, это добавит таксономический запрос в ваш WP_Query(). Таким образом, ваша ситуация будет выглядеть примерно так:

$args = array(
    'post_type' => 'post',
    'tax_query' => array(
        'relation' => 'AND',
        array(
            'taxonomy' => 'category',
            'field'    => 'term_id',
            'terms'    => array( 12 ),
            'operator' => 'IN',
        ),
        array(
            'taxonomy' => 'category',
            'field'    => 'term_id',
            'terms'    => array( 11, 12, 13 ),
            'operator' => 'NOT IN',
        ),
    ),
);
$query = new WP_Query( $args );

Я бы не стал беспокоиться о проблемах с производительностью в данном случае. Это, скорее всего, ваше единственное решение, если вы не хотите напрямую запрашивать записи из базы данных с помощью SQL-запроса (это может немного улучшить производительность).

26 нояб. 2017 г. 10:10:53
Комментарии

Но разве у нас не будет той же самой проблемы? Автор хотел использовать что-то, чтобы избежать перечисления всех категорий, но в вашем случае придется перечислять категории внутри части IN, верно?

Temani Afif Temani Afif
26 нояб. 2017 г. 20:40:00

Да, это снова приводит к опции category__in

Janith Chinthana Janith Chinthana
28 нояб. 2017 г. 15:44:16

category__in или category__and — это то, что вам нужно, как сказал Jack. Если вы хотите ускорить свои запросы, возможно, стоит рассмотреть использование transient-ов.

Tim Hallman Tim Hallman
29 нояб. 2017 г. 07:45:02

Также ответ graziondev ниже тоже верен, нет необходимости использовать category__in при первоначальном формировании запроса, так как вы по умолчанию включаете все категории. Загрузите этот запрос в transient, а затем фильтруйте последующие запросы из этих данных, если вам нужна скорость.

Tim Hallman Tim Hallman
29 нояб. 2017 г. 07:47:30

Какое преимущество использования category__in и category__not__in вместо простого указания отрицательных категорий в ( 'cat', '-5' )?

Nora McDougall-Collins Nora McDougall-Collins
7 июл. 2023 г. 20:56:29
0

Предположим, у нас есть 4 записи и 4 категории.

+----+--------+
| ID |  Запись |
+----+--------+
|  1 | Тест 1 |
|  2 | Тест 2 |
|  3 | Тест 3 |
|  4 | Тест 4 |
+----+--------+

+----+------------+
| ID |  Категория |
+----+------------+
|  1 | Категория 1 |
|  2 | Категория 2 |
|  3 | Категория 3 |
|  4 | Категория 4 |
+----+------------+

+--------+------------------------+
|  Запись |        Категория       |
+--------+------------------------+
| Тест 1 | Категория 1, Категория 2 |
| Тест 2 | Категория 2             |
| Тест 3 | Категория 3             |
| Тест 4 | Категория 4             |
+--------+------------------------+

Если я правильно понял ваш вопрос, вы хотите получить запись Тест 1, используя параметр category__not_in. Аргументы вашего запроса будут выглядеть так:

$args = array(
    'category__not_in' => array(2, 3, 4)
);

Проблема с category__not_in заключается в том, что он генерирует SQL-запрос с NOT IN SELECT.

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

NOT IN SELECT исключит все записи, включая Тест 1. Если бы этот SQL использовал JOIN вместо NOT IN SELECT, это сработало бы.

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 NOT IN (2, 3, 4))
  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

Приведенный выше SQL вернет только запись Тест 1. Мы можем сделать небольшой трюк, чтобы создать такой запрос с использованием класса WP_Query. Вместо использования параметра category__not_in заменим его на параметр category__in и добавим фильтр post_where, который напрямую изменит SQL для наших целей.

function wp_286618_get_posts() {

    $query = new WP_Query( array(
        'post_type' => 'post',
        'category__in' => array( 2, 3, 4 ) // Используем `category__in`, чтобы заставить SQL-запрос использовать JOIN.
    ) );

    return $query->get_posts();
}

function wp_286618_replace_in_operator($where, $object) {

    $search = 'term_taxonomy_id IN'; // Ищем оператор IN, созданный параметром `category__in`.
    $replace = 'term_taxonomy_id NOT IN'; // Заменяем IN на NOT IN

    $where = str_replace($search, $replace, $where);

    return $where;
}

add_filter( 'posts_where', 'wp_286618_replace_in_operator', 10, 2 ); // Добавляем фильтр для замены оператора IN

$posts = wp_286618_get_posts(); // Вернет только запись Тест 1

remove_filter( 'posts_where', 'wp_286618_replace_in_operator', 10, 2 ); // Удаляем фильтр, чтобы он не влиял на другие запросы

Преимущество этого решения перед другими заключается в том, что мне не нужно знать ID других категорий, и оно сохранит ваш цикл записей чистым.

29 нояб. 2017 г. 23:50:57
1

Почему вы заранее отмечаете все категории? Разве не проще отображать все результаты и при этом иметь все категории неотмеченными? Затем, когда пользователь выберет несколько (кто будет выбирать 300 категорий?), вы можете выполнить запрос с category__in.

23 нояб. 2017 г. 00:28:57
Комментарии

Но для меня это не вариант. Обычно люди склонны выбирать почти все категории, но некоторым не нравится видеть лишние посты, поэтому в таком случае они могут отменить выбор категорий. Но по моим предположениям, более 90% категорий будут выбраны. Именно поэтому мы предлагаем отмену выбора, а не выбор.

Janith Chinthana Janith Chinthana
23 нояб. 2017 г. 07:15:46
1

Не уверен, но думаю, что это нельзя сделать с помощью стандартного поведения/опций WP_Query. Возможно, обходным решением будет реализация функции, которая выполнит эту проверку после выбора всех записей.

Конечно, недостаток этого метода в том, что сначала нужно выбрать все записи, а затем отфильтровать их, но это может быть решением вашей проблемы. Вот как это можно сделать:

<?php 

function test_categorie($cats,$ex_cats) {

    $test = false; //считаем, что запись исключена, пока не найдется категория, не входящая в массив
    foreach ($cats as $cat){
        if(!in_array(intval($cat->term_id),$ex_cats)) {
            $test = true;
            //Можно прервать цикл, так как у записи есть хотя бы одна неисключенная категория
            break;
        }

    }
    return $test;
}
//Определяем исключаемые категории здесь
$exclude_cat = array(11,12,13);

$args = array(
    'posts_per_page'   => -1,
    'post_type'        => 'post',
);

$the_query = new WP_Query($args );
if ( $the_query->have_posts() ) :
     while ( $the_query->have_posts() ) : $the_query->the_post(); 
            //выполняем проверку, если (false) - пропускаем запись и переходим к следующей
            if(!test_categorie(get_the_category(),$exclude_cat))
                continue;

            /*  
                Ваш код здесь
            */

    endwhile;
    wp_reset_postdata(); 
endif;

?>
26 нояб. 2017 г. 10:33:48
Комментарии

Этот ответ сработает, я думаю, хотя у вас могут возникнуть некоторые проблемы, если использование или вывод зависят от корректности запроса в отношении исключения — например, для точной пагинации: вы можете получить страницы, которые должны показывать, скажем, 5 элементов, но вместо этого покажут 2. Конечно, есть обходные пути и для этого, но всегда будут компромиссы того или иного рода.

CK MacLeod CK MacLeod
29 нояб. 2017 г. 09:55:09
0

Попробуйте этот вариант. Думаю, это немного "грязно", но у меня отлично работает!

    $skills = get_user_meta($curr_id , 'designer_skills');
    $skills = array_map('intval', explode(',' , $skills[0]));
    $args = array(
            'numberposts' => -1,
            'post_type' => 'project',
            'meta_query'    => array(
                'relation'      => 'AND',
                array(
                    'key'       => 'status',
                    'compare'   => '=',
                    'value'     => 'open'
                ),
                array(
                   'key'        => 'payment_status',
                    'compare'   => '=',
                    'value'     => true
               )
            )
        );
        $posts = get_posts( $args );
        if ($posts) {
            $count = 0;
            foreach ($posts as $project) {
                if (in_array(get_the_terms( $project->ID, 'projects')[0] -> term_id , $skills)){
                    //реализуйте здесь свой собственный код
                    }
            }
    }
29 нояб. 2017 г. 13:44:59