Сортировка по мета-значению с включением записей без метаданных
Я модифицирую встроенный поиск WordPress используя фильтр pre_get_posts
, позволяя пользователю сортировать записи (включая множество произвольных типов записей) по различным полям.
Проблема в том, что когда я указываю WordPress сортировать по мета-значению, он исключает все записи, у которых это мета-значение не установлено. Это приводит к изменению количества результатов при смене сортировки, например, с "Цены" на "Дату", потому что у "Записей" нет установленной "Цены", а у "Товаров" есть.
Это не то, что мне нужно, поэтому я хотел бы узнать, есть ли способ включить ВСЕ записи - даже те, у которых отсутствует мета-значение, по которому идет сортировка - и поместить записи без значения в конец.
Я знаю, как сортировать по нескольким полям, но это не помогает.
Спасибо
Похоже, я не единственный с таким вопросом: Как включить записи как с определенным meta_key, так и без него в аргументах wp_query?, но там нет решения.
Обновление
Я попробовал ответ, но не уверен, правильно ли я понял, вот что у меня сейчас:
<?php
function my_stuff ($qry) {
$qry->set('meta_query', array(array(
'key' => 'item_price',
'value' => '',
'compare' => 'NOT EXISTS'
)));
$qry->set('orderby', 'meta_value date'); # Сортировка работает как с meta_value, так и с meta_value_num - я пробовал оба
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');
}
Мета-значение является числом (оно используется для хранения цены, как предполагает название)
Обновление 2
Я закомментировал параметры сортировки и теперь у меня только это:
<?php
$qry->set('meta_query', array(array(
'key' => 'item_price',
'value' => '',
'compare' => 'NOT EXISTS'
)));
С этим кодом запрос, похоже, возвращает все записи, у которых нет ключа item_price
, и ни одной записи, у которой он есть. То есть проблема теперь обратная.
Если я добавлю код сортировки, я получаю 0 результатов.
Редактирование: ...три года спустя... :P У меня снова возникла эта проблема. Я попробовал все предложенные ответы, и ни один не работает. Не понимаю, почему некоторые люди думают, что они работают, но у меня они точно не работают.
Решение, к которому я пришел - использование фильтра save_post
- гарантирует, что все записи имеют произвольное поле, по которому я хочу сортировать. Немного раздражает, что приходится это делать, но если делать это на ранней стадии, проблем обычно не возникает.
В данном случае я создавал "счетчик просмотров" для записей и хотел, чтобы пользователи могли сортировать по самым читаемым записям. Опять же, записи, которые никогда не просматривались (думаю, это маловероятно, но всё же), исчезали при сортировке по количеству просмотров. Я добавил этот фрагмент кода, чтобы убедиться, что все записи имеют счетчик просмотров:
add_action('save_post', function ($postId) {
add_post_meta($postId, '_sleek_view_count', 0, true);
});

