具有多个自定义帖子类型和多个日期列名称的复杂 meta_query 和 orderby

Posted

技术标签:

【中文标题】具有多个自定义帖子类型和多个日期列名称的复杂 meta_query 和 orderby【英文标题】:Complex meta_query and orderby with multiple custom post types, and multiple date column names 【发布时间】:2017-03-04 19:25:11 【问题描述】:

我正在尝试查询两种自定义帖子类型,exhibitionsevents

展览有exhibition_start_dateexhibition_end_date 自定义字段。

事件有一个event_date 自定义字段。

如何在一个 orderby 子句中对多列进行排序?

我一直在查看此内容以供参考 https://make.wordpress.org/core/2015/03/30/query-improvements-in-wp-4-2-orderby-and-meta_query/。

理想情况下,我的结果如下所示:

展览(2016 年 10 月) 活动(2016 年 9 月) 活动(2016 年 8 月) 展览(2016 年 7 月) 活动(2016 年 6 月)

到目前为止,我的查询没有正确排序:

$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

不过,这仍然不对。这是帖子返回的顺序:

活动(2014 年 10 月 15 日) 活动(2014 年 10 月 16 日) 活动(2014 年 10 月 31 日) 活动(2014 年 8 月 6 日) 展览(2016 年 10 月 14 日) 展览(2014 年 10 月 22 日)

何时应按结束日期排序,最近的日期在前。

【问题讨论】:

您现在使用这些 args 的结果如何? 返回的帖子是正确的,但顺序是错误的。 orderby 子句无效。如果我将 orderby 更改为直接引用字段,排序确实会产生影响。但是,我需要 orderby 将 exhibition_end_dateevent_date 组合成一个订单。 'orderby' =&gt; array( 'exhibition_end_date' =&gt; 'ASC', 'event_date' =&gt; 'ASC', ), 不要在 args 中使用 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-&gt;get_results())。除了posts_orderby,查询的其他部分也有类似的钩子,因此有时使用WP_Query 并使用这些钩子中的一个或多个调整查询会更直接。

【讨论】:

嗨,玛莎,谢谢你的想法。我尝试了你提到的并认为我更接近,但结果仍然不正确。我已经用查询的 sql 输出更新了答案。在posts_orderby 过滤器中,我将返回此行"IFNULL(mt1.meta_value+0, mt2.meta_value+0) DESC"。有什么想法吗? 查看更新了解更多详情。【参考方案2】:

不幸的是,我无法找出正确的查询来直接从数据库中获取我想要的订单。不过,我确实想出了一个简单的usort 函数来比较元值。

为了使订购此命令所需的 SQL 更加复杂,exhibition_start_dateYmd 格式存储,而 event_dateY-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;

【讨论】:

以上是关于具有多个自定义帖子类型和多个日期列名称的复杂 meta_query 和 orderby的主要内容,如果未能解决你的问题,请参考以下文章

带有 ACF 日期字段的 WordPress 自定义帖子类型日历

熊猫中具有相同名称的多个列

用于自定义帖子类型的 Wordpress 多个 slug

php 注册多个自定义帖子类型

php 注册多个自定义帖子类型CPT

php 注册多个自定义帖子类型CPT