Сложный meta_query и orderby с несколькими пользовательскими типами записей и несколькими именами столбцов даты

#mysql #wordpress #advanced-custom-fields

#mysql #wordpress #расширенные пользовательские поля

Вопрос:

Я пытаюсь запросить два пользовательских типа записей, exhibitions и events .

На выставках есть exhibition_start_date и exhibition_end_date пользовательские поля.

У событий есть event_date пользовательское поле.

Как бы мне отсортировать несколько столбцов в одном предложении orderby?

Я просматривал это для справки https://make.wordpress.org/core/2015/03/30/query-improvements-in-wp-4-2-orderby-and-meta_query /.

В идеале мои результаты должны выглядеть следующим образом:

  • Выставка (октябрь 2016)
  • Событие (сентябрь 2016)
  • Событие (август 2016)
  • Выставка (июль 2016)
  • Событие (июнь 2016)

Мой запрос до сих пор не сортируется должным образом:

 $today = date('Ymd');
$past_date = date( 'Ymd', strtotime( $today . ' - 12 months' ) );

$args = array(
  'post_type' => array( 'exhibition', 'event' ),
  'meta_query' => array(
    'relation' => 'OR',
    'exhibition_clause' => array(
      array (
        'key'     => 'exhibition_end_date',
        'compare' => '<',
        'value'   => $today,
      ),
       array (
        'key'     => 'exhibition_end_date',
        'compare' => '>=',
        'value'   => $past_date,
      )
    ),
    'event_clause' => array(
      array (
        'key'     => 'event_date',
        'compare' => '<',
        'value'   => $today,
      ),
      array (
        'key'     => 'event_date',
        'compare' => '>=',
        'value'   => $past_date,
      )
    )
  ),
  'orderby' => array(
    'exhibition_clause' => 'DESC',
    'event_clause' => 'DESC',
  ),
);
  

РЕДАКТИРОВАТЬ: после подключения к фильтру ‘posts_orderby’:

Фильтр:

 function my_exhibition_event_date_posts_orderby( $orderby ) {
  if ( is_exhibition_event_query() ) {
    unset( $GLOBALS['is_exhibition_event_query'] );
    return "IFNULL(mt1.meta_value 0, mt2.meta_value 0) DESC";
  }
  return $orderby;
}
add_filter( 'posts_orderby', 'my_exhibition_event_date_posts_orderby', 10, 1 );
  

Вывод sql:

 SELECT SQL_CALC_FOUND_ROWS  wp_posts.ID FROM wp_posts  INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )  INNER JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id )  INNER JOIN wp_postmeta AS mt2 ON ( wp_posts.ID = mt2.post_id )  INNER JOIN wp_postmeta AS mt3 ON ( wp_posts.ID = mt3.post_id ) WHERE 1=1  AND ( 
  ( 
    ( wp_postmeta.meta_key = 'exhibition_end_date' AND wp_postmeta.meta_value < '20161023' ) 
    AND 
    ( mt1.meta_key = 'exhibition_end_date' AND mt1.meta_value >= '20080623' )
  ) 
  OR 
  ( 
    ( mt2.meta_key = 'event_date' AND mt2.meta_value < '20161023' ) 
    AND 
    ( mt3.meta_key = 'event_date' AND mt3.meta_value >= '20080623' )
  )
) AND wp_posts.post_type IN ('exhibition', 'event') AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'acf-disabled' OR wp_posts.post_author = 1 AND wp_posts.post_status = 'private') GROUP BY wp_posts.ID ORDER BY IFNULL(mt2.meta_value 0, mt1.meta_value 0) DESC LIMIT 0, 20
  

Тем не менее, это все еще неправильно. В таком порядке возвращаются записи:

  • Событие (15 октября 2014)
  • Событие (16 октября 2014)
  • Событие (31 октября 2014)
  • Событие (6 августа 2014)
  • Выставка (14 октября 2016)
  • Выставка (22 октября 2014)

Когда они должны быть упорядочены по дате окончания, с самыми последними датами в первую очередь.

Комментарии:

1. Как выглядят ваши результаты прямо сейчас с этими аргументами?

2. Возвращаемые записи правильные, но порядок неправильный. Предложения orderby не имеют никакого эффекта. Если я изменю orderby, чтобы ссылаться на поля напрямую, сортировка действительно произведет эффект. Но мне нужен orderby, чтобы объединить exhibition_end_date и event_date в единый порядок. 'orderby' => array( 'exhibition_end_date' => 'ASC', 'event_date' => 'ASC', ),

3. Не используйте order by в аргументах и вместо этого упорядочивайте массив с помощью php после выполнения запроса. Итак, сначала получите данные, а затем функцию сортировки, подобную показанной здесь, для сравнения значений в массиве. php.net/manual/en/function.sort.php

Ответ №1:

Одним из вариантов является размещение перехвата для запроса orderby , имеющего примерно следующее содержимое:

 add_filter( 'posts_orderby', 'my_posts_orderby', 10, 1 );
function my_posts_orderby( $orderby ) {
    if (is_exhibition_event_query()) {
        return 'IFNULL(exhibition_end_date, event_date) ASC';
    }
    return $orderby;
}
  

Вам нужно реализовать is_exhibition_event_query() , чтобы ограничить эту модификацию запроса этим конкретным запросом. Один из вариантов — установить глобальную переменную перед запросом и отменить ее после завершения запроса.

Обновить

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

 SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts
INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )
WHERE  
  ( wp_postmeta.meta_key = 'exhibition_end_date' OR
    wp_postmeta.meta_key = 'event_date' )
  AND wp_postmeta.meta_value < '20161023'
  AND wp_postmeta.meta_value >= '20080623'
  AND wp_posts.post_type IN ('exhibition', 'event')
  AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'acf-disabled' OR wp_posts.post_author = 1 AND wp_posts.post_status = 'private')
ORDER BY wp_postmeta.meta_value DESC
LIMIT 0, 20
  

Я не уверен, достижимо ли это с помощью WP_Query . Для нетривиальных запросов я всегда использую вместо этого прямые запросы SQL ( wpdb->get_results() ). В дополнение к posts_orderby , существуют аналогичные перехваты для других частей запроса, поэтому иногда более просто использовать WP_Query и настраивать запрос с помощью одного или нескольких из этих перехватов.

Комментарии:

1. Привет, Маса, спасибо за идею. Я попробовал то, что вы упомянули, и думаю, что я ближе, но результат по-прежнему неверен. Я обновил ответ с помощью выходных данных sql запроса. В posts_orderby фильтре я возвращаю эту строку "IFNULL(mt1.meta_value 0, mt2.meta_value 0) DESC" . Есть идеи?

2. Смотрите ОБНОВЛЕНИЕ для получения более подробной информации.

Ответ №2:

К сожалению, я не смог определить правильный запрос, чтобы получить желаемый порядок прямо из базы данных. Тем не менее, я придумал простую usort функцию для сравнения мета-значений.

Чтобы еще больше усложнить SQL, необходимый для упорядочивания этого, exhibition_start_date хранится в формате Ymd , в то время как event_date хранится в формате Y-m-d H:i:s .

 usort( $the_query->posts, 'my_compare_exhibition_and_event_dates_descending' );

function my_compare_exhibition_and_event_dates_descending($a, $b) {
  $a_date = get_post_meta( $a->ID, 'exhibition_start_date', true );
  if ( empty( $a_date ) ) {
    $a_date = get_post_meta( $a->ID, 'event_date', true );
    $a_date = DateTime::createFromFormat( 'Y-m-d H:i:s', $a_date )->format( 'Ymd' );
  }

  $b_date = get_post_meta( $b->ID, 'exhibition_start_date', true );
  if ( empty( $b_date ) ) {
    $b_date = get_post_meta( $b->ID, 'event_date', true );
    $b_date = DateTime::createFromFormat( 'Y-m-d H:i:s', $b_date )->format( 'Ymd' );
  }

  return ( $a_date > $b_date ) ? -1 : 1;
}