$conf, $runtime; function_exists('chdir') AND chdir(APP_PATH); $r = 'mysql' == $conf['cache']['type'] ? website_set('runtime', $runtime) : cache_set('runtime', $runtime); } function runtime_truncate() { global $conf; 'mysql' == $conf['cache']['type'] ? website_set('runtime', '') : cache_delete('runtime'); } register_shutdown_function('runtime_save'); ?>posts - Random sort within an already sorted query|Programmer puzzle solving
最新消息:Welcome to the puzzle paradise for programmers! Here, a well-designed puzzle awaits you. From code logic puzzles to algorithmic challenges, each level is closely centered on the programmer's expertise and skills. Whether you're a novice programmer or an experienced tech guru, you'll find your own challenges on this site. In the process of solving puzzles, you can not only exercise your thinking skills, but also deepen your understanding and application of programming knowledge. Come to start this puzzle journey full of wisdom and challenges, with many programmers to compete with each other and show your programming wisdom! Translated with DeepL.com (free version)

posts - Random sort within an already sorted query

matteradmin10PV0评论

I have a working query which returns a set of custom posts which are ordered by their package ID (ASC). For a query with 9 results this may return for example:

4 posts (Post ID's 1,2,3,4) with Package ID 1

3 posts (Post ID's 5,6,7) with Package ID 2

2 posts (Post ID's 8,9) with Package ID 3

When these posts are displayed they are always in the same order. In other words, they are displayed in order from Post ID 1 to Post ID 9.

What I am trying to achieve is to sort the results of each subset (defined by Package ID) randomly. In this way the posts would be displayed as such:

Random display of Post ID's 1 through to 4 (eg 2,1,4,3)

Random display of Post ID's 5 through to 7 (eg 6,5,7)

Random display of Post ID's 8 through to 9 (eg 8,9)

So the posts are still grouped by the Package ID but the posts within each package are displayed randomly.

This is how my current query looks:

$args = array( 
    'post_type' => 'custom_post_type',
    'post_status' => 'publish',
    'posts_per_page' => 9,
    'meta_key' => 'pkg_id',
    'orderby' => 'pkg_id',
    'order' => 'ASC'
);

What I thought 'might' work but doesn't:

$args = array( 
    'post_type' => 'custom_post_type',
    'post_status' => 'publish',
    'posts_per_page' => 9,
    'meta_key' => 'pkg_id',
    'orderby' => array( 'pkg_id' => 'ASC', 'rand' )
);

Any suggestions as I am completely stumped!

I have a working query which returns a set of custom posts which are ordered by their package ID (ASC). For a query with 9 results this may return for example:

4 posts (Post ID's 1,2,3,4) with Package ID 1

3 posts (Post ID's 5,6,7) with Package ID 2

2 posts (Post ID's 8,9) with Package ID 3

When these posts are displayed they are always in the same order. In other words, they are displayed in order from Post ID 1 to Post ID 9.

What I am trying to achieve is to sort the results of each subset (defined by Package ID) randomly. In this way the posts would be displayed as such:

Random display of Post ID's 1 through to 4 (eg 2,1,4,3)

Random display of Post ID's 5 through to 7 (eg 6,5,7)

Random display of Post ID's 8 through to 9 (eg 8,9)

So the posts are still grouped by the Package ID but the posts within each package are displayed randomly.

This is how my current query looks:

$args = array( 
    'post_type' => 'custom_post_type',
    'post_status' => 'publish',
    'posts_per_page' => 9,
    'meta_key' => 'pkg_id',
    'orderby' => 'pkg_id',
    'order' => 'ASC'
);

What I thought 'might' work but doesn't:

$args = array( 
    'post_type' => 'custom_post_type',
    'post_status' => 'publish',
    'posts_per_page' => 9,
    'meta_key' => 'pkg_id',
    'orderby' => array( 'pkg_id' => 'ASC', 'rand' )
);

Any suggestions as I am completely stumped!

Share Improve this question asked May 11, 2016 at 0:39 David CloughDavid Clough 1631 silver badge8 bronze badges
Add a comment  | 

4 Answers 4

Reset to default 7

There is no need to do an overrated amount of queries to accomplish this. you can still use only one query, the_posts filter and some PHP to sort your code as needed.

I take that this is a custom query from what I read in your question, so we can do the following:

  • First, we want to introduce a custom WP_Query parameter so that we can use that parameter as a trigger for our the_posts filter. We will call this parameter n wpse_custom_sort and it will take a value of true in order to trigger the filter

  • Next, we will use the the_posts filter to sort the posts according to custom field, then sort those posts randomly per custom field and finally return the sorted posts

THE CODE

Just a few notes though

  • The code is untested and needs at least PHP 5.4

  • I have used get_post_meta() to return the custom field values per post, as you are using ACF, you might need to adjust this to use get_field(). I'm not familiar with ACF, so you will need to sort that part. The logic would still remain the same though

  • Just remember, querying custom fields does not result in extra queries as they are cached, so this is a very lean, optimized way to accomplish your end goal

Here is the code for the custom query. All you basically need to do is to add the extra parameter to your query args in your custom page template

// Run your query normally
$args = [
    'wpse_custom_sort' => true, // This parameter will trigger or the_posts filter
    // Your query args here
];
$q = new WP_Query( $args );

// Your loop as normal

Now, we will run our filter (this goes into functions.php or preferably into a custom plugin)

/**
 * Function to flatten a multidimentional array (for later use)
 * 
 * Credit to zdenko
 * @link https://gist.github/kohnmd/11197713
 */
function flatten_array( $arg ) 
{
    return is_array( $arg ) 
        ? 
        array_reduce( $arg, function ( $c, $a ) 
            { 
                return array_merge( $c, flatten_array( $a ) ); 
            }, [] ) 
        : 
        [$arg];
}

// The the_posts filter
add_filter( 'the_posts', function ( $posts, $q )
{
    // First we need remove our filter
    remove_filter( current_filter(), __FUNCTION__ );

    // Check if our custom parameter is set and is true, if not, bail early
    if ( true !== $q->get( 'wpse_custom_sort' ) )
        return $posts; 

    // Before we do any work, and to avoid WSOD, make sure that the flatten_array function exists
    if ( !function_exists( 'flatten_array' ) )
        return $posts;

    // Our custom parameter is available and true, lets continue
    // Loop through the posts
    $major_array = [];
    foreach ( $posts as $p ) {
        $meta = get_post_meta(
            $p->ID,
            'pkg_id',
            true
        );

        // Bail if we do not have a $meta value, just to be safe
        if ( !$meta )
            continue;

        // Sanitize the value
        $meta = filter_var( $meta, FILTER_SANITIZE_STRING );

        // Lets build our array
        $major_array[$meta][] = $p;
    }

    // Make sure we have a value for $major_array, if not, bail
    if ( !$major_array )
        return $posts;

    // Lets randomize the posts under each custom field
    $sorted = [];
    foreach ( $major_array as $field ) 
        $sorted[] = shuffle( $field );

    // Lets flatten and return the array of posts
    $posts = flatten_array( $sorted );

    return array_values( $posts );
}, 10, 2 );

If we use:

$args = array( 
    'post_type'      => 'custom_post_type',
    'post_status'    => 'publish',
    'posts_per_page' => 9,
    'meta_key'       => 'pkg_id',
    'orderby'        => array( 'pkg_id' => 'ASC', 'rand' => 'DESC' )
);

then we get the order part as:

ORDER BY wp_postmeta.meta_value ASC, RAND() DESC

This seems to work, but the DESC is not needed.

But note that ordering by RAND() doesn't scale well. The method by @PieterGoosen might be better suited for you in that case.

Not possible with a single query you would have to do 3 seperate query each one targeting the different "Package ID's" and ordering those queries as rand.

$args1 = array(
    'post_type' => 'custom_post_type',
    'orderby' => 'rand',
    'meta_query' => array(
        array(
            'key' => 'pkg_id',
            'value' => 1, // you will have to check if this is correct
        )
    )
);

$args2 = array(
    'post_type' => 'custom_post_type',
    'orderby' => 'rand',
    'meta_query' => array(
        array(
            'key' => 'pkg_id',
            'value' => 2, // you will have to check if this is correct
        )
    )
);

$args3 = array(
    'post_type' => 'custom_post_type',
    'orderby' => 'rand',
    'meta_query' => array(
        array(
            'key' => 'pkg_id',
            'value' => 3, // you will have to check if this is correct
        )
    )
);

This will seperate your posts into their "Package ID's" and order each set as Random

One addition to Pieter Goosen answer.

I had to change his code at the end since shuffle($field) returns a boolen, not the shuffled array itself. So i got an array of booleans, not an array of (post-)arrays. To get the expected result i did the following:

  //Instead of 
    foreach ( $major_array as $field ) 
        //Shuffle returns boolean, so $sorted will be a
        $sorted[] = shuffle( $field );  

 // I went with
    foreach ( $major_array as $field ) {
        shuffle( $field );   // 1. Shuffle $field, which is an array
        $sorted[] = $field;  // Then add this 
    }
Post a comment

comment list (0)

  1. No comments so far