Легко и просто, протестировано в 2018 году, используется в продакшене на данный момент.
Обновление 2022: Изменено так, что запрос NOT_EXISTS
идет перед запросом EXISTS
, и уточнено влияние нескольких ключей на параметр orderby
.
$query->set( 'meta_query', array(
'relation' => 'OR',
array(
'key' => 'custom_meta_key',
'compare' => 'NOT EXISTS'
),
array(
'key' => 'custom_meta_key',
'compare' => 'EXISTS'
),
) );
$query->set( 'orderby', 'meta_value title' );
Этот код проверяет все записи с указанным мета-ключом и без него, без указания конкретного значения. Мета-запрос надежно предоставляет ключ для сортировки. Код был протестирован. Параметр meta_value
/meta_value_num
в orderby
будет использовать последний ключ в цепочке.
Практический пример
/**
* Изменяет запрос перед получением записей. Устанавливает
* параметры `meta_query` и `orderby`, если параметр `orderby`
* не задан (сортировка по умолчанию).
*
* @param WP_Query $query Полный объект `WP_Query`.
* @return void
*/
function example_post_ordering( $query ) {
// Если не в админке,
// и запрос является основным,
// и запрос не является запросом для одной записи,
// и параметр orderby не задан...
// Примечание: можно добавить проверку типов записей и т.д.
if ( ! is_admin()
&& $query->is_main_query()
&& ! $query->is_singular()
&& empty( $query->get( 'orderby' ) ) ) {
// Просто установка `meta_key` недостаточна, так как это
// приведет к игнорированию записей без значения для
// указанного ключа. Этот мета-запрос зарегистрирует
// `meta_key` для сортировки, но не проигнорирует записи без значения.
$query->set( 'meta_query', array(
'relation' => 'OR',
array(
'key' => 'custom_meta_key',
'compare' => 'NOT EXISTS'
),
array(
'key' => 'custom_meta_key',
'compare' => 'EXISTS'
),
) );
// Сортировка по мета-значению, затем по заголовку, если
// несколько записей имеют одинаковое значение мета-ключа.
// Используйте `meta_value_num`, если значения числовые.
$query->set( 'orderby', 'meta_value title' );
}
}
add_action( 'pre_get_posts', 'example_post_ordering', 10 );
Этот код будет сортировать записи по custom_meta_key
по умолчанию и не будет игнорировать записи без значения для этого ключа.

Просто прочитав код, видно, что он выбирает записи, у которых есть custom_meta_key
, и записи, у которых нет custom_meta_key
. Можете добавить реальный рабочий пример с сортировкой.

Вы правы, это все, что он делает, но следующая строка отвечает за сортировку по meta_value (мета-ключа, по которому идет запрос). $query->set( 'orderby', 'meta_value title' );
(Сортировка по meta_value, затем по заголовку, когда несколько записей имеют одинаковое значение мета-ключа). Это должно быть сделано в хуке pre_get_posts
, используя переданную переменную $query
. Учтите, что вопрос был о том, как сортировать по meta_value, не игнорируя записи без значения для этого мета-ключа.

Хорошо, попробую в следующий раз, когда столкнусь с этой проблемой.

Сработало для меня в пользовательском вызове get_posts()
, чтобы поднять посты с метаполем _featured
вверх, а затем отсортировать по дате. Спасибо!

У меня сработало только при обратном порядке NOT EXISTS и EXISTS. Без этого сортировка игнорировалась.

@mikemike спасибо за информацию, переключение помогло мне решить проблему

