Cách hợp nhất hai truy vấn với nhau


10

Tôi đang cố gắng sắp xếp các bài đăng trong một danh mục bằng cách hiển thị các bài đăng có hình ảnh trước và sau đó bài viết không có hình ảnh cuối cùng. Tôi đã quản lý để làm điều đó bằng cách chạy hai truy vấn và bây giờ tôi muốn hợp nhất hai truy vấn lại với nhau.

Tôi có những điều sau đây:

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

Nhưng khi tôi thử và xem trang thì tôi gặp lỗi sau:

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

Sau đó, tôi đã thử truyền mảng_merge vào một đối tượng, nhưng tôi đã gặp lỗi sau:

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

Làm thế nào tôi có thể sửa lỗi này?

Câu trả lời:


8

Một truy vấn duy nhất

Nghĩ về điều này nhiều hơn một chút và có một cơ hội mà bạn có thể đi với một truy vấn duy nhất / chính. Hay nói cách khác: Không cần hai truy vấn bổ sung khi bạn có thể làm việc với truy vấn mặc định. Và trong trường hợp bạn không thể làm việc với một mặc định, bạn sẽ không cần nhiều hơn một truy vấn duy nhất cho dù bạn muốn chia bao nhiêu vòng truy vấn.

Điều kiện tiên quyết

Trước tiên, bạn cần đặt (như thể hiện trong câu trả lời khác của tôi) các giá trị cần thiết bên trong pre_get_postsbộ lọc. Có khả năng bạn sẽ thiết lập posts_per_pagecat. Ví dụ không có 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 ) );

Xây dựng căn cứ

Điều tiếp theo chúng tôi cần là một plugin tùy chỉnh nhỏ (hoặc chỉ đưa nó vào functions.phptệp của bạn nếu bạn không ngại di chuyển nó trong khi cập nhật hoặc thay đổi chủ đề):

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

Plugin này thực hiện một điều: Nó sử dụng PHP SPL (Thư viện PHP chuẩn) và các Giao diện và Trình lặp của nó. Những gì chúng ta có bây giờ là một FilterIteratorthứ cho phép chúng ta thuận tiện loại bỏ các mục khỏi vòng lặp của chúng ta. Nó mở rộng Bộ lặp Bộ lọc SPL PHP để chúng ta không phải thiết lập mọi thứ. Mã được nhận xét tốt, nhưng đây là một số lưu ý:

  1. Các accept()phương pháp cho phép xác định tiêu chí cho phép lặp mục - hay không.
  2. Bên trong phương pháp đó chúng tôi sử dụng WP_Query::the_post(), vì vậy bạn chỉ cần sử dụng mọi thẻ mẫu trong vòng lặp tệp mẫu của mình.
  3. Và chúng tôi cũng đang theo dõi vòng lặp và tua lại các bài đăng khi chúng tôi đến mục cuối cùng. Điều này cho phép lặp lại một số lượng vòng lặp vô hạn mà không cần đặt lại truy vấn của chúng tôi.
  4. Có một phương pháp tùy chỉnh không phải là một phần của FilterIteratorthông số kỹ thuật : deny(). Phương pháp này đặc biệt thuận tiện vì nó chỉ chứa "quá trình hoặc không" của chúng tôi và chúng tôi có thể dễ dàng ghi đè lên nó trong các lớp sau mà không cần biết gì ngoài các thẻ mẫu WordPress.

Làm thế nào để lặp?

Với Iterator mới này, chúng ta không cần if ( $customQuery->have_posts() )while ( $customQuery->have_posts() )nữa. Chúng tôi có thể đi với một foreachtuyên bố đơn giản vì tất cả các kiểm tra cần thiết đã được thực hiện cho chúng tôi. Thí dụ:

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

Cuối cùng, chúng ta không cần gì hơn một foreachvòng lặp mặc định . Chúng tôi thậm chí có thể thả the_post()và vẫn sử dụng tất cả các thẻ mẫu. Các $postđối tượng toàn cầu sẽ luôn luôn đồng bộ.

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

Các công ty con

Bây giờ, điều tuyệt vời là mọi bộ lọc truy vấn sau này khá dễ xử lý: Chỉ cần xác định deny()phương thức và bạn đã sẵn sàng cho vòng lặp tiếp theo của mình. $this->current()sẽ luôn luôn trỏ đến bài viết hiện đang lặp của chúng tôi.

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

Như chúng tôi đã xác định rằng bây giờ chúng tôi deny()lặp mỗi bài đăng có hình thu nhỏ, sau đó chúng tôi có thể lập tức lặp lại tất cả các bài đăng mà không cần hình thu nhỏ:

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

Kiểm tra nó

