Cum să utilizez WP_Query pentru a interoga mai multe categorii cu un număr limitat de postări pe categorie?
Am 3 categorii cu câte 15 postări fiecare și doresc să fac O SINGURĂ interogare în baza de date care să returneze doar primele 5 postări din fiecare categorie. Cum pot realiza acest lucru?
$q = new WP_Query(array( 'post__in' => array(2,4,8), 'posts_per_page' => **PRIMELE_5_DIN_FIECARE_CATEGORIE** ));
În cazul în care acest lucru nu este posibil, care este soluția mai eficientă: să obțin toate postările pentru categoria părinte și să le parcurg sau să creez 3 interogări separate?

Ceea ce dorești este posibil, dar va necesita să aprofundezi SQL, ceea ce prefer să evit ori de câte ori este posibil (nu pentru că nu-l cunosc, sunt un dezvoltator avansat SQL, ci pentru că în WordPress dorești să utilizezi API-ul ori de câte ori este posibil pentru a minimiza viitoarele probleme de compatibilitate legate de potențiale modificări viitoare ale structurii bazei de date).
SQL cu un operator UNION
este o posibilitate
Pentru a utiliza SQL, ceea ce ai nevoie este un operator UNION
în interogarea ta, ceva de genul acesta, presupunând că slug-urile categoriilor tale sunt "category-1"
, "category-2"
și "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
)
Poți utiliza SQL UNION cu un filtru posts_join
Folosind codul de mai sus, poți fie să faci apelul direct, fie să utilizezi un filtru prin hook-ul posts_join
, așa cum este prezentat mai jos; reține că folosesc PHP heredoc, așa că asigură-te că SQL;
este aliniat la stânga. De asemenea, am utilizat o variabilă globală pentru a-ți permite să definești categoriile în afara hook-ului, listând slug-urile categoriilor într-un array. Poți pune acest cod într-un plugin sau în fișierul functions.php
al temei tale:
<?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 " ;
}
Dar pot apărea efecte secundare
Desigur, utilizarea hook-urilor de modificare a interogărilor, cum ar fi posts_join
, poate avea efecte secundare, deoarece acționează global asupra interogărilor. Prin urmare, de obicei este nevoie să înfășori modificările într-un if
care le aplică doar atunci când este necesar, iar criteriile de testare pot fi complicate.
Mai bine te concentrezi pe optimizare?
Totuși, presupun că întrebarea ta este mai degrabă despre optimizare decât despre capacitatea de a face o interogare top 5 înmulțită cu 3, corect? Dacă da, atunci poate există alte opțiuni care utilizează API-ul WordPress?
Mai bine să utilizezi Transients API pentru caching?
Presupun că postările tale nu se schimbă prea des, corect? Ce-ar fi să accepți cele trei (3) interogări periodic și apoi să stochezi rezultatele în cache folosind Transients API? Vei obține un cod mai ușor de întreținut și o performanță excelentă; chiar mai bună decât interogarea UNION
de mai sus, deoarece WordPress va stoca listele de postări ca un array serializat într-un singur rând din tabela wp_options
.
Poți lua următorul exemplu și să-l plasezi în rădăcina site-ului tău ca test.php
pentru a-l testa:
<?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 "Salutare la fiecare {$timeout} ore!";
$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);
Rezumat
Deși da, poți face ceea ce ai cerut folosind o interogare SQL UNION
și hook-ul de filtru posts_join
, probabil că este mai bine să utilizezi caching cu Transients API în schimb.
Sper că acest lucru te ajută?

Cache-ul prin transient este o soluție excelentă pentru a reduce impactul asupra site-ului.

@Mike, dacă aș trăi pe continentul tău, aș fi urmat cursuri la tine! mulțumesc din nou!

Nu există o modalitate de a limita join hook-ul doar la un singur obiect WP_query?

@Manny Fleurmond - De obicei spun să pui o altă întrebare (ceea ce ar trebui să faci oricum) dar o metodă ar fi să extinzi clasa WP_Query
, să adaugi o proprietate și apoi să verifici acea proprietate.

ai putea să salvezi transient-ul pe termen nelimitat și apoi să-l golesti la save_post? există un exemplu bun (folosind hook-ul edit_term) în codex

@MikeSchinkel 2 ani mai târziu și folosesc transients tot timpul!

WP_Query()
nu suportă funcționalități de genul Primele X Postări Pentru Fiecare Categorie. În loc de WP_Query()
, poți folosi get_posts()
pentru fiecare dintre categoriile tale, adică de trei ori:
<?php
$posts = array();
$categories = array(2,4,8);
foreach($categories as $cat) {
$posts[] = get_posts('numberposts=5&offset=1&category='.$cat);
}
?>
$posts
acum conține primele cinci postări pentru fiecare categorie.

nu reprezintă asta 3 interogări la baza de date? Voiam să știu dacă se poate face într-o singură interogare, deoarece am crezut că e mai eficient.

păi tocmai pentru asta există baza de date, nu? Pentru a interoga date din ea. Baza de date este făcută pentru astfel de lucruri. Probabil e mai rapid decât să rulezi o interogare SQL complicată pe care o ceri. Nu-ți fie teamă să folosești aceste funcționalități. Dacă strică site-ul, raportează aici.

am înțeles... nu știu prea multe despre eficiența în php/mysql/baze de date (aș vrea să citesc mai mult), eram sigur că mai puține interogări sunt mai bune și apoi să procesez rezultatul eu.

Ei bine, este adevărat, în general mai mult înseamnă mai mult și mai puțin înseamnă mai puțin. Dar încearcă și testează mai întâi. Cu cât software-ul tău este "mai rău", cu atât are mai mult potențial de optimizare. Așadar, este mai bine să înveți făcând, pentru că în cele din urmă vei scrie software mai bun mai rapid și vei scrie software mai rapid mai bine.

Nu cunosc nicio metodă de a obține primele cinci articole pentru fiecare categorie într-o singură interogare. Dacă vei avea întotdeauna doar 45 de articole, atunci să interoghezi baza de date o singură dată și să obții câte cinci articole pentru fiecare categorie este probabil cea mai eficientă abordare. Totuși, nu este un lucru rău să interoghezi baza de date de trei ori și să combini rezultatele.
