Як об'єднати два запити разом


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те, що дозволяє нам зручно вилучати елементи з нашого циклу. Він розширює ітератор фільтрів PHP SPL, тому нам не потрібно встановлювати все. Код добре прокоментований, але ось кілька зауважень:

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

Перевірте це.

Наступний тестовий плагін доступний як Gist on GitHub. Просто завантажте та активуйте його. Він виводить / скидає ідентифікатор кожного циклового повідомлення як зворотний виклик loop_startдії. Це означає, що ви можете отримати досить невеликий вихід, залежно від налаштувань, кількості повідомлень та конфігурації. Будь-ласка, додайте кілька заяв про переривання і змініть var_dump()s в кінці того, що ви хочете бачити і де ви хочете його бачити. Це просто доказ концепції.


6

Хоча це не найкращий спосіб вирішити цю проблему (відповідь @ кайзера є), щоб відповісти на питання безпосередньо, фактичні результати запитів будуть в, $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's є кращим підходом для таких випадків, коли WP_Queryможна обробляти необхідну логіку.


3

Насправді є meta_query(або WP_Meta_Query) - який займає масив масивів - де ви можете шукати _thumbnail_idрядки. Якщо потім перевірити EXISTS, ви зможете отримати лише ті, які мають це поле. Поєднуючи це з catаргументом, ви отримуватимете лише повідомлення, призначені до категорії з ідентифікатором 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',
) );

Тепер, перебираючи їх через них, ви можете зібрати всі ідентифікатори та використати їх у ексклюзивному заяві для допоміжного запиту:

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

Це змінило основний запит, тому ми отримуватимемо лише повідомлення, які додають ескіз. Тепер ми можемо (як показано на першому запиті вище) збирати ідентифікатори під час основного циклу, а потім додавати другий запит, який відображає решту публікацій (без ескізу).

Крім цього, ви можете отримати ще розумніші та змінити posts_clausesта змінити запит безпосередньо впорядковуючи мета-значення. Погляньте на цю відповідь, оскільки нинішня є лише відправною точкою.


3

Вам потрібен насправді третій запит, щоб отримати всі публікації одразу. Потім ви змінюєте свої перші два запити, щоб не повертати публікації, а лише ідентифікатори публікації у форматі, з яким ви можете працювати.

The '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 тепер будуть масивом номерів ідентифікаторів допису, тому ми їх об’єднуємо

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

Усуньте дублювані ідентифікаційні номери ...

$mypostids = array_unique( $mypostids );

Тепер зробіть запит, щоб отримати фактичні публікації у вказаному порядку:

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

Змінна $ loop тепер є об'єктом WP_Query з вашими публікаціями.


Дякую за це Встановлено, що це найменш складне рішення для збереження однієї структури циклу та нескладних обчислень сторінки.
Джей Нілі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.