Этот метод вернет все записи, включая те, у которых есть и нет запрашиваемого meta_key
, но при сортировке могут происходить странные вещи.
add_action('pre_get_posts', 'my_stuff');
function my_stuff ($qry) {
$qry->set(
'meta_query',
array(
'relation' => 'OR', # Соответствия этому meta_query должны добавляться к тем, которые соответствуют запросу 'meta_key'
array(
'key' => 'item_price',
'value' => 'bug #23268',
'compare' => 'NOT EXISTS'
)
)
);
$qry->set('orderby', 'meta_value date'); # Сортировка работает с meta_value, а также с meta_value_num — я пробовал оба варианта
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');
}
Я нашел это, экспериментируя с различными ответами на этот вопрос и анализируя сгенерированный SQL методом проб и ошибок. Похоже, что установка array('meta_query' => array('relation' => 'OR'))
выводит соответствующий LEFT JOIN
вместо INNER JOIN
, который необходим для включения записей без метаданных. Указание NOT EXISTS
предотвращает фильтрацию записей, у которых отсутствует метаполе, в условии WHERE
. Для этого конкретного WP_Query
сгенерированный SQL выглядит так (добавлены отступы и переносы строк):
SELECT SQL_CALC_FOUND_ROWS
wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
INNER JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id
LEFT JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id AND mt1.meta_key = 'item_price')
WHERE 1=1
AND ( wp_term_relationships.term_taxonomy_id IN (2) )
AND wp_posts.post_type = 'post'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private')
AND (wp_postmeta.meta_key = 'item_price'
-- Обратите внимание, здесь мы даем SQL разрешение выбрать случайную
-- строку из wp_postmeta, если у этой записи отсутствует 'item_price':
OR mt1.post_id IS NULL )
GROUP BY wp_posts.ID
ORDER BY wp_postmeta.meta_value,wp_posts.post_date DESC
LIMIT 0, 10
В результате мы получаем список всех записей с метазначением item_price
и тех, у которых его нет. Все записи с item_price
будут отсортированы правильно относительно друг друга, но записи без item_price
будут использовать какое-то случайное другое метазначение (например, _edit_last
, которое в моей базе данных часто равно 1
, или какое-то другое внутреннее метаполе WordPress, которое совершенно произвольно) для своего wp_postmeta.meta_value
в условии ORDER BY
. Таким образом, хотя этот метод близок к идеалу и может работать для определенных данных, он сломан. Поэтому могу сказать только одно: если значения вашего item_price
случайно не конфликтуют со случайными метаполями, которые MySQL выбирает для записей без item_price
, это может сработать. Если вам нужно лишь гарантировать, что записи с item_price
правильно отсортированы относительно друг друга, без учета порядка остальных записей, это может быть приемлемо. Но, похоже, это ограничение WordPress. Поправьте меня, если я ошибаюсь — надеюсь, есть способ решить эту проблему ;-).
Похоже, что для INNER JOIN wp_postmeta
MySQL случайным образом выбирает строку из нескольких строк postmeta
, связанных с записью, когда meta_key
отсутствует у данной записи. С точки зрения SQL, нам нужно выяснить, как заставить WordPress выводить ORDER BY mt1.meta_value
. Этот столбец корректно содержит NULL
, когда запрашиваемый meta_key
отсутствует, в отличие от wp_postmeta.meta_value
. Если бы мы могли это сделать, SQL отсортировал бы эти NULL
(отсутствующие записи) перед любыми другими значениями, что дало бы нам четко определенный порядок: сначала все записи без указанного метаполя, затем записи с этим полем. Но вся проблема в том, что 'orderby' => 'meta_value'
может ссылаться только на 'meta_key' => 'item_price'
, а неалиасированный wp_postmeta
всегда является INNER JOIN
, а не LEFT JOIN
, что означает, что wp_postmeta.meta_value
и wp_postmeta.meta_key
никогда не могут быть NULL
.
Поэтому, видимо, приходится признать, что это невозможно с встроенным WP_Query
в текущей версии WordPress (wordpress-3.9.1). Досадно. Так что если вам действительно нужно, чтобы это работало корректно, вероятно, придется подключиться к WordPress в другом месте и напрямую изменить сгенерированный SQL.

Выглядит очень перспективно! Я попробую это в следующий раз, когда столкнусь с такой проблемой. Хотел бы сразу дать вам ответ, но предпочитаю сначала убедиться, что это работает у меня.

У меня после этого вообще ничего не отображалось. Ничего не появилось после реализации этого решения.

@Jake Да, та же история. Сегодня снова столкнулся с этой проблемой и попробовал это решение. Возвращает 0 результатов.

Проблема, с которой сталкиваются здесь все, связана с порядком мета-запросов. Для правильной сортировки необходимо разместить запрос "NOT EXISTS" перед запросом "EXISTS".
Причина в том, что WordPress использует meta_value из последнего оператора "LEFT JOIN" в условии "ORDER BY".
Например:
$pageQuery = new WP_Query([
'meta_query' => [
'relation' => 'OR',
['key' => 'item_price', 'compare' => 'NOT EXISTS'], // это должно быть первым!
['key' => 'item_price', 'compare' => 'EXISTS'],
],
'order' => 'DESC',
'orderby' => 'meta_value_num',
'post_status' => 'publish',
'post_type' => 'page',
'posts_per_page' => 10,
]);

