How to Avoid Duplicates When Using "Random" in a Query Loop?

If anyone can point me toward a tutorial or any code for this or tell me how to debug this, I’d be incredibly grateful.

I have a CSS Grid. I have seven blocks in the grid that each use a query loop to pull a random post. I want to avoid duplicates when the posts load in the blocks. What would be the best method to do this?

This is my query loop PHP:

return array(
‘post_type’ => ‘post’,
‘posts_per_page’ => 1,
‘orderby’ => ‘rand’,
‘post__not_in’ => $excluded_ids
);

This is the PHP I’m using in a code element:

<?php

function initialize_and_fetch_posts() {

    $excluded_ids = get_transient('excluded_post_ids');
    if ($excluded_ids === false) {
        $excluded_ids = array();
    }

    $block_queries = array();

    for ($i = 1; $i <= 7; $i++) {
        $query_args = array(
            'post_type'        => 'post',
            'posts_per_page'   => 1,
            'fields'           => 'ids',
            'post__not_in'     => $excluded_ids,
            'orderby'          => 'rand'
        );
        $query = new WP_Query($query_args);

        $excluded_ids = array_merge($excluded_ids, $query->posts);
        $block_queries[$i] = $query->posts;
    }

    set_transient('excluded_post_ids', $excluded_ids, 60);

    return $block_queries;
}
?>

Thanks!

Why not just have one block in your grid with the query set and have the options to show 7 posts, and set the order to random?

This way the block will get looped, show 7 blocks with the random order and no duplicates.

Thanks for responding!

When I apply a query loop to show 7 posts, it shows seven posts inside of the block (see the image). When I apply a query loop to show 7 posts to the block that wraps the block, it duplicates that block seven times and doesn’t fill the other blocks. When I apply the query loop to the container grid, the container that wraps the container grid, or the section, it duplicates the entire grid 7 times and each block in each duplicate grid is the same post repeated seven times.

I also tried to create a new query, but I’m very new to all of this and don’t know where I’m going wrong. I debugged it a bit and it seems to be getting post IDs, but something’s wrong with it not applying it properly when I set the “Type” in the query loop to “Random Unique Posts.”

I don’t know if I’m overthinking this and not seeing an easier answer. I’ve been trying everything I can think of to get this to work for days, but I feel more out of my depth the more I try to get it to work.

This is the query code, but I’m pretty sure it has major issues:

/** Code to avoid duplicate posts */
add_filter('bricks/setup/control_options', 'bl_setup_query_controls');
function bl_setup_query_controls($control_options) {
    $control_options['queryTypes']['random_unique_posts'] = esc_html__('Random Unique Posts');
    return $control_options;
}

/* Run new query if option selected */
add_filter('bricks/query/run', 'bl_maybe_run_new_query', 10, 2);
function bl_maybe_run_new_query($results, $query_obj) {
    if ($query_obj->object_type !== 'random_unique_posts') {
        return $results;
    }
    
    $results = run_new_query();
    
    return $results;
}

/* Return results from our custom WP Query arguments */
function run_new_query() {
    $excluded_ids = get_transient('excluded_post_ids');
    
    if ($excluded_ids === false) {
        $excluded_ids = array();
    }

    $query_args = array(
        'post_type'      => 'post',
        'posts_per_page' => 7,
        'orderby'        => 'rand',
        'post__not_in'   => $excluded_ids,
        'no_found_rows'  => true,
    );
    
    $query = new WP_Query($query_args);

    $posts = array();
    if ($query->have_posts()) {
        while ($query->have_posts()) {
            $query->the_post();
            $post_id = get_the_ID();
            $posts[] = array(
                'id'    => $post_id,
                'title' => get_the_title(),
                'video' => get_field('video'),
            );
            $excluded_ids[] = $post_id;
        }
        wp_reset_postdata();
    }

    set_transient('excluded_post_ids', $excluded_ids, 6);
    
    return $posts;
}

You just need to set it up a little differently.

Move the inner block you have with the query loop set on as the first element in your container and delete the rest.

Like this:
Screenshot 2024-05-14 220950

It should work fine for you then :+1:

It should automatically render in the builder for you, like this:

Thank you so much for helping with this!

The blocks have different styling with custom css and some of the blocks won’t contain post data. The top grid is I want the layout to look like, just without the duplicate posts. If I delete the rest of the blocks, it looks like the bottom grid, so I lose the styling and span.

In that case, would I do something like write nth-child CSS to style the grid and put it in the container grid’s custom CSS?

Yes exactly, the query loop is supposed to be based on one element, that will get duplicated, or ‘looped’ to your requirements.

For individual styling of each use the :nth-child method, but you would write it on the query looped element like this:

%root%:nth-child(1) {
  // Your styles
}

%root%:nth-child(2) {
  // Your styles
}

// etc

Happy building!

Thanks. I was able to get it figured out by adding this code to functions.php and the other code to the Query Editor (PHP). There’s a ton of styling and other stuff that I didn’t want to rewrite as nth-child. The code below doesn’t duplicate posts on each refresh. Leaving this up in case anyone deals with this in the future.

/** Code to avoid duplicate posts */
function get_unique_random_posts($num_posts = 1) {

    if (!isset($_SESSION['displayed_posts'])) {
        $_SESSION['displayed_posts'] = [];
    }

    $args = [
        'post_type' => 'post',
        'posts_per_page' => $num_posts,
        'orderby' => 'rand',
        'post__not_in' => $_SESSION['displayed_posts']
    ];

    $query = new WP_Query($args);

    if ($query->have_posts()) {
        while ($query->have_posts()) {
            $query->the_post();
            $_SESSION['displayed_posts'][] = get_the_ID();
            $post_titles[] = get_the_title(); 
        }
        wp_reset_postdata();
    }

    return $query->posts; // Return the post objects
}


/** Query editor (PHP) code */

$posts = get_unique_random_posts();
return [
	'posts_per_page' => 1,
	'post__in' => array_map(function($post) { return $post->ID; }, $posts),
	'orderby' => 'post__in'
];