用Meta Query可以实现WordPress文章按照自定义排序,假设安装了WP-PostRatings给文章打分,该插件会把文章平均分存成名叫ratings_average的自定义字段,现在就来按照这个字段排序。

简洁优雅的方法

就是Meta Query,代码放在主题的functions.php里。

function sort_by_ratings( $query ){
	
	if ( ( $query->is_home() || $query->is_archive() ) && $query->is_main_query() ) {
		
		$query->set( 'meta_key', 'ratings_average' );
		$query->set( 'orderby', 'meta_value_num');
		$query->set( 'order', 'DESC' );   
    }
}
add_action( 'pre_get_posts', 'sort_by_ratings' );

却有个严重的问题

该插件只会给打过分的文章创建ratings_average字段,而Meta Query只会选择带有这个字段的文章,也就是说所有没打过分的文章都会从blog首页和存档页消失。

解决的方法呢,直接点就是给每篇文章都创建这个字段,值为0。

复杂点呢,stackoverflow上有对这个问题的讨论,按照给出的方案改进了一下,找到一个暂时的解决方法如下,思路是直接修改sql语句。

function sort_by_ratings( $query ){
	
	if ( ( $query->is_home() || $query->is_archive() ) && $query->is_main_query() ) {
		
	    add_filter( 'posts_fields', 'ratings_fields' );
        add_filter( 'posts_join', 'ratings_join' );
        add_filter( 'posts_where', 'ratings_where' );  
        add_filter( 'posts_groupby', 'ratings_group' );
        add_filter( 'posts_orderby', 'ratings_orderby' );
    }
}
add_action( 'pre_get_posts', 'sort_by_ratings' );
function ratings_fields($fields){
    $order_key = "mt1.meta_value";

	return $fields . ",$order_key AS avg"; 
}

function ratings_join($join){
	global $wpdb;
    $new_join = "
    	INNER JOIN $wpdb->postmeta ON $wpdb->posts.ID = $wpdb->postmeta.post_id
        LEFT JOIN $wpdb->postmeta AS mt1 ON ($wpdb->posts.ID = mt1.post_id AND mt1.meta_key = 'ratings_average')
    ";
    return $join . ' ' . $new_join;    
}
function ratings_where($where){
    global $wpdb;
    $new_where = "
        AND ($wpdb->postmeta.meta_key = 'ratings_average'
        OR  mt1.post_id IS NULL )";
    return $where . ' ' . $new_where;
}

function ratings_group( $group ){
	global $wpdb;
	return "$wpdb->posts.ID";
}
function ratings_orderby( $orderby ){
	global $wpdb;
	return "ISNULL(avg), avg,$wpdb->posts.post_date ASC";
}

生成的sql语句如下:

SELECT SQL_CALC_FOUND_ROWS wp_posts.*,mt1.meta_value AS avg FROM wp_posts 
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 = 'ratings_average') 
WHERE 1=1 
AND wp_posts.post_type = 'post' 
AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') 
AND (wp_postmeta.meta_key = 'ratings_average' OR mt1.post_id IS NULL ) 
GROUP BY wp_posts.ID 
ORDER BY ISNULL(avg), avg,wp_posts.post_date ASC LIMIT 0, 10

结果是分数按照从低分到高分排序,即使order by DESC也是升序排列。如果要让高分上前面,只能用点奇怪的方法,比如最大分数是5,把posts_fields改成这样

function ratings_fields($fields){
    // 只能升序排列,所以要降序排列时先做一次运算
    $max_rating = 5;
    $order_key = "$max_rating - mt1.meta_value";

    return $fields . ",$order_key AS avg"; 
}

如果还有更简单的方法,欢迎留言指教。

