I have two queries that I need to try to combine so that my pagination works correctly, and the posts show up in the correct order.
I have this one query:
$today = date('m/d/Y', strtotime('today'));
$args = array(
'post_type' => 'workshops',
"posts_per_page" => 5,
"paged" => $paged,
'meta_key' => 'select_dates_0_workshop_date',
'orderby' => 'meta_value',
'order' => 'ASC',
'meta_query' => array(
array(
'key' => 'select_dates_0_workshop_date',
'meta-value' => "meta_value",
'value' => $today,
'compare' => '>=',
'type' => 'CHAR'
)
)
);
The results of this query need to come after the query above:
$args = array(
'post_type' => 'workshops',
"posts_per_page" => 5,
"paged" => $paged,
'meta_key' => 'select_dates_0_workshop_date',
'orderby' => 'meta_value',
'order' => 'DESC',
'meta_query' => array(
array(
'key' => 'select_dates_0_workshop_date',
'meta-value' => "meta_value",
'value' => $today,
'compare' => '<',
'type' => 'CHAR'
)
)
);
The difference between the two queries is the: 'order'
and the 'compare'
.
I have done this in pure MYSQL Queries, but I am not sure how to do this on WordPress
This is an updated version of the answer, that's more flexible then the previous one.
Here's one idea using a SQL UNION
:
We can use the data from the posts_clauses
filter to rewrite the SQL query from the posts_request
filter.
We extend the WP_Query
class to achieve our goal. We actually do that twice:
WP_Query_Empty
: to get the generated SQL query of each sub-queries, but without doing the database query.WP_Query_Combine
: to fetch the posts.The following implementation supports combining N
sub-queries.
Here are two demos:
Let's assume you have six posts, ordered by date (DESC):
CCC
AAA
BBB
CCC
YYY
ZZZ
XXX
where the XXX
, YYY
and ZZZ
are older than DT=2013-12-14 13:03:40
.
Let's order our posts so that posts published after DT
are ordered by title (ASC) and posts publisehd before DT
are ordered by title (DESC):
AAA
BBB
CCC
ZZZ
YYY
XXX
Then we can use the following:
/**
* Demo #1 - Combine two sub queries:
*/
$args1 = array(
'post_type' => 'post',
'orderby' => 'title',
'order' => 'ASC',
'date_query' => array(
array( 'after' => '2013-12-14 13:03:40' ),
),
);
$args2 = array(
'post_type' => 'post',
'orderby' => 'title',
'order' => 'DESC',
'date_query' => array(
array( 'before' => '2013-12-14 13:03:40', 'inclusive' => TRUE ),
),
);
$args = array(
'posts_per_page' => 1,
'paged' => 1,
'sublimit' => 1000,
'args' => array( $args1, $args2 ),
);
$results = new WP_Combine_Queries( $args );
This generates the following SQL query:
SELECT SQL_CALC_FOUND_ROWS * FROM (
( SELECT wp_posts.*
FROM wp_posts
WHERE 1=1
AND ( ( post_date > '2013-12-14 13:03:40' ) )
AND wp_posts.post_type = 'post'
AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private')
ORDER BY wp_posts.post_title ASC
LIMIT 1000
)
UNION
( SELECT wp_posts.*
FROM wp_posts
WHERE 1=1
AND ( ( post_date <= '2013-12-14 13:03:40' ) )
AND wp_posts.post_type = 'post'
AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private')
ORDER BY wp_posts.post_title DESC
LIMIT 1000
)
) as combined LIMIT 0, 10
Here's your example:
/**
* Demo #2 - Combine two sub queries:
*/
$today = date( 'm/d/Y', strtotime( 'today' ) );
$args1 = array(
'post_type' => 'workshops',
'meta_key' => 'select_dates_0_workshop_date',
'orderby' => 'meta_value',
'order' => 'ASC',
'meta_query' => array(
array(
'key' => 'select_dates_0_workshop_date',
'value' => $today,
'compare' => '>=',
'type' => 'CHAR',
),
)
);
$args2 = array(
'post_type' => 'workshops',
'meta_key' => 'select_dates_0_workshop_date',
'orderby' => 'meta_value',
'order' => 'DESC',
'meta_query' => array(
array(
'key' => 'select_dates_0_workshop_date',
'value' => $today,
'compare' => '<',
'type' => 'CHAR',
),
)
);
$args = array(
'posts_per_page' => 5,
'paged' => 4,
'sublimit' => 1000,
'args' => array( $args1, $args2 ),
);
$results = new WP_Combine_Queries( $args );
This should give you a query like this one:
SELECT SQL_CALC_FOUND_ROWS * FROM (
( SELECT wp_posts.*
FROM wp_posts
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)
WHERE 1=1
AND wp_posts.post_type = 'workshops'
AND (wp_posts.post_status = 'publish' OR wp_posts.post_author = 1 AND wp_posts.post_status = 'private')
AND (wp_postmeta.meta_key = 'select_dates_0_workshop_date' AND (mt1.meta_key = 'select_dates_0_workshop_date' AND CAST(mt1.meta_value AS CHAR) >= '05/16/2014') )
GROUP BY wp_posts.ID
ORDER BY wp_postmeta.meta_value ASC
LIMIT 1000
)
UNION
( SELECT wp_posts.*
FROM wp_posts
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)
WHERE 1=1
AND wp_posts.post_type = 'workshops'
AND (wp_posts.post_status = 'publish' OR wp_posts.post_author = 1 AND wp_posts.post_status = 'private')
AND (wp_postmeta.meta_key = 'select_dates_0_workshop_date' AND (mt1.meta_key = 'select_dates_0_workshop_date' AND CAST(mt1.meta_value AS CHAR) < '05/16/2014') )
GROUP BY wp_posts.ID
ORDER BY wp_postmeta.meta_value DESC
LIMIT 1000
)
) as combined LIMIT 15, 5
We could also combine more than two sub queries:
/**
* Demo #3 - Combine four sub queries:
*/
$args = array(
'posts_per_page' => 10,
'paged' => 1,
'sublimit' => 1000,
'args' => array( $args1, $args2, $args3, $args4 ),
);
$results = new WP_Combine_Queries( $args );
Here are our demo classes:
/**
* Class WP_Combine_Queries
*
* @uses WP_Query_Empty
* @link https://stackoverflow.com/a/23704088/2078474
*
*/
class WP_Combine_Queries extends WP_Query
{
protected $args = array();
protected $sub_sql = array();
protected $sql = '';
public function __construct( $args = array() )
{
$defaults = array(
'sublimit' => 1000,
'posts_per_page' => 10,
'paged' => 1,
'args' => array(),
);
$this->args = wp_parse_args( $args, $defaults );
add_filter( 'posts_request', array( $this, 'posts_request' ), PHP_INT_MAX );
parent::__construct( array( 'post_type' => 'post' ) );
}
public function posts_request( $request )
{
remove_filter( current_filter(), array( $this, __FUNCTION__ ), PHP_INT_MAX );
// Collect the generated SQL for each sub-query:
foreach( (array) $this->args['args'] as $a )
{
$q = new WP_Query_Empty( $a, $this->args['sublimit'] );
$this->sub_sql[] = $q->get_sql();
unset( $q );
}
// Combine all the sub-queries into a single SQL query.
// We must have at least two subqueries:
if ( count( $this->sub_sql ) > 1 )
{
$s = '(' . join( ') UNION (', $this->sub_sql ) . ' ) ';
$request = sprintf( "SELECT SQL_CALC_FOUND_ROWS * FROM ( $s ) as combined LIMIT %s,%s",
$this->args['posts_per_page'] * ( $this->args['paged']-1 ),
$this->args['posts_per_page']
);
}
return $request;
}
} // end class
/**
* Class WP_Query_Empty
*
* @link https://stackoverflow.com/a/23704088/2078474
*/
class WP_Query_Empty extends WP_Query
{
protected $args = array();
protected $sql = '';
protected $limits = '';
protected $sublimit = 0;
public function __construct( $args = array(), $sublimit = 1000 )
{
$this->args = $args;
$this->sublimit = $sublimit;
add_filter( 'posts_clauses', array( $this, 'posts_clauses' ), PHP_INT_MAX );
add_filter( 'posts_request', array( $this, 'posts_request' ), PHP_INT_MAX );
parent::__construct( $args );
}
public function posts_request( $request )
{
remove_filter( current_filter(), array( $this, __FUNCTION__ ), PHP_INT_MAX );
$this->sql = $this->modify( $request );
return '';
}
public function posts_clauses( $clauses )
{
remove_filter( current_filter(), array( $this, __FUNCTION__ ), PHP_INT_MAX );
$this->limits = $clauses['limits'];
return $clauses;
}
protected function modify( $request )
{
$request = str_ireplace( 'SQL_CALC_FOUND_ROWS', '', $request );
if( $this->sublimit > 0 )
return str_ireplace( $this->limits, sprintf( 'LIMIT %d', $this->sublimit ), $request );
else
return $request;
}
public function get_sql( )
{
return $this->sql;
}
} // end class
You can then adjust the classes to your needs.
I use the trick mentioned here to preserve the order of UNION
sub queries.
You can modify it accordingly with our sublimit
parameter.
This should also work for main queries, by using the posts_request
filter, for example.
I hope this helps.
The trouble lies in the 'order' argument: the 'meta_query' argument accepts an array of meta queries as well as the 'relation' argument (basically, "AND" or "OR"). This could be used to select posts both less-than and greater-than-or-equal-to $today
(though at that point the meta query is useless as it's selecting every ("OR") or no ("AND") workshops. Unfortunately, WP_Query doesn't allow you to arbitrarily sort results (switching from closest upcoming to most recent past workshop).
If you know how to pull this off directly in MySQL, you might consider looking at the WP_Query filters, specifically the posts_orderby
filter. These filters enable you to start playing with the actual SQL being generated by the WP_Query class without having to switch to totally-custom queries. Be forewarned, however, that it's important to limit the scope of these filters or risk them being applied to every query on your site (the posts_where
filter documentation has some good examples on how to do so).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With