Cómo fusionar dos consultas juntas

10

Estoy tratando de ordenar las publicaciones en una categoría mostrando primero las publicaciones con imágenes y luego las publicaciones sin imágenes. Logré hacerlo ejecutando dos consultas y ahora quiero fusionar las dos consultas juntas.

Tengo lo siguiente:

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

Pero cuando intento ver la página, aparece el siguiente error:

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

Luego intenté enviar array_merge a un objeto, pero recibí el siguiente error:

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

¿Cómo puedo solucionar este error?

Howli
fuente

Respuestas:

8

Una sola consulta

Pensé en esto un poco más y existe la posibilidad de que pueda ir con una sola / la consulta principal. O en otras palabras: no es necesario realizar dos consultas adicionales cuando puede trabajar con la predeterminada. Y en caso de que no pueda trabajar con una predeterminada, no necesitará más de una consulta, sin importar cuántos bucles desee dividir la consulta.

Prerrequisitos

Primero debe establecer (como se muestra en mi otra respuesta) los valores necesarios dentro de un pre_get_postsfiltro. Allí probablemente establecerás posts_per_pagey cat. Ejemplo sin el pre_get_posts-Filtro:

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

Construyendo una base

Lo siguiente que necesitamos es un pequeño complemento personalizado (o simplemente póngalo en su functions.phparchivo si no le importa moverlo durante las actualizaciones o cambios de tema):

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

Este complemento hace una cosa: utiliza PHP SPL (Biblioteca PHP estándar) y sus interfaces e iteradores. Lo que ahora tenemos es un FilterIteratorque nos permite eliminar convenientemente elementos de nuestro bucle. Extiende el iterador de filtro PHP SPL para que no tengamos que configurar todo. El código está bien comentado, pero aquí hay algunas notas:

  1. El accept()método permite definir criterios que permiten recorrer el ítem, o no.
  2. Dentro de ese método que usamos WP_Query::the_post(), puede simplemente usar cada etiqueta de plantilla en su bucle de archivos de plantilla.
  3. Y también estamos monitoreando el ciclo y rebobinando las publicaciones cuando llegamos al último elemento. Esto permite recorrer una cantidad infinita de bucles sin restablecer nuestra consulta.
  4. Hay un método personalizado que no es parte de las FilterIteratorespecificaciones: deny(). Este método es especialmente conveniente ya que contiene solo nuestra declaración "proceso o no" y podemos sobrescribirlo fácilmente en clases posteriores sin necesidad de saber nada aparte de las etiquetas de plantilla de WordPress.

¿Cómo hacer un bucle?

Con este nuevo iterador, que no necesitamos if ( $customQuery->have_posts() )y while ( $customQuery->have_posts() )más. Podemos ir con una foreachdeclaración simple ya que todas las verificaciones necesarias ya están hechas para nosotros. Ejemplo:

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

Finalmente, no necesitamos nada más que un foreachbucle predeterminado . Incluso podemos soltar the_post()y seguir usando todas las etiquetas de plantilla. El $postobjeto global siempre permanecerá sincronizado.

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

Bucles subsidiarios

Ahora lo bueno es que cada filtro de consulta posterior es bastante fácil de manejar: simplemente defina el deny()método y estará listo para su próximo ciclo. $this->current()siempre apuntará a nuestra publicación en bucle actual.

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

Como definimos que ahora hacemos un deny()bucle en cada publicación que tiene una miniatura, podemos instantáneamente hacer un bucle en todas las publicaciones sin una miniatura:

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

Pruébalo.

El siguiente complemento de prueba está disponible como Gist en GitHub. Simplemente cárguelo y actívelo. Produce / vuelca la ID de cada publicación en bucle como devolución de llamada en la loop_startacción. Esto significa que podría obtener bastante salida dependiendo de su configuración, número de publicaciones y configuración. Agregue algunas declaraciones de cancelación y modifique el var_dump()s al final de lo que desea ver y dónde desea verlo. Es solo una prueba de concepto.

emperador
fuente
6

Si bien esta no es la mejor manera de resolver este problema (la respuesta de @ kaiser es), para responder la pregunta directamente, los resultados reales de la consulta estarán en $loop->postsy $loop2->posts, entonces ...

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

... debería funcionar, pero necesitaría usar un foreachbucle y no la WP_Queryestructura de bucle estándar basada, ya que la fusión de consultas como esa romperá los WP_Querydatos "meta" del objeto sobre el bucle.

También puedes hacer esto:

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

Por supuesto, esas soluciones representan múltiples consultas, por lo que @ Kaiser es el mejor enfoque para casos como este donde se WP_Querypuede manejar la lógica necesaria.

s_ha_dum
fuente
3

En realidad, hay meta_query(o WP_Meta_Query), que toma una matriz de matrices, donde puede buscar las _thumbnail_idfilas. Si luego verifica EXISTS, solo puede obtener aquellos que tienen este campo. Combinando esto con el catargumento, solo obtendrá publicaciones asignadas a la categoría con la ID de 1y que tienen una miniatura adjunta. Si luego los ordena por meta_value_num, entonces los ordenará por la ID de miniatura de menor a mayor (como se indica con ordery ASC). No tiene que especificar valuecuándo usarlo EXISTScomo comparevalor.

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

Ahora, al recorrerlos, puede recopilar todas las ID y usarlas en una declaración exclusiva para la consulta subsidiaria:

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

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

        // do display/rendering stuff here
    }
}

Ahora puede agregar su segunda consulta. No es necesario wp_reset_postdata()aquí: todo está en la variable y no en la consulta principal.

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

Por supuesto, puede ser mucho más inteligente y simplemente alterar la declaración SQL dentro pre_get_postspara no desperdiciar la consulta principal. También podría simplemente hacer la primera consulta ( $thumbsUparriba) dentro de una pre_get_postsdevolución de llamada de filtro.

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

Esto alteró la consulta principal, por lo que solo obtendremos publicaciones que tengan una miniatura adjunta. Ahora podemos (como se muestra en la primera consulta anterior) recopilar los ID durante el ciclo principal y luego agregar una segunda consulta que muestre el resto de las publicaciones (sin una miniatura).

Aparte de eso, puede ser aún más inteligente y alterar posts_clausesy modificar la consulta directamente ordenada por el metavalor. Eche un vistazo a esta respuesta, ya que la actual es solo un punto de partida.

emperador
fuente
3

Lo que necesitas es en realidad una tercera consulta para obtener todas las publicaciones a la vez. Luego, cambia las dos primeras consultas para no devolver las publicaciones, sino solo las ID de las publicaciones en un formato con el que pueda trabajar.

El 'fields'=>'ids'parámetro hará que una consulta realmente devuelva una matriz de números de ID de publicación coincidentes. Pero no queremos el objeto de consulta completo, por lo que usamos get_posts para estos.

Primero, obtenga las ID de publicación que necesitamos:

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

$ imageposts y $ nonimageposts ahora serán una serie de números de ID de publicación, por lo que los fusionamos

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

Eliminar los números de identificación duplicados ...

$mypostids = array_unique( $mypostids );

Ahora, haga una consulta para obtener las publicaciones reales en el orden especificado:

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

La variable $ loop ahora es un objeto WP_Query con sus publicaciones.

Otón
fuente
Gracias por esto. Encontramos que esta es la solución menos complicada para mantener una estructura de bucle y cálculos de paginación sin complicaciones.
Jay Neely