如何将两个查询合并在一起


10

我正在尝试通过首先显示带有图像的帖子,然后显示没有图像的帖子来对类别中的帖子进行排序。我已经通过运行两个查询来做到这一点,现在我想将两个查询合并在一起。

我有以下内容:

<?php
$loop = new WP_Query( array('meta_key' => '_thumbnail_id', 'cat' => 1 ) );
$loop2 = new WP_Query( array('meta_key' => '', 'cat' => 1 ) );
$mergedloops = array_merge($loop, $loop2);

while($mergedloops->have_posts()): $mergedloops->the_post(); ?>

但是,当我尝试查看页面时,出现以下错误:

 Fatal error: Call to a member function have_posts() on a non-object in...

然后,我尝试将array_merge强制转换为对象,但出现以下错误:

Fatal error: Call to undefined method stdClass::have_posts() in...

如何解决此错误?

Answers:


8

单个查询

再多考虑一下,您就有可能进行单个/主查询。换句话说:当您可以使用默认查询时,不需要另外两个查询。而且,如果您无法使用默认查询,则无论您想拆分查询多少个循环,都不需要一个查询。

先决条件

首先,您需要在pre_get_posts过滤器中设置(如我的其他答案所示)所需的值。在那里,您可能会设置posts_per_pagecat。没有pre_get_posts-Filter的示例:

$catID = 1;
$catQuery = new WP_Query( array(
    'posts_per_page' => -1,
    'cat'            => $catID,
) );
// Add a headline:
printf( '<h1>%s</h1>', number_format_i18n( $catQuery->found_posts )
    .__( " Posts filed under ", 'YourTextdomain' )
    .get_cat_name( $catID ) );

建立基地

接下来,我们需要一个小的自定义插件(或者,functions.php如果您不介意在更新或主题更改期间随意移动插件,则将其放入文件中):

<?php
/**
 * Plugin Name: (#130009) Merge Two Queries
 * Description: "Merges" two queries by using a <code>RecursiveFilterIterator</code> to divide one main query into two queries
 * Plugin URl:  http://wordpress.stackexchange.com/questions/130009/how-to-merge-two-queries-together
 */

class ThumbnailFilter extends FilterIterator implements Countable
{
    private $wp_query;

    private $allowed;

    private $counter = 0;

    public function __construct( Iterator $iterator, WP_Query $wp_query )
    {
        NULL === $this->wp_query AND $this->wp_query = $wp_query;

        // Save some processing time by saving it once
        NULL === $this->allowed
            AND $this->allowed = $this->wp_query->have_posts();

        parent::__construct( $iterator );
    }

    public function accept()
    {
        if (
            ! $this->allowed
            OR ! $this->current() instanceof WP_Post
        )
            return FALSE;

        // Switch index, Setup post data, etc.
        $this->wp_query->the_post();

        // Last WP_Post reached: Setup WP_Query for next loop
        $this->wp_query->current_post === $this->wp_query->query_vars['posts_per_page'] -1
            AND $this->wp_query->rewind_posts();

        // Doesn't meet criteria? Abort.
        if ( $this->deny() )
            return FALSE;

        $this->counter++;
        return TRUE;
    }

    public function deny()
    {
        return ! has_post_thumbnail( $this->current()->ID );
    }

    public function count()
    {
        return $this->counter;
    }
}

这个插件做一件事:它利用了PHP SPL(标准PHP库)及其接口和迭代器。现在我们得到的是一个FilterIterator允许我们方便地从循环中删除项目的对象。它扩展了PHP SPL筛选器迭代器,因此我们无需设置所有内容。该代码的注释很好,但是这里有一些注意事项:

  1. accept()方法允许定义允许循环或不允许循环的条件。
  2. 在该方法内,我们使用WP_Query::the_post(),因此您可以简单地使用模板文件循环中的每个模板标记。
  3. 当到达最后一个项目时,我们还监视循环并回绕帖子。这允许循环通过无数个循环,而无需重置查询。
  4. 有一种自定义方法不属于FilterIterator规范:deny()。这种方法特别方便,因为它仅包含“ process or not”语句,并且我们可以在以后的类中轻松覆盖它,而无需了解WordPress模板标签之外的任何知识。

如何循环?

有了这个新的迭代,我们不需要if ( $customQuery->have_posts() )while ( $customQuery->have_posts() )了。我们可以简单地foreach声明一下,因为所有必需的检查已经为我们完成了。例:

global $wp_query;
// First we need an ArrayObject made out of the actual posts
$arrayObj = new ArrayObject( $wp_query->get_posts() );
// Then we need to throw it into our new custom Filter Iterator
// We pass the $wp_query object in as second argument to keep track with it
$primaryQuery = new ThumbnailFilter( $arrayObj->getIterator(), $wp_query );

最后,我们只需要一个默认foreach循环即可。我们甚至可以删除the_post()并仍然使用所有模板标签。全局$post对象将始终保持同步。

foreach ( $primaryQuery as $post )
{
    var_dump( get_the_ID() );
}

子回路

现在的好处是,以后的每个查询过滤器都非常易于处理:只需定义deny()方法,即可开始下一个循环。$this->current()将始终指向我们当前循环发布的帖子。

class NoThumbnailFilter extends ThumbnailFilter
{
    public function deny()
    {
        return has_post_thumbnail( $this->current()->ID );
    }
}

正如我们定义的那样,我们现在deny()循环播放每个具有缩略图的帖子,然后我们可以立即循环所有没有缩略图的帖子:

foreach ( $secondaryQuery as $post )
{
    var_dump( get_the_title( get_the_ID() ) );
}

测试一下。

以下测试插件可以在GitHub上以Gist的形式获得。只需上传并激活它。它输出/转储每个循环帖子的ID作为操作的回调loop_start。这意味着根据您的设置,帖子数量和配置,可能会得到很多输出。请添加一些异常中止的语句,并var_dump()在结尾处更改s,以使其符合您的期望以及在何处可以看到。这只是概念的证明。


6

虽然这不是解决此问题的最佳方法(@kaiser的答案是),但要直接回答问题,实际的查询结果将在$loop->posts和中$loop2->posts,因此...

$mergedloops = array_merge($loop->posts, $loop2->posts);

...应该可以,但是您需要使用foreach循环而不是WP_Query基于标准的标准循环结构,因为这样合并查询将破坏WP_Query有关循环的对象“元”数据。

您也可以这样做:

$loop = new WP_Query( array('fields' => 'ids','meta_key' => '_thumbnail_id', 'cat' => 1 ) );
$loop2 = new WP_Query( array('fields' => 'ids','meta_key' => '', 'cat' => 1 ) );
$ids = array_merge($loop->posts, $loop2->posts);
$merged = new WP_Query(array('post__in' => $ids,'orderby' => 'post__in'));

当然,这些解决方案代表多个查询,这就是为什么@ Kaiser's是这种情况下WP_Query可以处理必要逻辑的更好方法的原因。


3

实际上,这里有meta_query(或WP_Meta_Query)-需要一个数组数组-您可以在其中搜索_thumbnail_id行。如果您随后检查EXISTS,则只能获得具有此字段的内容。将其与cat参数结合使用,您将只会获得分配给该类别且ID为1且附加了缩略图的帖子。如果您随后按对其进行排序meta_value_num,那么实际上您将按其缩略图ID的最低到最高顺序对其进行排序(如order和所示ASC)。用作值value时不必指定。EXISTScompare

$thumbsUp = new WP_Query( array( 
    'cat'        => 1,
    'meta_query' => array( 
        array(
            'key'     => '_thumbnail_id',
            'compare' => 'EXISTS',
        ),
    ),
    'orderby'    => 'meta_value_num',
    'order'      => 'ASC',
) );

现在,当遍历它们时,您可以收集所有ID,并将它们用于辅助查询的排他语句中:

$postsWithThumbnails = array();
if ( $thumbsUp->have_posts() )
{
    while ( $thumbsUp->have_posts() )
    {
        $thumbsUp->the_post();

        // collect them
        $postsWithThumbnails[] = get_the_ID();

        // do display/rendering stuff here
    }
}

现在,您可以添加第二个查询。不需要wp_reset_postdata()这里-一切都在变量中,而不是主查询中。

$noThumbnails = new WP_Query( array(
    'cat'          => 1,
    'post__not_in' => $postsWithThumbnails
) );
// Loop through this posts

当然,您可以变得更聪明,只需更改内部的SQL语句pre_get_posts就不会浪费主查询。您也可以$thumbsUppre_get_posts过滤器回调中简单地执行第一个查询(如上)。

add_filter( 'pre_get_posts', 'wpse130009excludeThumbsPosts' );
function wpse130009excludeThumbsPosts( $query )
{
    if ( $query->is_admin() )
        return $query;

    if ( ! $query->is_main_query() )
        return $query;

    if ( 'post' !== $query->get( 'post_type' ) )
        return $query;

    // Only needed if this query is for the category archive for cat 1
    if (
        $query->is_archive() 
        AND ! $query->is_category( 1 )
    )
        return $query;

    $query->set( 'meta_query', array( 
        array(
            'key'     => '_thumbnail_id',
            'compare' => 'EXISTS',
        ),
    ) );
    $query->set( 'orderby', 'meta_value_num' );

    // In case we're not on the cat = 1 category archive page, we need the following:
    $query->set( 'category__in', 1 );

    return $query;
}

这改变了主要查询,因此我们只会得到附有缩略图的帖子。现在,我们可以(如上面的第一个查询所示)在主循环中收集ID,然后添加第二个查询以显示其余帖子(无缩略图)。

除此之外,您甚至可以变得更聪明,并可以posts_clauses根据元值直接更改和修改查询顺序。看一下这个答案,因为当前答案只是一个起点。


3

您实际上需要的是第三个查询,以一次获取所有帖子。然后,您更改前两个查询以不返回帖子,而仅返回可以使用的格式的帖子ID。

'fields'=>'ids'参数将使查询实际上返回匹配的帖子ID号的数组。但是我们不需要整个查询对象,因此我们将get_posts用于这些对象。

首先,获取我们需要的帖子ID:

$imageposts = get_posts( array('fields'=>'ids', 'meta_key' => '_thumbnail_id', 'cat' => 1 ) );
$nonimageposts = get_posts( array('fields'=>'ids', 'meta_key' => '', 'cat' => 1 ) );

$ imageposts和$ nonimageposts现在都将是帖子ID号的数组,因此我们将它们合并

$mypostids = array_merge( $imageposts, $nonimageposts );

消除重复的ID号...

$mypostids = array_unique( $mypostids );

现在,进行查询以按指定的顺序获取实际的帖子:

$loop = new WP_Query( array('post__in' => $mypostids, 'ignore_sticky_posts' => true, 'orderby' => 'post__in' ) );

$ loop变量现在是一个WP_Query对象,其中包含您的帖子。


谢谢你 发现这是保持一个循环结构和简单的分页计算最简单的解决方案。
杰·尼利
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.