Cómo combinar dos consultas juntas

10

Estoy intentando ordenar las publicaciones en una categoría mostrando primero las publicaciones con imágenes y luego las publicaciones sin imágenes. He logrado hacerlo ejecutando dos consultas y ahora quiero unir las dos consultas.

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é convertir array_merge en un objeto, pero obtuve el siguiente error:

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

¿Cómo puedo corregir este error?

    
pregunta Howli 16.01.2014 - 16:12

4 respuestas

8

Una sola consulta

Pensé un poco más en esto y existe la posibilidad de que puedas ir con una sola 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 uno predeterminado, no necesitará más que una sola consulta, sin importar cuántos bucles desee dividir la consulta.

Requisitos previos

Primero debe establecer (como se muestra en mi otra respuesta) los valores necesarios dentro de un filtro pre_get_posts . Allí es probable que establezca posts_per_page y cat . Ejemplo sin el 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 ) );

Construyendo una base

Lo siguiente que necesitamos es un pequeño complemento personalizado (o simplemente póngalo en su archivo functions.php si no le importa moverlo durante las actualizaciones o los 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 la PHP SPL (biblioteca estándar de PHP) y sus interfaces e iteradores. Lo que ahora tenemos es un FilterIterator que nos permite eliminar artículos 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 método accept() permite definir criterios que permiten hacer un bucle del elemento, o no.
  2. Dentro de ese método, usamos WP_Query::the_post() , así que simplemente puedes usar cada etiqueta de plantilla en tu bucle de archivos de plantilla.
  3. Y también estamos monitoreando el bucle y rebobinando las publicaciones cuando llegamos al último elemento. Esto permite realizar un bucle a través de una cantidad infinita de bucles sin restablecer nuestra consulta.
  4. Hay un método personalizado que no forma parte de las especificaciones FilterIterator : deny() . Este método es especialmente conveniente ya que solo contiene 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, ya no necesitamos if ( $customQuery->have_posts() ) y while ( $customQuery->have_posts() ) . Podemos ir con una simple declaración foreach ya que todas las comprobaciones 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 bucle foreach predeterminado. Incluso podemos eliminar the_post() y seguir utilizando todas las etiquetas de plantilla. El objeto global $post 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 método deny() y estará listo para su próximo ciclo. $this->current() siempre apuntará a nuestra publicación actual en bucle.

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

Como definimos que ahora deny() aplicamos un bucle a cada publicación que tiene una miniatura, entonces instantáneamente podemos enlazar 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árgalo y actívalo. Da salida / vuelca el ID de cada publicación en bucle como devolución de llamada en la acción loop_start . Esto significa que puede obtener bastante salida dependiendo de su configuración, número de publicaciones y configuración. Agregue algunas instrucciones 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.

    
respondido por el kaiser 16.01.2014 - 23:45
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->posts y $loop2->posts , así que ...

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

... debería funcionar, pero necesitarías usar un bucle foreach y no la estructura de bucle estándar basada en WP_Query , ya que la combinación de consultas así romperá los datos "meta" de objeto WP_Query 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 WP_Query puede manejar la lógica necesaria.

    
respondido por el s_ha_dum 16.01.2014 - 17:01
3

En realidad, hay meta_query (o WP_Meta_Query ), que toma una serie de matrices, donde puede buscar para las filas _thumbnail_id . Si luego verifica EXISTS , solo podrá obtener aquellos que tienen este campo. Combinando esto con el argumento cat , solo obtendrás publicaciones que estén asignadas a la categoría con el ID de 1 y que tengan una miniatura adjunta. Si luego los ordena por meta_value_num , en realidad los ordenará por la ID de la miniatura, de menor a mayor (como se indica con order y ASC ). No es necesario que especifique value cuando use EXISTS como valor de compare .

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

Ahora, cuando realiza un bucle a través de ellos, puede recopilar todos los ID y usarlos 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 puedes agregar tu segunda consulta. No hay necesidad de 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 modificar la declaración SQL dentro de pre_get_posts para no desperdiciar la consulta principal. También puede hacer la primera consulta ( $thumbsUp arriba) dentro de una devolución de llamada de pre_get_posts filter.

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 modificó 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 incluso más inteligente y alterar posts_clauses y modificar la consulta directamente por el valor meta. Echa un vistazo a esta respuesta ya que la actual es solo un punto de partida.

    
respondido por el kaiser 16.01.2014 - 17:00
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 parámetro 'fields'=>'ids' hará que una consulta 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 en su lugar.

Primero, obtenga los 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 );

Elimine 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 tus publicaciones en él.

    
respondido por el Otto 16.01.2014 - 20:30

Lea otras preguntas en las etiquetas