Почему orderby meta_value возвращает только посты с существующим meta_key в WordPress

14 мая 2015 г., 02:14:21
Просмотры: 13.6K
Голосов: 11

У меня есть следующий wp_query:

$args = array(
    'post_type' => 'news', // Тип поста - новости
    'orderby' => 'meta_key', // Сортировка по мета-ключу
    'order' => 'ASC', // Порядок сортировки - по возрастанию
    'meta_key'=>'custom_author_name', // Мета-ключ для сортировки
    'post_per_page'=>-1 // Вывести все посты
);

$query = new WP_Query($args);

echo $query->found_posts; // Вывести количество найденных постов

echo выводит 10 результатов, потому что только 10 постов типа news имеют meta_key = custom_author_name. Однако есть сотни постов news, у которых нет строки post_meta с этим конкретным meta_key. Обратите внимание, что здесь не используется meta_query. Не задано meta_value, потому что я только пытаюсь отсортировать посты по meta_key, а не фильтровать по meta_value.

Разве orderby не должен выбирать все посты? И просто их сортировать?

Если да, то почему результат фильтруется? Если meta_key не найден, почему бы просто не использовать пустую строку или совпадение со всеми?

Если нет, то почему?

Если я добавлю meta_key к каждому посту новости (даже если это пустая строка), то я получу ожидаемый результат. Но это кажется огромным количеством ненужных строк в таблице.

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

Как указано в ответе @ambroseya, так и должно работать. Когда вы объявляете мета-запрос, даже если вы не ищете конкретное значение, он будет запрашивать только записи с объявленным мета-ключом. Если вы хотите включить все записи и отсортировать их по мета-ключу, используйте следующий код:

$args = array(
    'post_type' => 'news',
    'orderby' => 'meta_value',
    'order' => 'ASC',
    'meta_query' => array(
        'relation' => 'OR',
        array( 
            'key'=>'custom_author_name',
            'compare' => 'EXISTS'           
        ),
        array( 
            'key'=>'custom_author_name',
            'compare' => 'NOT EXISTS'           
        )
    ),
    'posts_per_page'=>-1
);

$query = new WP_Query($args);

echo $query->found_posts;

Этот код использует расширенный мета-запрос, который ищет записи как с объявленным мета-ключом, так и без него. Поскольку условие с EXISTS указано первым, при сортировке по meta_value будет использоваться первый запрос.

14 мая 2015 г. 05:41:31
Комментарии

Для меня это вообще не изменило порядок, когда я использовал 'orderby' => 'meta_value', порядок изменился, но это не имело никакого отношения к фактическому метаполю.

Jake Jake
26 окт. 2016 г. 14:21:38
1

Я попробовал применить ответ @Manny Fleurmond, и, как @Jake, у меня не получилось заставить его работать даже после исправления опечатки: 'orderby' => 'meta_key' должно быть 'orderby' => 'meta_value'. (Для полноты также стоит отметить, что должно быть 'posts_per_page', а не 'post_per_page', но это не влияет на рассматриваемую проблему.)

Если посмотреть на SQL-запрос, фактически сгенерированный ответом @Manny Fleurmond (после исправления опечаток), то получится следующее:

SELECT   wp_{prefix}_posts.* FROM wp_{prefix}_posts
LEFT JOIN wp_{prefix}_postmeta ON (wp_{prefix}_posts.ID = wp_{prefix}_postmeta.post_id AND wp_{prefix}_postmeta.meta_key = 'custom_author_name' )
LEFT JOIN wp_{prefix}_postmeta AS mt1 ON ( wp_{prefix}_posts.ID = mt1.post_id )
WHERE 1=1  AND ( 
    wp_{prefix}_postmeta.post_id IS NULL 
    OR 
    mt1.meta_key = 'custom_author_name'
) AND wp_{prefix}_posts.post_type = 'news' AND
(wp_{prefix}_posts.post_status = 'publish' OR wp_{prefix}_posts.post_author = 1 AND wp_{prefix}_posts.post_status = 'private')
GROUP BY wp_{prefix}_posts.ID ORDER BY wp_{prefix}_postmeta.meta_value ASC

Это демонстрирует, как WordPress разбирает параметры запроса: для каждого условия meta_query создается таблица, затем определяется, как их соединять и по чему сортировать. Сортировка работала бы нормально, если бы использовалось только одно условие с 'compare' => 'EXISTS', но добавление второго условия 'compare' => 'NOT EXISTS' с оператором OR (как и требуется) нарушает порядок сортировки. В результате LEFT JOIN используется для соединения как первого условия/таблицы, так и второго условия/таблицы — и то, как WordPress объединяет всё это, означает, что таблица, созданная с помощью 'compare' => 'EXISTS', фактически заполняется meta_values из ЛЮБОГО произвольного поля, а не только из поля 'custom_author_name', которое нас интересует. Поэтому сортировка по этому условию/таблице даст желаемый результат, только если у данного типа записи 'news' есть только одно произвольное поле.