Есть два возможных решения этой проблемы:
1. У всех записей есть мета-данные
Лучшее решение, которое я нашел - присвоить остальным записям/товарам цену 0. Это можно сделать вручную или пройтись циклом по всем записям и, если цена пустая, обновить ее.
Чтобы упростить управление в будущем, можно использовать хук save_post
и задавать значение при первом добавлении записи (только если оно пустое).
2. Множественные запросы
Можно выполнить первый запрос, как вы делаете, и сохранить ID возвращенных записей. Затем выполнить другой запрос для всех записей с сортировкой по дате, исключая ID из первого запроса.
После этого можно отдельно вывести результаты обоих запросов в нужном порядке и получить желаемый результат.

Три года спустя у меня снова возникла та же проблема :P Пришлось использовать метод save_post
(я обновил свой вопрос с кодом, который использовал).

Это возможно без использования хуков или выполнения нескольких запросов. Смотрите мой ответ.

Я также столкнулся с похожей проблемой, и следующее решение помогло мне:
$args = array(
'post_type' => 'kosh_products',
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
'category_sort_order' => array(
'key' => '_sort_order',
'compare' => 'EXISTS'
),
'category_sort_order_not_exists' => array(
'key' => '_sort_order',
'compare' => 'NOT EXISTS'
),
),
'orderby' => array(
'category_sort_order' => 'ASC',
'date' => 'ASC'
));
$query = new WP_Query( $args );
Я нашел описание в WordPress Codex с заголовком "Сортировка ('orderby') с несколькими мета-ключами": https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters

Думаю, у меня есть решение.
Вы можете использовать два meta_key
: один, который есть у всех записей (например "_thumbnail_id")
, и meta_key
, который вы хотите использовать для фильтрации.
Ваши аргументы будут выглядеть так:
$qry->set(
'meta_query',
array(
'relation' => 'OR',
array(
'key' => 'item_price',
'value' => '',
'compare' => 'EXISTS'
),
array(
'key' => 'item_price',
'value' => '',
'compare' => 'EXISTS'
)
)
);
$qry->set('orderby', 'meta_value date'); # Сортировка работает как с meta_value, так и с meta_value_num - я пробовал оба варианта
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');

Я сам столкнулся с этой проблемой для числовых мета-значений и обнаружил, что порядок запроса также важен. В моем случае запрос NOT EXISTS
должен быть первым.
Пример:
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_query', [
'relation' => 'OR',
[ 'key' => 'your_meta_name', 'compare' => 'NOT EXISTS' ],
[
'key' => 'your_meta_name',
'compare' => 'EXISTS',
],
] );
Также важно для правильной сортировки числовых значений установить параметр ’orderby’
в значение ’meta_value_num’
. В противном случае вы получите странные результаты для числовых значений, например:
1, 2, 20, 21, 3, 4, 5 …
Вместо:
1, 2, 3, 4, 5 … 20, 21

Мета-запрос OR, сочетающий NOT EXISTS
и EXISTS
, работает, но вызывает медленный запрос.
Это решение работает значительно быстрее.
Ключевым моментом был SQL-запрос в ORDER BY:
order by YOUR_FIELD is NULL, YOUR_FIELD
add_filter('posts_orderby', function (string $orderby, WP_Query $query) {
if ('MY_CUSTOM_SORT' === $query->get('orderby')) {
$orderby = "mta.meta_value is NULL, mta.meta_value ='', mta.meta_value ASC";
}
return $orderby;
}, 10, 2);
add_filter('posts_join', function (string $join, WP_Query $query) {
if ('MY_CUSTOM_SORT' === $query->get('orderby')) {
$join .= " LEFT JOIN wp_postmeta AS mta ON ( wp_posts.ID = mta.post_id AND mta.meta_key = 'SOME_META_KEY')";
}
return $join;
}, 10, 2);
И в аргументах вашего WP_Query:
$args['orderby'] = 'MY_CUSTOM_SORT';

