Запрос для сортировки списка сначала по мета-полю (если существует), и показа оставшихся записей без мета-поля, отсортированных по заголовку
Я работаю над шаблоном страницы терминов пользовательской таксономии, где мы хотим отсортировать элементы, связанные с термином, по дате публикации (пользовательское поле даты). Если есть несколько элементов с одной и той же датой (в формате YYYY-MM-DD), то отсортировать их по заголовку, и наконец, сортировать по заголовку, если пользовательское поле не заполнено (старые элементы).
Я пробовал сотню разных способов с WP_Query, и это возвращает большинство результатов так, как я хочу - но в данном случае возвращаются только элементы, у которых есть meta_key publication_date. Все остальные элементы игнорируются и не отображаются. Я пробовал meta_query, используя relation "or" и сравнивал publication_date как EXISTS и NOT EXISTS, но это вернуло 0 результатов.
Кроме того, сайт все еще работает на версии 3.5.2, и они не хотят обновляться.
Вот мой последний запрос, который возвращает записи с пользовательским полем publication_date в правильном порядке:
$term = get_queried_object(); // находим термин таксономии страницы, на которой находимся
$wp_query = new WP_Query( array(
'post_type' => 'resource',
'tax_query' => array(
array(
'taxonomy' => 'resource_types',
'field' => 'slug',
'terms' => $term->name,
)),
'meta_key' => 'publication_date',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'paged' => $paged,
'posts_per_page' => '10',
));
Я также пробовал использовать wpdb и выполнять SQL-запрос, но я действительно не уверен, как достичь того, чего я хочу, делая это. Если кто-то может помочь мне, это было бы здорово!
Заранее спасибо.

Спасибо всем за помощь!
В итоге приведенный ниже запрос дал мне желаемый результат - отображать и сортировать записи сначала по пользовательскому полю "publication_date", сортируя по дате, а если есть несколько записей с одинаковой датой (например, 4 записи с датой июня 2013), то сортировать их по заголовку. Затем, после обработки всех записей с заполненной датой публикации, он проходит оставшиеся записи, сортируя их по алфавиту по заголовку.
Это позволяет получить нужный набор результатов в одном запросе и сохраняет пагинацию:
$term = get_queried_object();
the_post();
$wp_query = new WP_Query( array(
'post_type' => 'resource',
'tax_query' => array(
array(
'taxonomy' => 'resource_types',
'field' => 'slug',
'terms' => $term->name,
)),
'meta_query' => array(
'relation' => 'OR',
array( //проверяем, заполнена ли дата
'key' => 'publication_date',
'compare' => '=',
'value' => date('Y-m-d')
),
array( //если дата не добавлена, показываем и эти записи
'key' => 'publication_date',
'value' => date('Y-m-d'),
'compare' => 'NOT EXISTS'
)
),
'meta_key' => 'publication_date',
'orderby' => 'meta_value title',
'order' => 'ASC',
'paged' => $paged,
'posts_per_page' => '10',
));

Круто. Никогда не думал запускать два meta_query
по одному ключу!

У меня (использую WordPress 4.1.1), если я устанавливаю meta_key
, он автоматически не включает его, даже с NOT EXISTS
. Очень надеюсь, что я делаю что-то не так.

@RyanTaylor то же самое - meta_key не должен быть установлен в запросе для работы этого, но похоже, что сортировка по значению meta работает корректно, даже когда meta key не установлен.

Как сказано в комментариях выше, для WP 4.1+ удалите или закомментируйте строку 'meta_key' => 'publication_date',
.

Несколько лет спустя код, опубликованный CSSGirl, перестал работать для меня, потому что некоторые записи не имели мета-ключа или он был пустым. Вот что мне пришлось сделать, чтобы все записи сортировались по дате, а записи со значением мета-ключа отображались первыми:
$args = array(
'post_type' => $type,
'post_status' => 'publish',
'nopaging' => TRUE,
'meta_query' => array(
'relation' => 'OR',
array(
'key' => $meta_key,
'compare' => 'NOT EXISTS',
),
array(
'relation' => 'OR',
array(
'key' => $meta_key,
'value' => 'on',
),
array(
'key' => $meta_key,
'value' => 'on',
'compare' => '!=',
),
),
),
'orderby' => array( 'meta_value' => 'DESC', 'date' => 'DESC' ),
);

Я думаю, вам понадобится два отдельных цикла. Вы можете собрать все записи, найденные в первом цикле, и легко исключить их из второго цикла:
$found_posts = array();
while($loop->have_posts()): $loop->the_post();
// действия в цикле
$found_posts[] = get_the_id();
endwhile;
wp_reset_query();
$args = array(
// другие аргументы
'post__not_in' => $found_posts,
);
Затем запустите ваш второй цикл.

