Wordpress combine queries

时间秒杀一切 提交于 2020-07-04 16:43:18

问题


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


回答1:


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:

Demo #1:

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 

Demo #2:

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

Demo #3:

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 );

Classes:

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.




回答2:


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).



来源:https://stackoverflow.com/questions/23555109/wordpress-combine-queries

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!