Это очень старый вопрос, но ни один из ответов не был достаточно надежным для меня, чтобы использовать его и быть довольным результатами. Приведенное ниже решение по-прежнему использует WP_Query
, но манипулирует операторами join и order by.
Оно использует функцию MySQL под названием ORDER BY IF
, которая позволяет эффективно применять логику во время выполнения оператора ORDER BY. Это важно, потому что другие решения просто группируют результаты без какого-либо значения либо в начале, либо в конце списка, что бесполезно, когда вы пытаетесь упорядочить по двум ключам.
В приведенном ниже примере у меня есть пользовательский тип записи (CPT) 'businesses'. Я хочу, чтобы все они были упорядочены по названию, но любые записи с is_premium
должны идти первыми. Я добавил комментарии к коду, чтобы было понятно, что происходит в каждом месте.
<?php
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$args = [
'post_type' => 'business',
'posts_per_page' => 12,
'meta_query' => [
'relation' => 'OR',
[
'key' => 'is_premium',
'compare' => 'NOT EXISTS' // NOT EXISTS сначала важно
],
[
'key' => 'is_premium',
'compare' => 'EXISTS'
],
]
,
'orderby' => [
'meta_value' => 'DESC', // Это поле is_premium
'title' => 'ASC',
],
'paged' => $paged
];
// Добавляем собственный join к запросу
function custom_join($join) {
global $wpdb;
if( ! is_admin() ) {
$join .= $wpdb->prepare(
' LEFT JOIN ' . $wpdb->postmeta . ' cpm ON cpm.post_id = ' . $wpdb->posts . '.ID AND cpm.meta_key = %s'
, 'is_premium' );
}
return $join;
}
add_filter('posts_join','custom_join');
// Добавляем собственный order by к запросу
function custom_orderby($orderby_statement){
global $wpdb;
if ( ! is_admin() ) {
// Здесь мы сортируем по meta_value ТОЛЬКО если оно равно 1, в противном случае игнорируем его для этого оператора сортировки,
// что означает, что запись будет отсортирована по следующему оператору (title), так же как и другие результаты
$orderby_statement = "IF(cpm.meta_value = 1, 1, 0) DESC, wp_posts.post_title ASC ";
}
return $orderby_statement;
}
add_filter('posts_orderby','custom_orderby', 10, 2 );
// Запрос
$business = new WP_Query( $args );
// Удаляем фильтры
remove_filter('posts_orderby','custom_orderby');
remove_filter('posts_join','custom_join');

При необходимости вы можете добавлять значение мета-поля по умолчанию каждый раз, когда запись сохраняется или обновляется, если это мета-значение не существует.
function addDefaultMetaValue($post_id) {
add_post_meta($post_id, 'item_price', 0, true);
}
add_action('save_post', 'addDefaultMetaValue');
Если вы используете пользовательский тип записи (custom post type), замените add_action('save_post', 'addDefaultMetaValue');
на add_action('save_post_{post_type}', 'addDefaultMetaValue');
, например add_action('save_post_product', 'addDefaultMetaValue');

Для этого можно использовать значение orderby
как meta_value
.
$query = new WP_Query( array (
'meta_key' => 'your_keys_name', // Укажите имя вашего мета-ключа
'orderby' => 'meta_value', // Сортировка по значению мета-поля
'order' => 'DESC', // Сортировка по убыванию
'meta_query' => array( array(
'key' => 'your_meta_key', // Ключ мета-поля для сравнения
'value' => '', // Пустое значение
'compare' => 'NOT EXISTS', // Условие: мета-поле не существует
// 'type' => 'CHAR', // Тип данных (закомментировано)
) )
) );
Если у вас числовые значения, используйте meta_value_num
вместо meta_value
.
Примечание: Этот код не тестировался, но он должен работать. Главное — указать значения meta_key
и key
. Иначе нельзя сравнивать отсутствующие значения, что позволит запросить оба типа записей. Это немного хакерский подход, но если работает...

Спасибо за ответ, пожалуйста, проверьте мой обновленный вопрос, я не уверен, что правильно вас понял.

У меня до сих пор не получилось заставить это работать, поэтому если у вас есть решение, я бы хотел узнать, что я делаю не так. Также я установил награду на SO, если вы хотите её получить: http://stackoverflow.com/questions/17016770/wordpress-order-by-meta-value-if-it-exists-else-date/17183618

