Orderby meta_value returnează doar postările care au meta_key existent
Am următorul wp_query:
$args = array(
'post_type' => 'news',
'orderby' => 'meta_key',
'order' => 'ASC',
'meta_key'=>'custom_author_name',
'post_per_page'=>-1
);
$query = new WP_Query($args);
echo $query->found_posts;
echo = 10 rezultate deoarece există doar 10 postări news
cu un meta_key = custom_author_name
. Dar există sute de postări news
care nu au un rând post_meta cu acel meta_key specific. Rețineți că nu există nicio meta_query implicată. Nu este atribuită nicio meta_value, pentru că încerc doar să ordonez postările după meta_key, nu să le filtrez după meta_value.
Nu ar trebui ca orderby să selecteze toate postările? și doar să le ordoneze?
Dacă da, de ce rezultatul este filtrat? Dacă meta_key nu este găsit, de ce să nu folosească un șir gol sau o potrivire generală?
Dacă nu, de ce nu?
Dacă introduc un meta_key pentru fiecare postare news (chiar dacă este un șir gol), atunci obțin rezultatul așteptat. Dar asta pare a fi o mulțime de rânduri inutile în tabel.

După cum a menționat @ambroseya în răspunsul său, funcționarea este conformă așteptărilor. Odată ce declarați o interogare meta, chiar dacă nu căutați o valoare specifică, aceasta va interoga doar postările care au acea cheie meta declarată. Dacă doriți să includeți toate postările și să le sortați după cheia meta, utilizați următorul cod:
$args = array(
'post_type' => 'news',
'orderby' => 'meta_value',
'order' => 'ASC',
'meta_query' => array(
'relation' => 'OR',
array(
'key'=>'custom_author_name',
'compare' => 'EXISTS'
),
array(
'key'=>'custom_author_name',
'compare' => 'NOT EXISTS'
)
),
'posts_per_page'=>-1
);
$query = new WP_Query($args);
echo $query->found_posts;
Ceea ce face acest cod este să utilizeze o interogare meta avansată care caută postări care au și nu au acea cheie meta declarată. Deoarece cea cu EXISTS
este prima, atunci când sortați după meta_value
, aceasta va folosi prima interogare.

Am încercat să aplic răspunsul lui @Manny Fleurmond și, la fel ca @Jake, nu am reușit să-l fac să funcționeze, chiar și după ce am corectat greșeala că 'orderby' => 'meta_key'
ar trebui să fie 'orderby' => 'meta_value'
. (Și pentru completitudine, ar trebui să fie 'posts_per_page'
, nu 'post_per_page'
, dar asta nu afectează problema analizată.)
Dacă te uiți la interogarea SQL generată efectiv de răspunsul lui @Manny Fleurmond (după corectarea greșelilor), aceasta este rezultatul:
SELECT wp_{prefix}_posts.* FROM wp_{prefix}_posts
LEFT JOIN wp_{prefix}_postmeta ON (wp_{prefix}_posts.ID = wp_{prefix}_postmeta.post_id AND wp_{prefix}_postmeta.meta_key = 'custom_author_name' )
LEFT JOIN wp_{prefix}_postmeta AS mt1 ON ( wp_{prefix}_posts.ID = mt1.post_id )
WHERE 1=1 AND (
wp_{prefix}_postmeta.post_id IS NULL
OR
mt1.meta_key = 'custom_author_name'
) AND wp_{prefix}_posts.post_type = 'news' AND
(wp_{prefix}_posts.post_status = 'publish' OR wp_{prefix}_posts.post_author = 1 AND wp_{prefix}_posts.post_status = 'private')
GROUP BY wp_{prefix}_posts.ID ORDER BY wp_{prefix}_postmeta.meta_value ASC
Aceasta ilustrează modul în care WP parsează variabilele de interogare: creează un tabel pentru fiecare clauză meta_query, apoi determină cum să le unească și după ce să ordoneze. Ordonarea ar funcționa bine dacă ai folosi doar o singură clauză cu 'compare' => 'EXISTS'
, dar unirea celei de-a doua clauze 'compare' => 'NOT EXISTS'
cu OR (așa cum trebuie) încurcă ordonarea. Rezultatul este că LEFT JOIN este folosit pentru a uni atât prima clauză/tabel, cât și a doua clauză/tabel - iar modul în care WP le asamblează înseamnă că tabelul creat folosind 'compare' => 'EXISTS'
este, de fapt, populat cu meta_values din ORICE câmp personalizat, nu doar din câmpul 'custom_author_name'
la care suntem interesați. Deci, cred că ordonarea după acea clauză/tabel va oferi rezultatele dorite doar dacă tipul de postare 'news' are un singur câmp personalizat.
Soluția care a funcționat în cazul meu a fost ordonarea după cealaltă clauză/tabel - cea NOT EXISTS. Pare contraintuitiv, știu, dar din cauza modului în care WP parsează variabilele de interogare, acest tabel este cel în care meta_value
este populat doar de câmpul personalizat pe care îl căutăm.
(Singurul mod prin care am descoperit acest lucru a fost prin rularea unei interogări echivalente pentru cazul meu:
SELECT wp_{prefix}_posts.ID, wp_{prefix}_postmeta.meta_value, mt1.meta_value FROM wp_{prefix}_posts
LEFT JOIN wp_{prefix}_postmeta ON (wp_{prefix}_posts.ID = wp_{prefix}_postmeta.post_id AND wp_{prefix}_postmeta.meta_key = 'custom_author_name' )
LEFT JOIN wp_{prefix}_postmeta AS mt1 ON ( wp_{prefix}_posts.ID = mt1.post_id )
WHERE 1=1 AND (
wp_{prefix}_postmeta.post_id IS NULL
OR
mt1.meta_key = 'custom_author_name'
) AND wp_{prefix}_posts.post_type = 'news' AND
(wp_{prefix}_posts.post_status = 'publish' OR wp_{prefix}_posts.post_author = 1 AND wp_{prefix}_posts.post_status = 'private')
ORDER BY wp_{prefix}_postmeta.meta_value ASC
Tot ce am făcut a fost să schimb coloanele afișate și să elimin clauza GROUP BY. Acest lucru mi-a arătat ce se întâmpla - că coloana postmeta.meta_value extragea valori din toate meta_key-urile, în timp ce coloana mt1.meta_value extragea doar meta_values din câmpul personalizat news.)
Soluția
Așa cum spune @Manny Fleurmond, prima clauză este cea folosită pentru orderby, deci răspunsul este doar să inversăm clauzele, obținând astfel:
$args = array(
'post_type' => 'news',
'orderby' => 'meta_value',
'order' => 'ASC',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'custom_author_name',
'compare' => 'NOT EXISTS'
),
array(
'key' => 'custom_author_name',
'compare' => 'EXISTS'
)
),
'posts_per_page' => -1
);
$query = new WP_Query($args);
Alternativ, poți face clauzele array-uri asociative și să ordonezi după cheia corespunzătoare, astfel:
$args = array(
'post_type' => 'news',
'orderby' => 'not_exists_clause',
'order' => 'ASC',
'meta_query' => array(
'relation' => 'OR',
'exists_clause' => array(
'key' => 'custom_author_name',
'compare' => 'EXISTS'
),
'not_exists_clause' => array(
'key' => 'custom_author_name',
'compare' => 'NOT EXISTS'
)
),
'posts_per_page' => -1
);
$query = new WP_Query($args);

