Jak połączyć dwa zapytania razem

10

Próbuję uporządkować posty w kategorii, pokazując najpierw posty ze zdjęciami, a następnie posty bez obrazów jako ostatnie. Udało mi się to zrobić, uruchamiając dwa zapytania, a teraz chcę je połączyć.

Mam następujące:

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

Ale kiedy próbuję wyświetlić stronę, pojawia się następujący błąd:

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

Następnie próbowałem rzutować array_merge na obiekt, ale otrzymałem następujący błąd:

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

Jak mogę naprawić ten błąd?

Howli
źródło

Odpowiedzi:

8

Jedno zapytanie

Zastanowiłem się nad tym trochę więcej i istnieje szansa, że ​​możesz przejść do pojedynczego / głównego zapytania. Lub innymi słowy: Nie trzeba dwóch dodatkowych zapytań, gdy można pracować z domyślnym. A jeśli nie możesz pracować z domyślnym, nie potrzebujesz więcej niż jednego zapytania, bez względu na to, ile pętli chcesz podzielić.

Wymagania wstępne

Najpierw musisz ustawić (jak pokazano w mojej innej odpowiedzi) potrzebne wartości w pre_get_postsfiltrze. Tam prawdopodobnie ustawisz posts_per_pagei cat. Przykład bez pre_get_postsfiltru:

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

Budowanie bazy

Następna rzecz, której potrzebujemy, to mała niestandardowa wtyczka (lub po prostu umieść ją w functions.phppliku, jeśli nie masz nic przeciwko przenoszeniu jej podczas aktualizacji lub zmian motywu):

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

Ta wtyczka robi jedną rzecz: wykorzystuje PHP SPL (Standardową bibliotekę PHP) oraz jej interfejsy i iteratory. Teraz mamy to, FilterIteratorco pozwala nam wygodnie usuwać przedmioty z naszej pętli. Rozszerza PHP SPL Filter Iterator, więc nie musimy ustawiać wszystkiego. Kod jest dobrze skomentowany, ale oto kilka uwag:

  1. accept()Metoda pozwala określić kryteria, które pozwalają na pętli element - czy nie.
  2. Wewnątrz tej metody używamy WP_Query::the_post(), więc możesz po prostu użyć każdego znacznika szablonu w pętli plików szablonów.
  3. Monitorujemy również pętlę i przewijamy posty po dotarciu do ostatniego elementu. Pozwala to na przechodzenie przez nieskończoną liczbę pętli bez resetowania naszego zapytania.
  4. Jest jeden sposób niestandardowy, który nie jest częścią FilterIteratorspecyfikacji: deny(). Ta metoda jest szczególnie wygodna, ponieważ zawiera tylko naszą informację „przetwarzaj lub nie” i możemy łatwo zastąpić ją w późniejszych klasach bez konieczności poznawania czegokolwiek poza tagami szablonów WordPress.

Jak zapętlić?

Dzięki tej nowej Iterator, nie potrzebujemy if ( $customQuery->have_posts() )i while ( $customQuery->have_posts() )już. Możemy zastosować proste foreachoświadczenie, ponieważ wszystkie potrzebne kontrole są już dla nas wykonane. Przykład:

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

Wreszcie nie potrzebujemy nic więcej niż domyślnej foreachpętli. Możemy nawet upuścić the_post()i nadal używać wszystkich tagów szablonów. Obiekt globalny $postzawsze będzie zsynchronizowany.

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

Pętle pomocnicze

Zaletą jest to, że każdy późniejszy filtr zapytań jest dość łatwy w obsłudze: wystarczy zdefiniować deny()metodę i możesz przejść do następnej pętli. $this->current()zawsze będzie wskazywać nasz aktualnie zapętlony post.

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

Ponieważ zdefiniowaliśmy, że deny()zapętlamy teraz każdy post z miniaturą, możemy natychmiast zapętlić wszystkie posty bez miniatury:

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

Sprawdź to.

Poniższa wtyczka testowa jest dostępna jako Gist w GitHub. Wystarczy go przesłać i aktywować. Wysyła / zrzuca identyfikator każdego zapętlonego postu jako wywołanie zwrotne dla loop_startakcji. Oznacza to, że może uzyskać całkiem sporo danych wyjściowych w zależności od konfiguracji, liczby postów i konfiguracji. Dodaj kilka instrukcji przerywania i zmień var_dump()s na końcu tego, co chcesz zobaczyć i gdzie chcesz to zobaczyć. To tylko dowód koncepcji.

