WordPress: Sort Posts by Multiple Fields

Tue July 21, 2009

UPDATE: I have written a new post about how to better accomplish this task: WordPress: Sort Posts by Multiple Fields (Part II)

Please see the above link for a much more efficient way to sort by multiple fields.

I was recently creating a star-rating system for a client, and we were sorting the reviews (or “posts”) by the number of stars. The problem that came up was that with a 5 star system, and increments of half-stars, you will necessarily end up with many of the ratings being equal. We wanted to be able to control the exact order the reviews were shown in the case of a tie. Here’s how I did it.

Adding a “Priority” Custom Field

The first step was to add a custom field called “priority”, which would act as the tie-breaker.  Basically, if two posts have the same star rating, we’ll check the priority and see which one is higher, and that one will be displayed first.  I was using the Flutter plugin, so adding another custom field was as simple as clicking a few buttons in the admin panel.

Custom Query

Sorting by a single field is relatively easy.  You would create a custom query for the posts by using something like this (to sort alphabetically):

query_posts("orderby=title&order=ASC");

Using Flutter’s syntax for sorting by its custom fields, we’d use:

query_posts("customorderby=x_stars-overall-rating&order=DESC");

Refining the Order with usort()

But you can’t sort by more than one field using the above methods.  That’s where usort comes in.  PHP has a function called usort() which sorts an array based on a custom function which “compares” the two array items in any way you like.  So all we need to do is run our array of posts through a function which does the more complex comparison using both the “stars” field and then the “priority” field for tie-breakers.

First, we insert this in the category listing page (or whichever page you’re working on) to run the custom sort function:

usort($wp_query->posts, 'review_tie_breaker');

The first argument in the function is the array of posts created by the default query in WordPress.  The second argument is the name of our custom sort function, which is shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function review_tie_breaker($a, $b) {
   // get the star value for each 
   $a_stars = get_post_meta($a->ID, 'stars-overall-rating', true);
   $b_stars = get_post_meta($b->ID, 'stars-overall-rating', true);
   // if it's not a tie, return which has more stars
   if ($a_stars != $b_stars) {
      return ((float)$a_stars > (float)$b_stars) ? -1 : 1;
   }
   // else, if they are tied, go to the priority tiebreaker...
 
   // get the priority value for each
   $a_priority = get_post_meta($a->ID, 'rating-priority', true);
   $b_priority = get_post_meta($b->ID, 'rating-priority', true);
 
   // if a priority value has not been entered, default to 0
   $a_priority = ($a_priority == '') ? 0 : (int)$a_priority;
   $b_priority = ($b_priority == '') ? 0 : (int)$b_priority;
 
   // if the priority is also equal, just return as a tie
   if ($a_priority == $b_priority) {
      return 0;
   }
   // if not, we return the priority comparison
   return ($a_priority < $b_priority) ? -1 : 1;
}

How the Tie-Breaker Function Works

The way the custom sort function works is by comparing two elements of the array ($a and $b, above), and deciding which one is “greater than” the other.  If $a is greater than $b, we should return a value of 1.  If the opposite is true, we return -1.  If they are tied, we return 0.  The above function uses ternary operators to compare first the star ratings and then the priority values.  If the stars are not tied, it just returns which has the higher star rating, and never gets to the tie-breaker phase.  But if they are equal, we then compare the second criteria.

There is also a check to make sure the “priority” field has a value.  This is so that the user is not forced to input a value for every single review, just the ones that need to be tweaked.  Using this system, all that is required to bump a review up above another review with an equal star-rating is to insert a “-1″ in the priority box.

I used this for a star rating system, but it could be used for any number of applications.  For example, if you wanted to order your posts by author, but for each author make sure they are in chronological order.

Tags: ,

16 Comments:

rendom said:

Sun August 9, 2009 at 4:23 am

Great, just what i need. But where i must to put this code?

rendom said:

Sun August 9, 2009 at 6:05 am

nevermind its works!

progzy said:

Thu April 15, 2010 at 4:01 am

This can be achieved with “posts_orderby” hook

Jeff Gran said:

Thu April 15, 2010 at 6:49 am

@progzy: Yes, you are correct. And it’s most likely more efficient CPU and memory-wise to do it that way. When I wrote this, my knowledge of MySQL and WordPress’ hooks wasn’t as developed. Maybe I should write an update.

Thanks for the tip. :)

goto10 said:

Tue May 11, 2010 at 7:47 pm