Две вещи. 'your_keys_name'
и 'your_meta_key'
должны быть одной и той же строкой, а не разными, иначе похоже, что вы неверно поняли вопрос. Во-вторых, я протестировал это на своей локальной настройке, и оно исключает все записи, где ключ существует (через meta_query
), а также исключает записи, где ключ отсутствует (через meta_key
), в результате чего не отображаются никакие записи. Однако этот ответ — шаг к чему-то, что работает, по крайней мере ;-).

О, интересно, что этот ответ действительно работает, если просто добавить 'relation' => 'OR'
в meta_query
. Странные дела о_о.

@binki Просто предложите [правку] к моему вопросу и измените то, что, по вашему мнению, нужно изменить. Это сайт, управляемый сообществом :)

@kaiser, Я ещё поэкспериментировал, даже протестировал, и в результате получился небольшой монолог. Полагаю, мой новый ответ — подходящее место для моих размышлений ;-).

Это решение сработало для меня:
add_action( 'pre_get_posts', 'orden_portfolio' );
function orden_portfolio( $query ) {
if( ! is_admin() ) {
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'order', 'ASC' );
$query->set( 'meta_query', [
'relation' => 'OR',
[
'key' => 'ce_orden',
'compare' => 'NOT EXISTS' ],
[
'key' => 'ce_orden',
'compare' => 'EXISTS',
],
] );
return $query;
}
}
Однако это решение сначала показывает записи с пустым meta_value. Вот другое решение, которое показывает сортировку по возрастанию (ASC) и записи с NULL в конце:
function custom_join($join) {
global $wpdb;
if( ! is_admin() ) {
$join .= $wpdb->prepare(
' LEFT JOIN ' . $wpdb->postmeta . ' cpm ON cpm.post_id = ' . $wpdb->posts . '.ID AND cpm.meta_key = %s'
, 'ce_orden' );
}
return $join;
}
add_filter('posts_join','custom_join');
function custom_orderby($orderby_statement){
global $wpdb;
if ( ! is_admin() ) {
$orderby_statement = "CAST( COALESCE(cpm.meta_value,99999) as SIGNED INTEGER) ASC";
}
return $orderby_statement;
}
add_filter('posts_orderby','custom_orderby', 10, 2 );

Мне удалось решить это с помощью одного запроса и правильных аргументов. Для WordPress 4.1+
$args = array(
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'custom_sort',
'compare' => 'EXISTS'
),
array(
'key' => 'custom_sort',
'compare' => 'NOT EXISTS'
)
),
'orderby' => 'meta_value_num title',
'order' => 'ASC',
));
$query = new WP_Query($args);
Смотрите мой полный ответ здесь: https://wordpress.stackexchange.com/a/370841/15209

