当前位置: 首页>>技术解读>>正文


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

webfans 技术解读 , , 去评论

问题描述

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

我有以下内容:

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

我该如何解决这个错误?

最佳解决方案

单个查询

考虑到这一点,你可以使用单个/主要查询。或者换句话说:当您可以使用默认查询时,无需另外两个查询。如果您无法使用默认查询,则无论您要分割查询的循环数多少,都不需要多个查询。

Prerequisites

首先,您需要在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 (Standard PHP Library)及其接口和迭代器。我们现在得到的是FilterIterator,它允许我们方便地从循环中删除项目。它扩展了PHP SPL Filter Iterator,因此我们不必设置所有内容。代码评论很好,但这里有一些注意事项:

  1. accept()方法允许定义允许循环项目的标准 – 或不。

  2. 在该方法中,我们使用WP_Query::the_post(),因此您可以简单地在模板文件循环中使用每个模板标记。

  3. 当我们到达最后一个项目时,我们也监控循环并倒回帖子。这允许循环通过无限量的循环而不重置我们的查询。

  4. 有一种自定义方法不属于FilterIterator规范:deny()。这种方法特别方便,因为它只包含我们的“进程与否” – 语句,我们可以在以后的类中轻松覆盖它,而不需要知道除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() ) );
}

测试一下。

以下test plugin在GitHub上作为Gist提供。只需上传并激活它。它在loop_start操作上输出/转储每个循环帖子的ID作为回调。这意味着可能会获得相当多的输出,具体取决于您的设置,帖子数量和配置。请添加一些中止语句,并在最后将var_dump()更改为您想要查看的内容以及您想要查看的位置。这只是一个概念证明。

次佳解决方案

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

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

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

你也可以这样做:

$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是更好的方法来处理WP_Query可以处理必要逻辑的情况。

第三种解决方案

实际上有meta_query(或WP_Meta_Query) – 它采用一系列数组 – 您可以在其中搜索_thumbnail_id行。如果您然后检查EXISTS,则只能获得具有此字段的那些。将此与cat参数结合使用,您将只获得分配给ID为1且附有缩略图的类别的帖子。如果您通过meta_value_num订购它们,那么您实际上将按照从最低到最高的缩略图ID订购它们(如orderASC所述)。使用EXISTS作为compare值时,不必指定value

$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

当然,您可以更聪明,只需更改pre_get_posts中的SQL语句即可不浪费主查询。您也可以在pre_get_posts过滤器回调中简单地执行第一个查询(上面的$thumbsUp)。

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并直接按元值修改查询。看看this answer,因为当前的只是一个起点。

第四种方案

您需要的实际上是第三个查询,以立即获得所有帖子。然后,您将前两个查询更改为不返回帖子,而只更改您可以使用的格式的帖子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对象,其中包含您的帖子。

参考资料

本文由朵颐IT整理自网络, 文章地址: https://duoyit.com/article/2407.html,转载请务必附带本地址声明。