Come utilizzare WP_Query per interrogare più categorie con un numero limitato di post per categoria?
Ho 3 categorie con 15 post ciascuna, voglio fare UN'unica query al database che mi restituisca solo i primi 5 post per ogni categoria, come posso farlo?
$q = new WP_Query(array( 'post__in' => array(2,4,8), 'posts_per_page' => **PRIMI_5_PER_CIASCUNA_CAT** ));
Se non fosse possibile, cosa è più efficiente: ottenere tutti i post della categoria genitore e scorrerli oppure creare 3 query separate?

Quello che vuoi ottenere è possibile ma richiederà di approfondire SQL, che preferisco evitare quando possibile (non perché non lo conosca, sono uno sviluppatore SQL avanzato, ma perché in WordPress è meglio utilizzare le API quando possibile per minimizzare futuri problemi di compatibilità legati a potenziali cambiamenti nella struttura del database).
SQL con un operatore UNION
è una possibilità
Per usare SQL ti servirà un operatore UNION
nella tua query, qualcosa come questo assumendo che gli slug delle tue categorie siano "category-1"
, "category-1"
e "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
)
Puoi usare SQL UNION con un filtro posts_join
Usando il codice sopra puoi fare la chiamata direttamente oppure puoi usare un hook filter posts_join
come segue; nota che sto usando un PHP heredoc quindi assicurati che SQL;
sia allineato a sinistra. Inoltre ho usato una variabile globale per permetterti di definire le categorie al di fuori dell'hook elencando gli slug delle categorie in un array. Puoi inserire questo codice in un plugin o nel file functions.php
del tuo 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 " ;
}
Ma potrebbero esserci effetti collaterali
Ovviamente usare gli hook di modifica delle query come posts_join
può avere effetti collaterali dato che agiscono globalmente sulle query, quindi di solito è necessario racchiudere le modifiche in un if
che le applica solo quando necessario, e determinare i criteri da testare può essere complicato.
Meglio concentrarsi sull'ottimizzazione?
Tuttavia, immagino che la tua domanda sia più legata all'ottimizzazione che alla possibilità di fare una query per i primi 5 articoli per 3 categorie, giusto? Se è così, forse ci sono altre opzioni che utilizzano le API di WordPress?
Meglio usare la Transients API per la cache?
Suppongo che i tuoi articoli non cambino così spesso, corretto? E se accettassi il costo delle tre (3) query periodicamente e poi memorizzassi i risultati usando la Transients API? Avresti codice mantenibile e ottime prestazioni; addirittura migliori rispetto alla query UNION
perché WordPress memorizzerà gli elenchi di articoli come un array serializzato in un singolo record della tabella wp_options
.
Puoi prendere il seguente esempio e salvarlo nella root del tuo sito come test.php
per testarlo:
<?php
$timeout = 4; // 4 ore
$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 "Hello Every {$timeout} hours!";
$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);
Riepilogo
Sebbene tu possa fare ciò che hai chiesto usando una query SQL UNION
e l'hook filter posts_join
, probabilmente è meglio utilizzare la cache con la Transients API.
Spero sia utile?

La memorizzazione nella cache tramite transient è un'ottima cosa per ridurre l'impatto sul sito.

@Mike, se vivessi nel tuo continente avrei seguito le tue lezioni! grazie ancora!

Non esiste un modo per limitare l'hook join a un solo oggetto WP_query?

@Manny Fleurmond - Di solito dico di fare un'altra domanda (cosa che dovresti fare comunque) ma un modo è creare una sottoclasse di WP_Query
, aggiungere una proprietà e poi verificare quella proprietà.

potresti anche salvare il transient indefinitamente e poi svuotarlo su save_post? c'è un buon esempio (usando l'hook edit_term) nel codex

WP_Query()
non supporta qualcosa come Primi X Per Ogni Categoria. Invece di WP_Query()
puoi usare get_posts()
su ognuna delle tue categorie, diciamo tre volte:
<?php
$posts = array();
$categories = array(2,4,8);
foreach($categories as $cat) {
$posts[] = get_posts('numberposts=5&offset=1&category='.$cat);
}
?>
$posts
ora contiene i primi cinque post per ogni categoria.

non sono 3 accessi al database? Volevo sapere se puoi farlo in un solo accesso perché pensavo fosse più efficiente.

ma è proprio a questo che serve il database, no? Interrogare i dati da esso. Il database è fatto per questo tipo di operazioni. Probabilmente è più veloce che eseguire una complicata query SQL come quella che stai chiedendo. Non aver paura di usare questi strumenti. Se rompe il tuo sito, segnalalo qui.

mm.. capisco, non so molto sull'efficienza di php/mysql/database (mi piacerebbe leggere di più), ero sicuro che meno accessi fossero meglio e poi elaborare il risultato da solo.

beh, vero, generalmente più è meglio e meno è peggio. Ma prova e testa prima. Più "scadente" è il tuo software, più potenziale ha per l'ottimizzazione. Quindi è meglio imparare facendo, perché alla fine scriverai software migliore più velocemente e scriverai più velocemente software migliore.

Non conosco un modo per ottenere i primi cinque post per ciascuna delle categorie con una singola query. Se prevedi di avere sempre solo 45 post, allora interrogare il database una volta e ottenere i tuoi cinque post per ogni categoria è probabilmente l'approccio più efficiente. Tuttavia, interrogare il database tre volte e combinare i risultati non è una cosa negativa.
