# WP_QUERY: Как сравнивать значения полей метаданных в запросе meta_query
WP_Query не позволяет напрям��ю сравнивать значения полей метаданных. Однако, в meta_query можно указать для сравнений такие операторы, как BETWEEN
, >
, >=
, <
, <=
. Но в параметре значения нам нужно указать конкретные данные. Если необходимо сравнить значения двух метаданных, WP не справляется с этой задачей без изменения самого запроса. Это может быть сложно для начинающих пользователей и иногда кажется нерешаемой задачей.
## Пример использования
Представьте, у вас есть карточка товара с двумя метаполями: "Этаж" (room_floor
) и "Максимальный этаж в здании" (room_floor_max
).

Для фильтрации объектов необходимо основываться на количестве этажей. Например, "Не первый этаж" — для этого я передаю значение первого этажа в параметре значения.

### Вопрос
Как обработать последний этаж? Мне нужно сравнить два метаполя (room_floor
и room_floor_max
): если они равны, то объект не отображается. Например, если объект находится на 5-м этаже из 5 возможных (5/5), то такой объект подходит для фильтрации.

## Решение
Эту задачу можно решить следующим образом:
```php
$args = [
'post_type' => 'post',
'posts_per_page' => 50,
'meta_query' => [
'relation' => 'AND',
[
'key' => 'room_floor',
'value' => 1,
'compare' => '!=',
'type' => 'NUMERIC',
],
[
'key' => 'room_floor_max',
'value' => 'wp_postmeta.meta_value', // заглушка
'compare' => '!=',
'type' => 'NUMERIC',
],
],
];
$closure = function( $clauses ) {
$clauses['where'] = str_replace( "'wp_postmeta.meta_value'", 'wp_postmeta.meta_value', $clauses['where'] );
return $clauses;
};
add_filter( 'posts_clauses', $closure );
$my_posts = get_posts( [ 'suppress_filters' => false ] + $args );
remove_filter( 'posts_clauses', $closure );
foreach( $my_posts as $pst ){
echo "$pst->post_titlen";
}
В этом примере значение 'wp_postmeta.meta_value'
— это имя столбца, которое будет использоваться в SQL-запросе. Но это имя используется как строка, поэтому с помощью хука posts_clauses_request
мы модифицируем запрос и заменяем строку на фактическую часть запроса.
Универсальный вариант
Я рекомендую использовать универсальный подход. Он:
- Более понятен в использовании.
- Удобен — не нужно выяснять, какой префикс у метаполей в запросе.
- Устойчив — при изменении/расширении параметров meta_query, работающее будет продолжать работать и не сломается.
Этот подход можно сделать более универсальным — присвоить имя массиву, указанному в meta_query, и использовать это имя в другом массиве meta_query. Затем заменить это имя на актуальное имя столбца в результате сборки запроса. Преимущество этого подхода в том, что вам не нужно знать конкретный префикс для столбца meta_value
.
Как использовать?
Подключите код класса в одном из файлов (желательно в отдельном, который затем подключите в functions.php). Далее в functions.php инициируйте класс:
WP_Query_Allow_Postmeta_Compare::init();
Теперь в запросах get_posts()
или WP_Query
в параметре meta_query
вы можете указать ключ для отдельного элемента и использовать этот ключ в значении другого элемента для сравнения значений метаполей.
Пример запроса
$args = [
'post_type' => 'post',
'posts_per_page' => 50,
'meta_query' => [
'relation' => 'AND',
'room_floor.value' => [
'key' => 'room_floor',
'value' => 1,
'compare' => '!=',
'type' => 'NUMERIC',
],
[
'key' => 'room_floor_max',
'compare' => '!=',
'value' => 'room_floor.value',
'type' => 'NUMERIC',
],
],
];
$my_posts = get_posts( [ 'suppress_filters' => false ] + $args );
foreach( $my_posts as $pst ){
echo "$pst->post_titlen";
}
Важные моменты:
- Имя ключа (в примере
room_floor.value
) не должно совпадать с именем самого метаполя (в примереroom_floor
). - Параметр запроса
suppress_filters
должен быть установлен вfalse
, чтобы данный трюк работал, поскольку он зависит от хуков. По умолчанию в функцииget_posts()
этот параметр установлен вtrue
. ДляWP_Query
не нужно указывать этот параметр.
$query = new WP_Query( $args );
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
?>