16条留言

  1. 妹妹问个问题:我vps上的mysql最近用show full processlist发现,总是有这么一条语句在执行,造成系统运行很慢,该语句是,
    SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = ‘post’ AND (wp_posts.post_status = ‘publish’) ORDER BY wp_posts.post_date DESC LIMIT 0, 15
    这条语句在后台反复执行,这究竟是咋回事呢?
    不胜感激。

    1. 这个没什么,只要显示博客日志都会运行这条语句,而且这个不费资源,最后的”limit 0,15“保证了这一点。

      如果你网站很慢,先安装debug bar,看一下内存占用多少,每个页面大概执行多少条sql查询,是否耗费时间的查询。并不是查询次数越多越慢,一条复杂的sql语句拆分成几条简单语句说不定还能提升性能呢。

      当数据库内容很多时,进行select all查询会很耗资源。

  2. sola老师:
    冒昧地再请教您一下,我安装了WP-PostRatings插件,创建了一个普通页面,结合https://wordpress.org/plugins/wp-postratings/faq/ 中倒数第二个提示,想按照评分由高到低顺序输出id为79分类下的所有文章,输出排序正常,现在遇到的问题是分页出现问题,“上一页”“下一页”地址栏变了,但是输出的内容不变,从下面代码能看出我哪里做的不对吗?谢谢,希望您空闲时间帮忙看看
    代码发不了,附上图片 http://newwebzen.com/sola.jpg
    (错发两次评论,麻烦您删了吧)

    1. 你没保存分页的query变量,所以分页信息丢了。
      文档http://codex.wordpress.org/Function_Reference/query_posts
      看reserving Existing Query Parameters哪一段
      代码应该类似这样

      global $wp_query;
      $args = array_merge( $wp_query->query_vars, array( 'post_type' => 'product' ) );
      query_posts( $args );
      
      1. 好了,解决了,代码如下,谢谢sola老师 😉

        $paged = (get_query_var(‘paged’)) ? get_query_var(‘paged’) : 1;
        $args = array(
        ‘meta_key’ => ‘ratings_average’,
        ‘orderby’ => ‘meta_value_num’,
        ‘order’ => ‘DESC’,
        ‘showposts’ => 10,
        ‘cat’=>79,
        ‘paged’ =>$paged
        );
        query_posts($args);

  3. 慢慢学习!!看看姐的评论提交要刷新么? 最近评论做完了,才发现还有ajax提交评论。据说页面不用刷新诶!!!!看看姐的

    1. ajax提交评论不是啥新鲜货了,老早以前用过,体验也不是特别好,所以还是老实的用默认的了。你可以看看www.ludou.org,他的是ajax提交评论

      1. 算了 我还是用默认的算了 , 姐 现在我的评论遗留个小问题,看你们的评论点击回复后立马可以在下面就回复,为什么我的老是跳到最上面的评论表单,这个怎么在回复后面跟随呢?

  4. 您好,我遇到一個很神奇的問題
    我的文章會重複顯示! 同時造成有的文章沒有顯示!
    debug很久很久..文章確實只有一個(但卻重複顯示)
    資料庫看也確實沒錯..
    後來才發現原來是添加了
    function sort_by_ratings( $query ){
    if ( ( $query ->is_home() || $query ->is_archive() ) && $query ->is_main_query() ) {
    $query ->set( ‘meta_key’ , ‘ratings_average’ );
    $query ->set( ‘orderby’ , ‘meta_value_num’ );
    $query ->set( ‘order’ , ‘DESC’ );
    }
    }
    add_action( ‘pre_get_posts’ , ‘sort_by_ratings’ );

    造成的!!!!!
    真的不明白為什麼..
    我首頁也沒有用到其他query
    在我localhost測試時也曾發生過
    在我專案上現在又發生了
    但時好時壞(好像把文章都砍掉清除重新到一次文章時又好了)
    我有大量文章要倒入..不可能一又發生這問題就全砍掉重倒..
    我不知道到底是哪邊出了問題..
    會有可能是 meta_key 在搞鬼媽?? (可能我文章曾經重複po過造成?)
    還是到底哪邊出了問題呢…(用的主題是pinpress)

    1. 抱歉,補充一下
      我有修改Function: Add Rating Custom Fields 的預設值
      ratings_users(有多少人評價這篇文章)改為1;
      ratings_score(這篇文章的總得分)改為3;
      ratings_average(這篇文章的平均得分)改為3
      不知道有沒有影響?
      還有
      我試了您的暫時解決方法
      卻會造成掛掉@@…….???
      真的不知道是哪裡出了問題….哎..

      1. 这你只能自己调试,把mysql语句打出来,直接到phpmyadmin里去执行,看结果。post重复可能是没有group by post_id这句话。
        另外正常的WordPress Meta Query不应该造成post重复现象,建议你不要开启无关插件,用默认主题测试,功能测试好了再换主题,逐个开启你需要的插件。如果主题里有影响main query的代码,冲突一下也不无可能。

        1. 好的,還是非常感謝您!!

  5. 大侠,求助。我在使用其他woocommerce主题时,填写订单联系人信息了,不见有支付宝选择。但是使用系统原来的主题Twenty Twelve,就能提交订单去支付宝付款。为啥情况,望指点明津。

    1. 没遇到过这种情况,你用的什么主题,试过多少个第三方主题。

评论功能已关闭