Решение, которое сработало в моей ситуации, — сортировка по другому условию/таблице — с NOT EXISTS. Казалось бы, это противоречит интуиции, но из-за того, как WordPress разбирает параметры запроса, именно в этой таблице meta_value заполняется только тем произвольным полем, которое нам нужно.

(Единственный способ, которым я это выяснил, — выполнение эквивалентного запроса для моего случая:

SELECT   wp_{prefix}_posts.ID, wp_{prefix}_postmeta.meta_value, mt1.meta_value FROM wp_{prefix}_posts
LEFT JOIN wp_{prefix}_postmeta ON (wp_{prefix}_posts.ID = wp_{prefix}_postmeta.post_id AND wp_{prefix}_postmeta.meta_key = 'custom_author_name' )
LEFT JOIN wp_{prefix}_postmeta AS mt1 ON ( wp_{prefix}_posts.ID = mt1.post_id )
WHERE 1=1  AND ( 
    wp_{prefix}_postmeta.post_id IS NULL 
    OR 
    mt1.meta_key = 'custom_author_name'
) AND wp_{prefix}_posts.post_type = 'news' AND
(wp_{prefix}_posts.post_status = 'publish' OR wp_{prefix}_posts.post_author = 1 AND wp_{prefix}_posts.post_status = 'private')
ORDER BY wp_{prefix}_postmeta.meta_value ASC

Всё, что я сделал, — изменил отображаемые столбцы и удалил предложение GROUP BY. Это показало мне, что происходит: столбец postmeta.meta_value получал значения из всех meta_keys, а столбец mt1.meta_value — только meta_values из произвольного поля news.)

Решение

Как сказал @Manny Fleurmond, для orderby используется первое условие, так что ответ заключается в простой перестановке условий местами:

$args = array(
    'post_type' => 'news',
    'orderby' => 'meta_value',
    'order' => 'ASC',
    'meta_query' => array(
        'relation' => 'OR',
        array( 
            'key' => 'custom_author_name',
            'compare' => 'NOT EXISTS'           
        ),
        array( 
            'key' => 'custom_author_name',
            'compare' => 'EXISTS'           
        )
    ),
    'posts_per_page' => -1
);

$query = new WP_Query($args);

Альтернативно можно сделать условия ассоциативными массивами и сортировать по соответствующему ключу:

$args = array(
    'post_type' => 'news',
    'orderby' => 'not_exists_clause',
    'order' => 'ASC',
    'meta_query' => array(
        'relation' => 'OR',
        'exists_clause' => array( 
            'key' => 'custom_author_name',
            'compare' => 'EXISTS'           
        ),
        'not_exists_clause' => array( 
            'key' => 'custom_author_name',
            'compare' => 'NOT EXISTS'           
        )
    ),
    'posts_per_page' => -1
);

$query = new WP_Query($args);
5 нояб. 2017 г. 11:09:23
Комментарии

Стоит отметить, что если мета-ключ custom_author_name был установлен, а затем снят, этот meta_key всё равно будет реагировать на EXISTS, и в результате такие записи будут отображаться вместе с записями, у которых действительно есть custom_author_name. В моём случае я использую чекбокс, поэтому применяю "value" => "1" вместо EXISTS, но для строк потребуется другой подход.

djb djb
23 мая 2019 г. 18:20:17
0

Именно так это и работает.

Если вы хотите сделать это без добавления строк в таблицу, вам придется выполнить два запроса. Один с meta_key, который даст ограниченные результаты, и другой, который получит весь список; затем использовать PHP для сравнения результатов двух запросов (возможно, удаляя результаты с meta_key из второго запроса, чтобы избежать дублирования, или используя другой подходящий способ в вашей ситуации).

14 мая 2015 г. 04:45:13
0

К сожалению, WP_Query не работает таким образом. Как только вы добавляете компонент "meta", вы создаете своего рода фильтр. Выведите $query->request и вы увидите, что я имею в виду.

Во-вторых, WP_Query вообще не поддерживает сортировку по meta ключу. Вы можете сортировать по meta значению для определенного ключа, но не по самому ключу. Снова выведите запрос, чтобы увидеть это. Вы заметите, что компоненты "order" пропадают при попытке сделать это.

Самый чистый способ заставить это работать, на мой взгляд - пара коротких фильтров:

function join_meta_wpse_188287($join) {
  remove_filter('posts_join','join_meta_wpse_188287');
  global $wpdb;
  return ' INNER JOIN '.$wpdb->postmeta.' ON ('.$wpdb->posts.'.ID = '.$wpdb->postmeta.'.post_id)';
}
add_filter('posts_join','join_meta_wpse_188287');

function orderby_meta_wpse_188287($orderby) {
  remove_filter('posts_orderby','orderby_meta_wpse_188287');
  global $wpdb;
  return $wpdb->postmeta.'.meta_key ASC';
}
add_filter('posts_orderby','orderby_meta_wpse_188287');

$args = array(
    'post_type' => 'news',
    'post_per_page'=>-1
);
$q = new WP_Query($args);
var_dump($q->request); // отладка
var_dump(wp_list_pluck($q->posts,'post_title')); // отладка
14 мая 2015 г. 16:41:30