В итоге я обошёл эту проблему с помощью небольшого хака (ИМХО), но в моём случае он сработал.
Вы можете подключиться к фильтрам posts_join_paged и posts_orderby, чтобы изменить строки JOIN и ORDER BY. Это позволит вам сортировать по любому полю, при условии что вы сначала присоедините его, вместо того чтобы WP_Query предполагал, что поле должно существовать для конкретной записи. Затем вы можете удалить параметры meta_key
, orderby
и order
из аргументов вашего WP_Query.
Ниже приведён пример. В начале каждой функции мне пришлось добавить проверки для исключения определённых случаев, так как эти фильтры применяются ко всем запросам через WP_Query. Возможно, вам нужно будет адаптировать это под свои нужды.
Документация по этим двум фильтрам, к сожалению, практически отсутствует... так что удачи! :)
add_filter('posts_join_paged', 'edit_join', 999, 2);
add_filter('posts_orderby', 'edit_orderby', 999, 2);
/**
* Изменяет JOIN запроса
*
* @param string $join_paged_statement
* @param WP_Query $wp_query
* @return string
*/
function edit_join($join_paged_statement, $wp_query)
{
global $wpdb;
if (
!isset($wp_query->query)
|| $wp_query->is_page
|| $wp_query->is_admin
|| (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
) {
return $join_paged_statement;
}
$join_to_add = "
LEFT JOIN {$wpdb->prefix}postmeta AS my_custom_meta_key
ON ({$wpdb->prefix}posts.ID = my_custom_meta_key.post_id
AND my_custom_meta_key.meta_key = 'my_custom_meta_key')
";
// Добавляем только если ещё не добавлено
if (strpos($join_paged_statement, $join_to_add) === false) {
$join_paged_statement = $join_paged_statement . $join_to_add;
}
return $join_paged_statement;
}
/**
* Изменяет ORDER BY запроса
*
* @param string $orderby_statement
* @param WP_Query $wp_query
* @return string
*/
function edit_orderby($orderby_statement, $wp_query)
{
if (
!isset($wp_query->query)
|| $wp_query->is_page
|| $wp_query->is_admin
|| (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
) {
return $orderby_statement;
}
$orderby_statement = "my_custom_meta_key.meta_value DESC";
return $orderby_statement;
}

Код работает. Но meta_value обрабатывается как строка. Поэтому 6 оценивается выше, чем 50. Можно ли внести изменения, чтобы они обрабатывались как числа?

@Drivingralle cast(my_custom_meta_key.meta_value as unsigned) DESC
должно сработать...

Я в итоге воспользовался фильтром 'get_meta_sql', который предоставляет WordPress
add_filter( 'get_meta_sql', 'adjust_the_meta_sql' ), 10, 2 );
Функция выглядит следующим образом
function adjust_the_meta_sql( $sql, $queries ) {
if ( 'my_post_type' === filter_input( INPUT_GET, 'post_type' )
&& '_my_key' === $queries[0]['key'] ) {
$sql['join'] = preg_replace( '/INNER JOIN wp_postmeta ON \(([^)]+)\)/', 'LEFT JOIN wp_postmeta ON ($1 ' . $sql['where'] . ')', $sql['join'] );
$sql['where'] = '';
}
return $sql;
}
Это решает две проблемы с SQL-запросом, генерируемым WordPress.
- Преобразует INNER JOIN в LEFT JOIN
- Переносит условие WHERE в условие ON.
Ваш конкретный запрос может отличаться, поэтому обязательно протестируйте и при необходимости адаптируйте решение.

Я думаю, что @kaiser пытался сделать следующее: указать запросу вернуть все записи, которые имеют этот мета-ключ, применив своего рода "фиктивное условие WHERE", чтобы не фильтровать ни одну из этих записей. Таким образом, если вы знаете, что все возможные значения ваших произвольных полей — это x, y, z, вы могли бы сказать "WHERE meta_key IN(x,y,z)", но идея в том, что можно полностью избежать этой проблемы, используя условие != (''):
$query = new WP_Query( array (
'orderby' => 'meta_value_num',
'order' => 'DESC',
'meta_query' => array( array(
'key' => 'item_price',
'value' => '',
'compare' => '!=',
) )
) );
Также не тестировал, но кажется, что стоит попробовать :-).

Ребята, спасибо за все правильные ответы, но сейчас (2022) ключевым моментом является вложенность:
$query->set( 'orderby', array(
'meta_value_num' => 'DESC',
'title' => 'ASC' )
); // приоритеты сортировки
$query->set( 'meta_query', array(
'featured' => array(
'relation' => 'OR',
array(
'key' => '_featured',
'value' => '1', // сначала показываем записи со значением = 1
),
array( // эти вложенные правила имеют меньший приоритет для сортировки, но гарантируют показ всех остальных записей
'relation' => 'OR',
array(
'key' => '_featured',
'compare' => 'NOT EXISTS', // объяснение излишне
),
array(
'key' => '_featured',
'value' => '0', // случай, когда значение существует, но должно рассматриваться как отсутствующее
)
)
)
));
Бонусный случай: я не тестировал это, но если мета-поле содержит не 0/1 значение, третье правило должно выглядеть так:
array(
'key' => '_featured',
'value' => '[значение приоритета]',
'compare' => '!='
)