kajzer
źródło
6

Choć nie jest to najlepszy sposób na rozwiązanie tego problemu (@ odpowiedź Kaisera jest), aby odpowiedzieć na pytanie bezpośrednio, rzeczywiste wyniki kwerendy będzie $loop->postsi $loop2->poststak ...

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

... powinno działać, ale musisz użyć foreachpętli, a nie WP_Queryopartej na standardowej strukturze pętli, ponieważ scalanie takich zapytań spowoduje WP_Queryuszkodzenie danych meta obiektu o pętli.

Możesz także to zrobić:

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

Oczywiście rozwiązania te reprezentują wiele zapytań, dlatego @ Kaiser's jest lepszym podejściem do takich przypadków, w których WP_Querymożna obsłużyć niezbędną logikę.

s_ha_dum
źródło
3

W rzeczywistości istnieje meta_query(lub WP_Meta_Query) - który zajmuje tablicę tablic - gdzie można wyszukiwać _thumbnail_idwiersze. Jeśli następnie sprawdzisz EXISTS, możesz uzyskać tylko te, które mają to pole. Łącząc to z catargumentem, otrzymujesz tylko posty przypisane do kategorii o identyfikatorze 1i z dołączoną miniaturą. Jeśli następnie zamówisz je według meta_value_num, to faktycznie zamówisz je według identyfikatora miniatury od najniższej do najwyższej (jak podano za pomocą orderi ASC). Nie trzeba, aby określić value, kiedy użyć EXISTSjako comparewartości.

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

Teraz podczas ich zapętlania możesz zebrać wszystkie identyfikatory i użyć ich w wyłącznej instrukcji dla zapytania pomocniczego:

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

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

        // do display/rendering stuff here
    }
}

Teraz możesz dodać swoje drugie zapytanie. Nie ma wp_reset_postdata()tu potrzeby - wszystko jest w zmiennej, a nie w głównym zapytaniu.

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

Oczywiście możesz być znacznie mądrzejszy i po prostu zmienić instrukcję SQL, pre_get_postsaby nie marnować głównego zapytania. Równie dobrze możesz po prostu wykonać pierwsze zapytanie ( $thumbsUppowyżej) w pre_get_postswywołaniu zwrotnym filtra.

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

To zmieniło główne zapytanie, więc otrzymamy tylko posty z dołączoną miniaturą. Teraz możemy (jak pokazano w pierwszym zapytaniu powyżej) zebrać identyfikatory podczas głównej pętli, a następnie dodać drugie zapytanie, które wyświetla resztę postów (bez miniatury).

Oprócz tego możesz stać się jeszcze mądrzejszy oraz modyfikować posts_clausesi modyfikować zapytanie bezpośrednio zamawiając według wartości meta. Spójrz na tę odpowiedź, ponieważ obecna jest tylko punktem wyjścia.

kajzer
źródło
3

W rzeczywistości potrzebujesz trzeciego zapytania, aby uzyskać wszystkie posty naraz. Następnie zmienisz pierwsze dwa zapytania, aby nie zwracały postów, a jedynie identyfikatory postów w formacie, z którym możesz pracować.

Ten 'fields'=>'ids'parametr spowoduje, że zapytanie faktycznie zwróci tablicę pasujących numerów identyfikacyjnych postów. Ale nie chcemy całego obiektu zapytania, dlatego zamiast tego używamy get_posts.

Najpierw uzyskaj potrzebne nam identyfikatory postów:

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

$ imageposts i $ nonimageposts będą teraz zarówno tablicą numerów identyfikacyjnych postów, więc łączymy je

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

Wyeliminuj zduplikowane numery identyfikacyjne ...

$mypostids = array_unique( $mypostids );

Teraz zrób zapytanie, aby uzyskać rzeczywiste posty w podanej kolejności:

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

Zmienna $ loop jest teraz obiektem WP_Query z twoimi wpisami.

Otto
źródło
Dzięki za to. Okazało się, że jest to najmniej skomplikowane rozwiązanie do utrzymania struktury jednej pętli i nieskomplikowanych obliczeń paginacji.
Jay Neely,