Great post Jeff.

For each of my custom post type “posts”, I’m storing all of the custom fields in a single array. I ended up needing to sort the post listing based on this data, and was bummed to see that I couldn’t do it directly with query_posts().

Using your post, I was able to work out a solution. I’m still not sure that I would have been able to use progzy’s suggestion of the posts_orderby hook, since the my meta is all wrapped up in an array (I need to verify that).

I’ll certainly be more careful about “optimizing” my metadata like this in the future, especially if I’m going to be using it as a basis for sorting a list of posts.

Jeff Gran said:

Tue May 11, 2010 at 8:10 pm

Hi, goto10

Yeah, if your meta data is a serialized array, I think you’re right. You wouldn’t able to sort by a specific element of the array. You also wouldn’t be able to query to get only posts with certain values there. Which is why storing multiple values in a single database field is considered bad practice. :) Look up 1st/2nd/3rd degree normalization if you’re curious.

Marget Michelotti said:

Fri August 6, 2010 at 6:30 pm

Awesome job!

Will said:

Wed November 17, 2010 at 12:31 pm

I have a site that needs to sort by date first, then by a custom value (sort_index).

E.g. Group A has 5 news stories from 11/17. Group B has 5 news stories from the day before (11/16).

Now, on the main archive page, the visitor would see these sorted by date descending.

But within each day, the posts would be sorted by a custom (sort_index) value.

Using the above method, im seeing the entire post list resorted according to this sort_index value. that is, it doesn’t seem to be double sorting but rather *re-sorting*.

Does this make sense? Am I way off base here? :P

Cheers, and thanks Jeff.

chety said:

Thu November 18, 2010 at 8:37 am

Can you show how to use posts_orderby to sort by multiple fields?

Jeff Gran said:

Fri November 26, 2010 at 11:52 pm

@Will: yes, that’s exactly what the above code is for. It allows you to sort on one field, and then sort each grouping within that first sort, just like you’re talking about.

@chety: In the instance above, it would be a bit more complicated than just a simple hook into the posts_orderby hook, because there are also joins involved, so you’d also have to add the join into the join hook. But basically the idea is to use SQL to do the sorting for you before it returns the posts, instead of getting ALL the posts and then sorting them in memory, via the PHP code.

But if you were just ordering on two different standard fields (meaning, two fields both in the wp_posts table), in your SQL code the order clause would look something like ‘order by post_title, post_date’. The first column is the first one to sort on, and the second one will sort within the first sort.

Elizabeth said:

Wed March 23, 2011 at 9:19 am

I need to query posts so that they sort first by date with the oldest appearing first, then by category with the lowest number appearing first. I can’t get anything to work. Please help!

elite destinations reviews said:

Fri May 27, 2011 at 9:45 pm

I am curious to find out what blog platform you have been working with? I’m having some small security problems with my latest website and I’d like to find something more secure. Do you have any recommendations?

daniel mamann puerto vallarta said:

Sat May 28, 2011 at 9:06 pm

Hey there! I know this is kinda off topic but I’d figured I’d ask. Would you be interested in exchanging links or maybe guest authoring a blog post or vice-versa? My site addresses a lot of the same topics as yours and I think we could greatly benefit from each other. If you happen to be interested feel free to shoot me an e-mail. I look forward to hearing from you! Awesome blog by the way!

Steve said:

Fri July 1, 2011 at 2:24 pm

” For example, if you wanted to order your posts by author, but for each author make sure they are in chronological order.”

Can you give an example of how this might work? In my case, I want to sort first by a custom field, and then alphabetically by post title within each “block” of posts of the custom field. I’m also not clear how the usort line is meant to integrate with a standard query_posts.

Thanks!

Jeff Gran said:

Wed July 13, 2011 at 8:22 pm

@Steve,

Basically, you query the posts via query_posts first. WordPress has a global variable called $wp_query, which gets populated when you run query_posts().

So then when you run the usort on those posts, it sorts them. It’s inefficient since it’s getting all the posts from MySQL and then sorting them all in memory (which if you have a ton of posts would be veeery slow). But it works OK if you have a small number of posts.

In the example above, I am sorting by stars, and THEN within each block of the same number of stars, sorting by priority. You should be able to take the example tie-breaker function and adapt it to your needs.

Hade said:

Tue August 30, 2011 at 2:43 am

Fantastic, I was looking for a way to order by multiple criteria everywhere.
Thank you!

Leave a Comment