Este important de menționat că, dacă cheia meta custom_author_name
este setată și apoi eliminată, acel meta_key
va răspunde la EXISTS
și efectul va fi că acestea vor fi afișate împreună cu postările care au un custom_author_name
. În cazul meu, am o casetă de bifare, așa că folosesc "value" => "1"
în loc de EXISTS
, dar pentru șirurile de caractere va fi necesară o abordare diferită.

Așa funcționează de fapt.
Dacă doriți să faceți acest lucru fără a adăuga rânduri în tabel, va trebui să faceți două interogări. Una cu meta_key care are rezultatele limitate, iar cealaltă care obține întreaga listă; apoi utilizați PHP pentru a compara cele două rezultate ale interogărilor (eventual eliminând rezultatele meta_key din cealaltă interogare pentru a elimina duplicatele sau orice altceva are sens în setarea dvs.).

Din păcate, funcționarea WP_Query
nu este așa. De îndată ce adaugi componenta "meta", ai creat un fel de filtru. Afișează $query->request
și vei înțelege la ce mă refer.
În al doilea rând, WP_Query
nu acceptă ordonarea după un cheie meta. Poți ordona după o valoare meta pentru o anumită cheie, dar nu după cheia în sine. Din nou, afișează interogarea pentru a vedea la ce mă refer. Vei observa că componentele de "order" dispar dacă încerci.
Cea mai elegantă soluție pentru a face acest lucru să funcționeze, în opinia mea, este folosirea a câtorva filtre scurte:
function join_meta_wpse_188287($join) {
remove_filter('posts_join','join_meta_wpse_188287');
global $wpdb;
return ' INNER JOIN '.$wpdb->postmeta.' ON ('.$wpdb->posts.'.ID = '.$wpdb->postmeta.'.post_id)';
}
add_filter('posts_join','join_meta_wpse_188287');
function orderby_meta_wpse_188287($orderby) {
remove_filter('posts_orderby','orderby_meta_wpse_188287');
global $wpdb;
return $wpdb->postmeta.'.meta_key ASC';
}
add_filter('posts_orderby','orderby_meta_wpse_188287');
$args = array(
'post_type' => 'news',
'post_per_page'=>-1
);
$q = new WP_Query($args);
var_dump($q->request); // debug
var_dump(wp_list_pluck($q->posts,'post_title')); // debug
