วิธีการรวมสองแบบสอบถามเข้าด้วยกัน


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

ฉันจะแก้ไขข้อผิดพลาดนี้ได้อย่างไร?

คำตอบ:


8

แบบสอบถามเดียว

คิดเกี่ยวกับเรื่องนี้อีกเล็กน้อยและมีโอกาสที่คุณจะสามารถไปกับคำค้นหาหลักเดียว / หรือกล่าวอีกนัยหนึ่ง: ไม่จำเป็นต้องมีสองข้อความค้นหาเพิ่มเติมเมื่อคุณสามารถทำงานกับคำสั่งเริ่มต้นได้ และในกรณีที่คุณไม่สามารถทำงานกับค่าเริ่มต้นได้คุณจะไม่ต้องการแบบสอบถามมากกว่าหนึ่งแบบสอบถามไม่ว่าคุณต้องการแยกลูปกี่ครั้ง

ข้อกำหนดเบื้องต้น

ก่อนอื่นคุณต้องตั้งค่า (ดังแสดงในคำตอบอื่น ๆ ของฉัน) ค่าที่ต้องการภายในpre_get_postsตัวกรอง มีคุณมีแนวโน้มที่จะตั้งค่าและposts_per_page catตัวอย่างที่ไม่มีตัวpre_get_postsกรอง:

$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ที่ช่วยให้เราสามารถลบรายการออกจากวงของเราได้อย่างสะดวก มันขยายตัวกรอง Iterator PHP SPL ดังนั้นเราไม่จำเป็นต้องตั้งค่าทุกอย่าง รหัสมีความเห็นดี แต่นี่เป็นบันทึกย่อบางส่วน:

  1. accept()วิธีการช่วยให้การกำหนดเกณฑ์ที่ช่วยให้บ่วงรายการ - หรือไม่
  2. ภายในวิธีการที่เราใช้WP_Query::the_post()ดังนั้นคุณสามารถใช้เทมเพลตแท็กทุกอันในลูปไฟล์เทมเพลตของคุณ
  3. และเช่นกันเรากำลังตรวจสอบลูปและกรอกลับโพสต์เมื่อเราไปถึงไอเท็มสุดท้าย วิธีนี้ทำให้สามารถวนซ้ำลูปได้ไม่ จำกัด โดยไม่ต้องรีเซ็ตเคียวรีของเรา
  4. มีวิธีการที่กำหนดเองหนึ่งที่ไม่ได้เป็นส่วนหนึ่งของการเป็นรายละเอียด:FilterIterator deny()วิธีนี้สะดวกมากเป็นพิเศษเนื่องจากมันมีเพียง "กระบวนการหรือไม่" ของเรา - สถานะและเราสามารถเขียนทับมันในคลาสที่ใหม่กว่าโดยไม่จำเป็นต้องรู้อะไรนอกเหนือจากแท็กแม่แบบ WordPress

วิธีการวนซ้ำ

ด้วย Iterator ใหม่นี้เราไม่จำเป็น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() ) );
}

ทดสอบมัน

ปลั๊กอินทดสอบต่อไปนี้มีให้เป็น Gist ใน GitHub เพียงอัปโหลดและเปิดใช้งาน มันส่งออก / ทิ้ง 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 เป็นวิธีที่ดีกว่าสำหรับกรณีเช่นนี้ที่WP_Queryสามารถจัดการกับตรรกะที่จำเป็น


3

จริงๆแล้วมีmeta_query(หรือWP_Meta_Query) - ซึ่งใช้อาร์เรย์อาร์เรย์ - ซึ่งคุณสามารถค้นหา_thumbnail_idแถว หากคุณตรวจสอบแล้วEXISTSคุณจะได้รับเฉพาะผู้ที่มีเขตข้อมูลนี้ เมื่อรวมกับcatอาร์กิวเมนต์คุณจะได้รับเฉพาะโพสต์ที่กำหนดให้กับหมวดหมู่ด้วย ID 1และมีรูปขนาดย่อที่แนบมา หากคุณสั่งซื้อสินค้าเหล่านั้นด้วยรหัสmeta_value_numจริงคุณจะสั่งซื้อสินค้าเหล่านั้นด้วยรหัสรูปขนาดย่อต่ำสุดถึงสูงสุด (ตามที่ระบุด้วยorderและASC) คุณไม่จำเป็นต้องระบุvalueเมื่อคุณใช้EXISTSเป็นcompareค่า

$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เพื่อไม่ให้สิ้นเปลืองคิวรีหลัก คุณสามารถทำแบบสอบถามแรก ( $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;
}

สิ่งนี้จะเปลี่ยนคำค้นหาหลักดังนั้นเราจะได้รับเฉพาะโพสต์ที่แนบรูปขนาดย่อเท่านั้น ตอนนี้เราสามารถ (ดังที่แสดงในเคียวรีที่ 1 ด้านบน) รวบรวม ID ในระหว่างลูปหลักแล้วเพิ่มเคียวรีที่สองที่แสดงส่วนที่เหลือของโพสต์ (ไม่มีภาพขนาดย่อ)

นอกเหนือจากนั้นคุณสามารถได้รับอย่างชาญฉลาดยิ่งขึ้นและแก้ไขposts_clausesและปรับเปลี่ยนแบบสอบถามโดยตรงสั่งโดยค่าเมตา ดูคำตอบนี้เนื่องจากคำตอบปัจจุบันเป็นเพียงจุดเริ่มต้น


3

สิ่งที่คุณต้องการจริง ๆ แล้วเป็นข้อความค้นหาที่สามเพื่อรับโพสต์ทั้งหมดในครั้งเดียว จากนั้นคุณเปลี่ยนคิวรีสองรายการแรกเพื่อไม่ให้ส่งคืนโพสต์ แต่เฉพาะรหัสโพสต์ในรูปแบบที่คุณสามารถใช้งานได้

'fields'=>'ids'พารามิเตอร์จะทำให้แบบสอบถามจริงกลับอาร์เรย์ของหมายเลขรหัสการจับคู่โพสต์ แต่เราไม่ต้องการวัตถุแบบสอบถามทั้งหมดดังนั้นเราจึงใช้ get_posts แทนสิ่งเหล่านี้

ก่อนอื่นรับรหัสโพสต์ที่เราต้องการ:

$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 พร้อมกับโพสต์ของคุณ


ขอบคุณสำหรับสิ่งนี้. พบว่านี่เป็นวิธีแก้ปัญหาที่ซับซ้อนน้อยที่สุดสำหรับการรักษาโครงสร้างลูปเดียวและการคำนวณการแบ่งหน้าแบบไม่ซับซ้อน
Jay Neely
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.