Skip to content

Adding WP_Query SortBy Rand Optimization #8715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/wp-includes/class-wp-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ public function fill_query_vars( $query_vars ) {
'fields',
'menu_order',
'embed',
'rand_factor',
);

foreach ( $keys as $key ) {
Expand Down Expand Up @@ -768,6 +769,8 @@ public function fill_query_vars( $query_vars ) {
* @type int $posts_per_page The number of posts to query for. Use -1 to request all posts.
* @type int $posts_per_archive_page The number of posts to query for by archive page. Overrides
* 'posts_per_page' when is_archive(), or is_search() are true.
* @type float $rand_factor The factor used to determine the probability for optimized
* random ordering. Default 3.0.
* @type string $s Search keyword(s). Prepending a term with a hyphen will
* exclude posts matching that term. Eg, 'pillow -sofa' will
* return posts containing 'pillow' but not 'sofa'. The
Expand Down Expand Up @@ -2911,6 +2914,10 @@ public function get_posts() {
$where = 'AND 0';
}
}
// Orderby Rand Optimization.
if ( isset( $q['orderby'] ) && 'rand' === $q['orderby'] && empty( $q['nopaging'] ) ) {
$where = $this->_optimize_rand_orderby( $q, $where, $join );
}

$pieces = array( 'where', 'groupby', 'join', 'orderby', 'distinct', 'fields', 'limits' );

Expand Down Expand Up @@ -5115,4 +5122,48 @@ public function lazyload_comment_meta( $check, $comment_id ) {
_deprecated_function( __METHOD__, '4.5.0' );
return $check;
}

/**
* Optimizes ORDER BY RAND() queries using probability sampling.
* Modifies the WHERE clause to include a probability condition based on RAND().
* See: https://core.trac.wordpress.org/ticket/18836
*
* @since 6.8.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param array $q Query vars.
* @param string $where The current WHERE clause.
* @param string $join The current JOIN clause.
* @return string The potentially modified WHERE clause.
*/
protected function _optimize_rand_orderby( $q, $where, $join ) {
global $wpdb;

// Default factor to over-select rows, configurable via query var.
$rand_factor = isset( $q['rand_factor'] ) ? (float) $q['rand_factor'] : 3.0;
$size = absint( $q['posts_per_page'] );
if ( $size <= 0 ) {
// If post_per_page is -1 set at least the default posts_per_page.
$size = (int) get_option( 'posts_per_page' );
}

// Use a simpler count query that respects existing WHERE and JOIN clauses.
$count_query = "SELECT COUNT(*) FROM {$wpdb->posts} {$join} WHERE 1=1 {$where}";

$row_count = (int) $wpdb->get_var( $count_query );

if ( $row_count > 0 && $size > 0 ) {
// Calculate probability, capped at 1.0 (100%).
$probability = min( 1.0, ( $size * $rand_factor ) / $row_count );

// Use seed if provided in query vars (e.g., 'orderby' => 'rand', 'seed' => 123).
$rand_call = isset( $q['seed'] ) ? $wpdb->prepare( 'RAND(%d)', $q['seed'] ) : 'RAND()';

// Add the probability condition to the main $where clause.
$where .= $wpdb->prepare( " AND {$rand_call} <= %f ", $probability );
}

return $where;
}
}
Loading