Класс WP_Query — это сердце выборки контента в WordPress. Без него не обходится ни один более-менее сложный сайт: кастомные списки записей, фильтры по категориям, выборка по произвольным полям, пагинация. Рассказываю, как выжать из WP_Query максимум и не просесть по производительности.
Что нужно знать о WP_Query? WP_Query — основной класс WordPress для выборки записей из БД. Позволяет фильтровать по десяткам параметров: тип записи, таксономии, мета-поля, даты, статусы. Главные правила: (1) всегда используйте pre_get_posts вместо прямого изменения основного запроса, (2) не вытаскивайте всё подряд — ограничивайте posts_per_page и поля через fields, (3) для сложных мета-запросов используйте meta_query с правильными типами сравнения.
Основы WP_Query: что внутри и зачем это знать
WP_Query — это не просто «ещё один способ получить записи». Это полноценный SQL-билдер с кэшированием, хуками и поддержкой пагинации. Когда вы вызываете new WP_Query( $args ), WordPress:
- Строит SQL-запрос на основе переданных параметров
- Выполняет запрос к базе данных
- Кэширует результаты (при правильной настройке)
- Наполняет глобальные переменные (
$wp_query,$post) - Вызывает хуки
the_post,loop_start,loop_end
В отличие от get_posts(), WP_Query даёт полный контроль над пагинацией, подсчётом общего количества записей и навигацией.
Базовая структура
$query = new WP_Query( array(
'post_type' => 'post',
'posts_per_page' => 10,
'orderby' => 'date',
'order' => 'DESC',
) );
if ( $query->have_posts() ) :
while ( $query->have_posts() ) : $query->the_post();
the_title( '<h2>', '</h2>' );
the_excerpt();
endwhile;
wp_reset_postdata();
endif;Казалось бы, ничего сложного. Но дьявол — в деталях.
Мета-запросы: фильтрация по произвольным полям
Самый частый сценарий в реальных проектах — выборка записей по значениям произвольных полей (ACF, CMB2, или ручных meta-box).
Простая проверка meta_key
$query = new WP_Query( array(
'meta_key' => 'price',
'meta_value' => '1000',
'meta_compare' => '>=',
'type' => 'NUMERIC',
) );Множественные условия: meta_query
$query = new WP_Query( array(
'post_type' => 'product',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'price',
'value' => array( 500, 5000 ),
'compare' => 'BETWEEN',
'type' => 'NUMERIC',
),
array(
'key' => 'in_stock',
'value' => 'yes',
'compare' => '=',
),
),
) );Параметр type критически важен. Без него WordPress сравнивает числа как строки, и «999» окажется больше «10000».
Поддержка ACF
Advanced Custom Fields умеет оптимизировать запросы через свои функции:
$query = new WP_Query( array(
'post_type' => 'event',
'meta_query' => array(
array(
'key' => 'event_date',
'value' => date( 'Ymd' ),
'compare' => '>=',
'type' => 'DATE',
),
),
'orderby' => 'meta_value',
'meta_key' => 'event_date',
'order' => 'ASC',
) );Обратите внимание: orderby работает только в паре с meta_key. Если нужно сортировать по одному полю, а фильтровать по другому — обязательно указывайте meta_key для сортировки.
Таксономии: когда одной category не хватает
Кастомные таксономии — стандартный инструмент любого серьёзного проекта на WordPress.
Простая фильтрация по таксономии
$query = new WP_Query( array(
'post_type' => 'portfolio',
'tax_query' => array(
array(
'taxonomy' => 'skill',
'field' => 'slug',
'terms' => 'web-design',
),
),
) );Сложные условия с несколькими таксономиями
$query = new WP_Query( array(
'post_type' => 'portfolio',
'tax_query' => array(
'relation' => 'OR',
array(
'taxonomy' => 'skill',
'field' => 'slug',
'terms' => array( 'web-design', 'ux' ),
'operator' => 'IN',
),
array(
'taxonomy' => 'technology',
'field' => 'slug',
'terms' => 'wordpress',
'operator' => 'IN',
),
),
) );Исключение категорий (NOT IN)
$query = new WP_Query( array(
'post_type' => 'post',
'tax_query' => array(
array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => array( 'uncategorized' ),
'operator' => 'NOT IN',
),
),
'posts_per_page' => 12,
) );Объединение meta_query и tax_query
Настоящая мощь WP_Query раскрывается, когда комбинируете оба типа запросов:
$query = new WP_Query( array(
'post_type' => 'realty',
'posts_per_page' => 20,
'meta_query' => array(
array(
'key' => 'price',
'value' => 10000000,
'compare' => '<=',
'type' => 'NUMERIC',
),
array(
'key' => 'area',
'value' => 50,
'compare' => '>=',
'type' => 'NUMERIC',
),
),
'tax_query' => array(
array(
'taxonomy' => 'city',
'field' => 'slug',
'terms' => 'moscow',
),
array(
'taxonomy' => 'type',
'field' => 'slug',
'terms' => 'apartment',
),
),
'orderby' => 'meta_value_num',
'meta_key' => 'price',
'order' => 'ASC',
) );Этот запрос найдёт квартиры в Москве площадью от 50 м² с ценой до 10 млн рублей и отсортирует по возрастанию цены. Без WP_Query пришлось бы писать кастомный SQL с JOIN.
Дата, автор и статус — недооценённые параметры
Выборка по датам
// Записи за последние 30 дней
$query = new WP_Query( array(
'date_query' => array(
array(
'after' => '30 days ago',
'before' => 'today',
'inclusive' => true,
),
),
) );
// Записи за конкретный месяц
$query = new WP_Query( array(
'date_query' => array(
array(
'year' => 2025,
'month' => 6,
),
),
) );Фильтр по автору
// Несколько авторов
$query = new WP_Query( array(
'author__in' => array( 2, 5, 12 ),
) );
// Исключить автора
$query = new WP_Query( array(
'author__not_in' => array( 1 ),
) );Статусы записей
$query = new WP_Query( array(
'post_type' => array( 'post', 'page' ),
'post_status' => array( 'publish', 'draft', 'pending' ),
'perm' => 'readable',
) );Производительность: частые ошибки и их решения
Самое больное место WP_Query — неоптимальные запросы, которые кладут базу на сайтах с десятками тысяч записей.
Ошибка 1: Выборка без ограничения полей
// ПЛОХО: тянет все поля
$query = new WP_Query( array( 'posts_per_page' => 100 ) );
// ХОРОШО: только ID
$query = new WP_Query( array(
'posts_per_page' => 100,
'fields' => 'ids',
) );Параметр fields => 'ids' сокращает запрос с SELECT wp_posts.* до SELECT wp_posts.ID.
Ошибка 2: Игнорирование no_found_rows
$query = new WP_Query( array(
'posts_per_page' => 3,
'no_found_rows' => true,
) );Без этого WordPress выполняет второй SQL-запрос SELECT COUNT(*). Если пагинация не нужна — отключайте.
Ошибка 3: Лишнее кэширование мета-полей и терминов
$query = new WP_Query( array(
'posts_per_page' => 50,
'fields' => 'ids',
'update_post_meta_cache' => false,
'update_term_cache' => false,
) );Ошибка 4: WP_Query внутри цикла WP_Query
Если без вложенного запроса не обойтись — используйте wp_reset_postdata() и ставьте no_found_rows => true на внутреннем запросе. Лучше — вынесите данные во внешний запрос через meta_query и tax_query.
pre_get_posts: правильная модификация основного запроса
Вместо создания новых WP_Query копий часто правильнее модифицировать существующий запрос через хук pre_get_posts.
Изменение количества записей на странице архива
add_action( 'pre_get_posts', function( $query ) {
if ( ! is_admin() && $query->is_main_query() && is_post_type_archive( 'portfolio' ) ) {
$query->set( 'posts_per_page', 12 );
$query->set( 'orderby', 'menu_order' );
$query->set( 'order', 'ASC' );
}
} );Исключение категории из главной
add_action( 'pre_get_posts', function( $query ) {
if ( ! is_admin() && $query->is_home() && $query->is_main_query() ) {
$query->set( 'cat', '-7' );
}
} );Фильтрация кастомного архива по мета-полю
add_action( 'pre_get_posts', function( $query ) {
if ( ! is_admin() && $query->is_main_query() && $query->is_post_type_archive( 'event' ) ) {
$meta_query = $query->get( 'meta_query' ) ?: array();
$meta_query[] = array(
'key' => 'event_date',
'value' => date( 'Ymd' ),
'compare' => '>=',
'type' => 'DATE',
);
$query->set( 'meta_query', $meta_query );
$query->set( 'orderby', 'meta_value' );
$query->set( 'meta_key', 'event_date' );
$query->set( 'order', 'ASC' );
}
} );Без проверки is_main_query() вы сломаете административную панель и виджеты.
Заключение
WP_Query — мощный, но требовательный инструмент. Основные правила:
- Используйте
pre_get_postsдля модификации основных запросов - Оптимизируйте запросы:
fields => 'ids',no_found_rows, отключайте кэширование - Не делайте WP_Query внутри WP_Query — это убивает производительность
- Комбинируйте
meta_queryиtax_queryвместо кастомных SQL - Не забывайте про
wp_reset_postdata()после каждого кастомного цикла
Хотите углубиться? Почитайте про свои поля оформления заказа в WooCommerce. А если только начинаете — загляните в раздел разработки WordPress.
Как получить только ID записей в WP_Query?
Используйте параметр 'fields' => 'ids'. Это сокращает SQL-запрос и снижает нагрузку на память.
Чем отличается WP_Query от get_posts?
get_posts() использует WP_Query внутри, но сбрасывает глобальные переменные и отключает подсчёт строк по умолчанию. WP_Query даёт полный контроль над пагинацией.
Как отфильтровать записи по диапазону цен?
Используйте 'compare' => 'BETWEEN' с массивом: 'value' => array(1000, 5000). Обязательно указывайте 'type' => 'NUMERIC'.
Когда использовать pre_get_posts?
Если нужно изменить стандартный запрос на странице архива — используйте pre_get_posts. Новый WP_Query создавайте только для дополнительных выборок.
Как ускорить WP_Query на сайте с 50 000+ записей?
Три правила: (1) 'no_found_rows' => true если не нужна пагинация; (2) отключайте кэширование мета-полей и терминов; (3) для числовой сортировки используйте 'orderby' => 'meta_value_num'.
Можно ли сортировать записи по произвольному полю?
Да: укажите 'orderby' => 'meta_value' или 'meta_value_num' и 'meta_key' => 'field_name'.