Это сработало, но сломалось пагинация - есть идеи, как это исправить? Вот как это выглядит сейчас: echo paginate_links( array(
'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
'format' => '?page=%#%',
'current' => max( 1, get_query_var('paged') ),
'total' => $publication_query->max_num_pages,
'prev_text' => __('Previous |'),
'next_text' => __('| Next'),
) );

Есть ли причина, по которой вы не можете обязать наличие мета-ключа publication_date для каждой записи, даже с пустым значением?
Таким образом, в вашем действии save_post
вы будете добавлять/обновлять мета-ключ независимо от того, пустое ли значение в $_POST
или нет.
Вам потребуется запустить скрипт обновления, чтобы пройтись по старым записям и добавить ключ с пустым значением, например:
add_action( 'admin_init', 'update_old_posts' );
function update_old_posts() {
if ( ! isset( $_GET[ 'update_old_posts' ] ) )
return;
foreach( get_posts() as $post ) {
if ( false === get_post_meta( $post->ID, 'publication_date', true ) ) {
update_post_meta( $post->ID, 'publication_date', '' );
echo "Обновлено {$post->post_title} <br />";
}
}
die;
}
Запустите его, перейдя по ссылке http://example.com/wp-admin/?update_old_posts
После этого вы можете использовать тот же запрос, что и у вас. Возможно, стоит добавить дополнительный фильтр для сортировки по разным колонкам в разных направлениях — логично сортировать по дате в порядке убывания и по заголовку в порядке возрастания.
add_filter( 'posts_orderby', 'multicolumn_orderby', 10, 2 );
function multicolumn_orderby( $orderby, $query ) {
global $wpdb;
// проверяем, что это нужный запрос
if ( $query->get( 'meta_key' ) == 'publication_date' ) {
$orderby = "$wpdb->postmeta.meta_value+0 DESC, $wpdb->posts.post_title ASC";
}
return $orderby;
}

Я создал пользовательское условие WHERE. Протестировал его с помощью $wp_query->request
прямо перед основным циклом, я не особо разбираюсь в SQL, но это сработало.
add_action('pre_get_posts', 'add_trending_sort', 11, 1);
function add_trending_sort($query){
if(!$query->is_main_query())
return;
// Переопределяем аргументы запроса
$query->set('meta_query', array(
array(
'key' => 'TRENDING',
//'value' => 'asdfasdf',// может потребоваться значение для старых версий WordPress
'compare' => 'NOT EXISTS',
)
));
$query->set('orderby', 'meta_value_num date');
$query->set('order', 'DESC');
}
add_filter('posts_where', 'add_trending_where');
function add_trending_where($where = ''){
global $wpdb, $wp_query;
if(!$wp_query->is_main_query())// Не уверен, работает ли это. Должно быть нормально
return $where;
$where .= " OR ( $wpdb->postmeta.meta_key = 'TRENDING' )";
// Не выполнять это дважды
remove_filter('posts_where', 'add_trending_where');
return $where;
}
Альтернативно, вы можете установить compare
в 'EXISTS'
и изменить строку в add_trending_where на $where .= " OR ($wpdb->postmeta.post_id IS NULL)";
. Тогда вам нужно будет изменить значение ключа только в одном месте. Опять же, выведите $wp_query->request
и поэкспериментируйте, если хотите лучше понять или доработать это.
РЕДАКТИРОВАНО: Я только что заметил, что это не работает, если в запросе установлен meta_key
. Вы можете использовать $query->set('meta_key', NULL);
, если это необходимо.
РЕДАКТИРОВАНО 2: Я заставил это работать с помощью метода выше. По какой-то причине сначала это не сработало (возможно, meta_key был установлен... я не знаю).
add_action('pre_get_posts', 'add_trending_sort', 11, 1);
function add_trending_sort($query){
// Пропускаем, если это не основной "скрытый" запрос, в отличие от вызова 'new WP_Query()'
if(!$query->is_main_query())
return;
// Устанавливаем meta_query для получения долей для orderby, а также контента без долей.
$query->set('meta_query', array(
'relation' => 'OR',
array(
'key' => 'TRENDING',
'compare' => 'NOT EXISTS',
),
array(
'key' => 'TRENDING',
'compare' => 'EXISTS',
)
));
//$query->set('meta_key', NULL);
$query->set('orderby', array('meta_value_num' => 'DESC', 'date' => 'DESC'));
}
