¿Cómo usar WP_Query para consultar múltiples categorías con un límite de publicaciones por categoría?

25 ago 2010, 23:28:25
Vistas: 14.3K
Votos: 11

Tengo 3 categorías con 15 posts cada una, quiero hacer UNA sola consulta a la base de datos que traiga solo los primeros 5 posts de cada categoría, ¿cómo puedo hacerlo?

$q = new WP_Query(array( 'post__in' => array(2,4,8), 'posts_per_page' => **PRIMEROS_5_DE_CADA_CAT** ));

En caso de que no sea posible, ¿qué es más eficiente, obtener todos los posts de la categoría principal y recorrerlos o crear 3 consultas diferentes?

3
Comentarios

¿Cómo quieres que se ordenen?

MikeSchinkel MikeSchinkel
26 ago 2010 00:05:15

publicados recientemente... es decir, los 5 más recientes de la categoría A, lo más reciente de la categoría B, etc.

Amit Amit
26 ago 2010 00:09:41

Vale, mira mi respuesta a continuación

MikeSchinkel MikeSchinkel
26 ago 2010 02:12:36
Todas las respuestas a la pregunta 3
11
17

Lo que deseas es posible, pero requerirá que profundices en SQL, lo cual prefiero evitar siempre que sea posible (no porque no lo conozca, soy un desarrollador avanzado de SQL, sino porque en WordPress es mejor utilizar la API siempre que se pueda para minimizar futuros problemas de compatibilidad relacionados con posibles cambios en la estructura de la base de datos).

SQL con un operador UNION es una posibilidad

Para usar SQL, necesitas un operador UNION en tu consulta, algo como esto, asumiendo que los slugs de tus categorías son "category-1", "category-2" y "category-3":

SELECT * FROM wp_posts WHERE ID IN (
  SELECT tr.object_id
  FROM wp_terms t 
  INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
  INNER JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
  WHERE tt.taxonomy='category' AND t.slug='category-1'
  LIMIT 5

  UNION

  SELECT tr.object_id
  FROM wp_terms t 
  INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
  INNER JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
  WHERE tt.taxonomy='category' AND t.slug='category-2'
  LIMIT 5

  UNION

  SELECT tr.object_id
  FROM wp_terms t 
  INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
  INNER JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
  WHERE tt.taxonomy='category' AND t.slug='category-3'
  LIMIT 5
)

Puedes usar SQL UNION con un filtro posts_join

Usando lo anterior, puedes simplemente hacer la llamada directamente o puedes usar un gancho de filtro posts_join como se muestra a continuación; ten en cuenta que estoy usando un heredoc de PHP para que el SQL; quede alineado a la izquierda. También nota que usé una variable global para permitirte definir las categorías fuera del gancho, listando los slugs de categoría en un array. Puedes colocar este código en un plugin o en el archivo functions.php de tu tema:

<?php
global $top_5_for_each_category_join;
$top_5_for_each_category_join = array('category-1','category-2','category-3');
add_filter('posts_join','top_5_for_each_category_join',10,2);
function top_5_for_each_category_join($join,$query) {
  global $top_5_for_each_category_join;
  $unioned_selects = array();
  foreach($top_5_for_each_category_join as $category) {
    $unioned_selects[] =<<<SQL
SELECT object_id
FROM wp_terms t
INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
INNER JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
WHERE tt.taxonomy='category' AND t.slug='{$category}'
LIMIT 5
SQL;
  }
  $unioned_selects = implode("\n\nUNION\n\n",$unioned_selects);
  return $join . " INNER JOIN ($unioned_selects) categories ON wp_posts.ID=categories.object_id " ;
}

Pero puede haber efectos secundarios

Por supuesto, usar ganchos de modificación de consultas como posts_join siempre invita a efectos secundarios, ya que actúan globalmente en las consultas, por lo que generalmente necesitas envolver tus modificaciones en un if que solo las use cuando sea necesario, y los criterios para probar pueden ser complicados.

¿Enfocarse en la optimización en su lugar?

Sin embargo, supongo que tu pregunta está más preocupada por la optimización que por poder hacer una consulta de los 5 mejores por 3 categorías, ¿verdad? Si ese es el caso, tal vez haya otras opciones que usen la API de WordPress.

¿Mejor usar la API de transients para el almacenamiento en caché?

Supongo que tus posts no cambiarán con tanta frecuencia, ¿correcto? ¿Qué tal si aceptas el impacto de las tres (3) consultas periódicamente y luego almacenas en caché los resultados usando la API de transients? Obtendrás código mantenible y un gran rendimiento; un poco mejor que la consulta UNION anterior porque WordPress almacenará las listas de posts como un array serializado en un registro de la tabla wp_options.

Puedes tomar el siguiente ejemplo y colocarlo en la raíz de tu sitio web como test.php para probarlo:

