Как сравнивать значения полей метаданных в запросе meta_query

# WP_QUERY: Как сравнивать значения полей метаданных в запросе meta_query

WP_Query не позволяет напрям��ю сравнивать значения полей метаданных. Однако, в meta_query можно указать для сравнений такие операторы, как BETWEEN, >, >=, <, <=. Но в параметре значения нам нужно указать конкретные данные. Если необходимо сравнить значения двух метаданных, WP не справляется с этой задачей без изменения самого запроса. Это может быть сложно для начинающих пользователей и иногда кажется нерешаемой задачей.

## Пример использования

Представьте, у вас есть карточка товара с двумя метаполями: "Этаж" (room_floor) и "Максимальный этаж в здании" (room_floor_max).

![пример метаполей](https://via.placeholder.com/400x200)

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

![фильтрация объектов](https://via.placeholder.com/400x200)

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

![фильтрация по этажам](https://via.placeholder.com/400x200)

## Решение

Эту задачу можно решить следующим образом:

```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();
        ?>
        
  • Leave a Reply

    Ваш адрес email не будет опубликован. Обязательные поля помечены *