Plugin kiểm tra sau đây có sẵn dưới dạng Gist trên GitHub. Đơn giản chỉ cần tải lên và kích hoạt nó. Nó xuất / bỏ ID của mỗi bài đăng được lặp lại dưới dạng gọi lại trên loop_starthành động. Điều này có nghĩa là có thể nhận được đầu ra khá ít tùy thuộc vào thiết lập, số lượng bài đăng và cấu hình của bạn. Vui lòng thêm một số tuyên bố hủy bỏ và thay đổi var_dump()s ở cuối thành những gì bạn muốn xem và nơi bạn muốn xem nó. Nó chỉ là một bằng chứng về khái niệm.


6

Mặc dù đây không phải là cách tốt nhất để giải quyết vấn đề này (câu trả lời của @ kaiser là), để trả lời trực tiếp câu hỏi, kết quả truy vấn thực tế sẽ nằm trong $loop->posts$loop2->posts, vì vậy ...

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

... sẽ hoạt động, nhưng bạn cần sử dụng một foreachvòng lặp chứ không phải WP_Querycấu trúc vòng lặp tiêu chuẩn dựa trên việc hợp nhất các truy vấn như thế sẽ phá vỡ WP_Querydữ liệu "meta" của đối tượng về vòng lặp.

Bạn cũng có thể làm điều này:

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

Tất nhiên, những giải pháp đó đại diện cho nhiều truy vấn, đó là lý do tại sao @ Kaiser là cách tiếp cận tốt hơn cho các trường hợp như thế này, nơi WP_Querycó thể xử lý logic cần thiết.


3

Trên thực tế có meta_query(hoặc WP_Meta_Query) - trong đó có một mảng các mảng - nơi bạn có thể tìm kiếm các _thumbnail_idhàng. Nếu sau đó bạn kiểm tra EXISTS, bạn chỉ có thể nhận được những người có trường này. Kết hợp điều này với catđối số, bạn sẽ chỉ nhận được các bài đăng được gán cho danh mục có ID của 1và có hình thu nhỏ được đính kèm. Nếu sau đó bạn đặt hàng chúng theo meta_value_num, thì thực tế bạn sẽ đặt chúng theo ID hình thu nhỏ từ thấp nhất đến cao nhất (như đã nêu orderASC). Bạn không phải chỉ định thời valueđiểm bạn sử dụng EXISTSlàm comparegiá trị.

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

Bây giờ khi lặp qua máng chúng, bạn có thể thu thập tất cả các ID và sử dụng chúng trong một câu lệnh độc quyền cho truy vấn phụ:

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

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

        // do display/rendering stuff here
    }
}

Bây giờ bạn có thể thêm truy vấn thứ hai của bạn. Không cần wp_reset_postdata()ở đây - mọi thứ đều nằm trong biến và không phải là truy vấn chính.

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

Tất nhiên bạn có thể thông minh hơn nhiều và chỉ cần thay đổi câu lệnh SQL bên trong pre_get_postsđể không lãng phí truy vấn chính. Bạn cũng có thể chỉ cần thực hiện truy vấn đầu tiên ( $thumbsUpở trên) bên trong một pre_get_postscuộc gọi lại bộ lọc.

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

Điều này đã thay đổi truy vấn chính, vì vậy chúng tôi sẽ chỉ nhận được các bài đăng có hình thu nhỏ được đính kèm. Bây giờ chúng ta có thể (như được hiển thị trong truy vấn thứ 1 ở trên) thu thập ID trong vòng lặp chính và sau đó thêm truy vấn thứ hai hiển thị phần còn lại của bài đăng (không có hình thu nhỏ).

Ngoài ra, bạn có thể nhận được thậm chí thông minh hơn và thay đổi posts_clausesvà sửa đổi thứ tự truy vấn trực tiếp theo giá trị meta. Hãy xem câu trả lời nàycâu hỏi hiện tại chỉ là điểm khởi đầu.


3

Những gì bạn cần thực sự là một truy vấn thứ ba để có được tất cả các bài viết cùng một lúc. Sau đó, bạn thay đổi hai truy vấn đầu tiên của mình để không trả lại các bài đăng, mà chỉ các ID bài đăng ở định dạng mà bạn có thể làm việc.

Các 'fields'=>'ids'tham số sẽ thực hiện một truy vấn thực sự trả về một mảng của phù hợp với số ID bài. Nhưng chúng tôi không muốn toàn bộ đối tượng truy vấn, vì vậy chúng tôi sử dụng get_posts cho những đối tượng này thay thế.

Đầu tiên, nhận ID bài đăng chúng tôi cần:

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

$ imageposts và $ nonimageposts bây giờ sẽ là một mảng các số ID bài đăng, vì vậy chúng tôi hợp nhất chúng

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

Loại bỏ số ID trùng lặp ...

$mypostids = array_unique( $mypostids );

Bây giờ, tạo một truy vấn để có được các bài viết thực tế theo thứ tự được chỉ định:

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

Biến vòng lặp $ bây giờ là một đối tượng WP_Query có các bài đăng của bạn trong đó.


Cảm ơn vì điều đó. Tìm thấy đây là giải pháp ít phức tạp nhất để giữ cấu trúc một vòng lặp và tính toán phân trang không phức tạp.
Jay Neely
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.