<?php

$timeout = 4; // 4 horas
$categories = array('category-1','category-2','category-3');

include "wp-load.php";
$category_posts = get_transient('top5_posts_per_category');
if (!is_array($category_posts) || count($category_posts)==0) {
  echo "¡Hola cada {$timeout} horas!";
  $category_posts = array();
  foreach($categories as $category) {
    $posts = get_posts("post_type=post&numberposts=5&taxonomy=category&term={$category}");
    foreach($posts as $post) {
      $category_posts[$post->ID] = $post;
    }
  }
  set_transient('top5_posts_per_category',$category_posts,60*60*$timeout);
}
header('Content-Type:text/plain');
print_r($category_posts);

Resumen

Aunque sí, puedes hacer lo que pediste usando una consulta SQL UNION y el gancho de filtro posts_join, probablemente sea mejor usar el almacenamiento en caché con la API de transients.

¿Espero que esto ayude?

26 ago 2010 01:23:23
Comentarios

El almacenamiento en caché mediante transients es una excelente manera de reducir el impacto en el sitio.

hakre hakre
26 ago 2010 02:01:48

@Mike gran idea usar los Transients.

Chris_O Chris_O
26 ago 2010 03:41:30

@Mike, si viviera en tu continente, ¡habría tomado clases contigo! ¡gracias de nuevo!

Amit Amit
26 ago 2010 17:13:21

@Amit: ¡Jajaja! :-)

MikeSchinkel MikeSchinkel
26 ago 2010 21:21:14

¿No hay una manera de limitar el hook de join a solo un objeto WP_query?

Manny Fleurmond Manny Fleurmond
18 may 2011 01:15:51

@Manny Fleurmond - Normalmente digo que hagas otra pregunta (lo cual deberías hacer de todos modos) pero una forma es crear una subclase de WP_Query, añadir una propiedad y luego verificar esa propiedad.

MikeSchinkel MikeSchinkel
19 may 2011 19:44:34

¿podrías también guardar el transient indefinidamente y luego borrarlo en save_post? hay un buen ejemplo (usando el hook edit_term) en el codex

helgatheviking helgatheviking
27 feb 2012 19:22:33

@helgatheviking - Claro, si quieres.

MikeSchinkel MikeSchinkel
7 sept 2014 01:25:15

@MikeSchinkel 2 años después y ¡uso transients todo el tiempo!

helgatheviking helgatheviking
7 sept 2014 01:40:19

@helgatheviking ¿Mi respuesta te sirvió para empezar?!?

MikeSchinkel MikeSchinkel
7 sept 2014 03:27:31

@MikeSchinkel ¿Quizás? No podría decirlo con seguridad. Estás poniendo a prueba los límites de mi memoria. :)

helgatheviking helgatheviking
7 sept 2014 13:11:46
Mostrar los 6 comentarios restantes
4

WP_Query() no soporta algo como Primeros X Para Cada Categoría. En lugar de WP_Query() puedes usar get_posts() en cada una de tus categorías, es decir, tres veces:

<?php
$posts      = array();
$categories = array(2,4,8);
foreach($categories as $cat) {
  $posts[] = get_posts('numberposts=5&offset=1&category='.$cat);
}
?>

$posts ahora contiene los primeros cinco posts para cada categoría.

25 ago 2010 23:54:06
Comentarios

¿no son 3 consultas a la base de datos? Quería saber si se puede hacer en 1 consulta porque pensé que es más eficiente.

Amit Amit
25 ago 2010 23:56:36

bueno, para eso está la base de datos, ¿no? para consultar los datos. La base de datos está hecha para este tipo de cosas. Probablemente sea más rápido que ejecutar una consulta SQL complicada como la que pides. No temas usar estas herramientas. Si rompe tu sitio, repórtalo aquí.

hakre hakre
26 ago 2010 00:05:45

mm... entiendo, no sé mucho sobre eficiencia en php/mysql/bases de datos (me gustaría leer más), estaba seguro de que menos consultas es mejor y luego procesar el resultado yo mismo.

Amit Amit
26 ago 2010 00:11:26

bueno, es cierto, generalmente más es más y menos es menos. Pero primero prueba y comprueba. Cuanto "peor" sea tu software, más potencial tendrá para optimización. Así que mejor aprende haciendo, porque al final escribirás mejor software más rápido y podrás escribir software más rápido mejor.

hakre hakre
26 ago 2010 00:15:11
0

No conozco una forma de obtener las primeras cinco publicaciones para cada una de las categorías en una sola consulta. Si en algún momento solo vas a tener 45 publicaciones, entonces acceder a la base de datos una vez y obtener tus cinco publicaciones por categoría probablemente sea el enfoque más eficiente. Sin embargo, acceder a la base de datos tres veces y combinar los resultados no es algo malo.

25 ago 2010 23:38:26