2つのクエリをマージする方法


10

最初に画像付きの投稿を表示し、次に画像なしの投稿を最後に表示することで、カテゴリ内の投稿を並べようとしています。2つのクエリを実行してなんとかできましたが、2つのクエリをマージしたいと思います。

私は以下を持っています:

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

このエラーを修正するにはどうすればよいですか?

回答:


8

単一のクエリ

これについてもう少し考えてみてください。単一またはメインのクエリで実行できる可能性があります。または言い換えると、デフォルトのクエリで作業できる場合、追加の2つのクエリは必要ありません。また、デフォルトのクエリを使用できない場合は、クエリを分割するループの数に関係なく、クエリは1つしか必要ありません。

前提条件

最初に、(他の回答で示されているように)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;
    }
}

このプラグインは1つのことを行います。それは、PHP SPL(標準PHPライブラリー)とそのインターフェースおよびイテレーターを利用します。これでFilterIterator、ループからアイテムを簡単に削除できるようになりました。PHP SPL Filter Iteratorを拡張するため、すべてを設定する必要はありません。コードはよくコメントされていますが、ここにいくつかのメモがあります:

  1. このaccept()メソッドでは、アイテムのループを許可する基準を定義できます。
  2. そのメソッド内ではを使用WP_Query::the_post()しているため、テンプレートファイルループ内のすべてのテンプレートタグを使用できます。
  3. また、ループを監視し、最後のアイテムに到達したときに投稿を巻き戻します。これにより、クエリをリセットすることなく、無限ループをループできます。
  4. FilterIterator仕様に含まれていないカスタムメソッドが1つあります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() ) );
}

試して。

次のテストプラグインはGitHubのGistとして入手できます。アップロードしてアクティブ化するだけです。ループされたすべての投稿のIDをloop_startアクションのコールバックとして出力/ダンプします。つまり、設定、投稿の数、設定によっては、かなりの出力が得られる可能性があります。いくつかの中止ステートメントを追加var_dump()し、最後のsを、見たいものと見たい場所に変更してください。これは単なる概念実証です。


6

これはこの問題を解決する最良の方法ではありませんが(@kaiserの答えは)、質問に直接答える場合、実際のクエリ結果は$loop->postsand $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'));

もちろん、これらのソリューションは複数のクエリを表していますWP_Query。そのため、このような場合に必要なロジックを処理できる@Kaiserの方が優れたアプローチです。


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
    }
}

これで、2番目のクエリを追加できます。wp_reset_postdata()ここでは必要ありません。すべてが変数であり、メインのクエリではありません。

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

もちろん、はるかに賢くpre_get_posts、メインのクエリを無駄にしないように内部のSQLステートメントを変更することもできます。フィルターコールバック$thumbsUp内で最初のクエリ(上記)を実行することもできpre_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を収集し、残りの投稿を表示する2番目のクエリを追加できます(サムネイルなし)。

それとは別に、さらにスマートになりposts_clauses、メタ値によってクエリを直接変更して変更することができます。現在の答えは出発点にすぎないので、この答えを見てください。


3

実際に必要なのは、すべての投稿を一度に取得するための3番目のクエリです。次に、最初の2つのクエリを変更して、投稿を返さず、操作できる形式の投稿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オブジェクトになりました。


これをありがとう。これは、1つのループ構造と複雑でないページ付け計算を維持するための最も複雑でないソリューションであることがわかりました。
Jay Neely、2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.