Orderby meta_value restituisce solo post con meta_key esistente
Ho la seguente 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 risultati perché ci sono solo 10 post news
con un meta_key = custom_author_name
. Ma ci sono centinaia di post news
che non hanno una riga post_meta con quella meta_key specifica. Si noti che non c'è alcun meta_query coinvolto. Nessun meta_value è assegnato, perché sto solo cercando di ordinare i post per meta_key, non filtrarli per meta_value.
Non dovrebbe orderby selezionare tutti i post? e semplicemente ordinarli?
Se è così, perché il risultato viene filtrato? Se la meta_key non viene trovata, perché non usare semplicemente una stringa vuota o un match all?
Se no, perché no?
Se inserisco una meta_key in ogni post news (anche se è una stringa vuota) allora ottengo il risultato atteso. Ma sembrano un sacco di righe di tabella non necessarie.

Come indicato nella risposta di @ambroseya, dovrebbe funzionare così. Una volta dichiarata una meta query, anche se non stai cercando un valore specifico, interrogherà solo i post che hanno quella meta key dichiarata. Se vuoi includere tutti i post e ordinarli per la meta key, usa il seguente codice:
$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;
Questo codice utilizza una meta query avanzata che cerca i post che hanno e non hanno quella meta key dichiarata. Poiché quello con EXISTS
è primo, quando ordini per meta_value
, utilizzerà la prima query.

Ho provato ad applicare la risposta di @Manny Fleurmond e, come @Jake, non sono riuscito a farla funzionare anche dopo aver corretto il typo in cui 'orderby' => 'meta_key'
doveva essere 'orderby' => 'meta_value'
. (Per completezza, dovrebbe essere 'posts_per_page'
e non 'post_per_page'
, ma questo non influisce sul problema in esame.)
Se osservi la query SQL effettivamente generata dalla risposta di @Manny Fleurmond (dopo aver corretto i typo), ecco cosa ottieni:
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
Questo mostra come WP interpreta i query vars: crea una tabella per ogni clausola meta_query, poi determina come unirle e come ordinare. L'ordinamento funzionerebbe bene se usassi solo una singola clausola con 'compare' => 'EXISTS'
, ma unire la seconda clausola 'compare' => 'NOT EXISTS'
con OR (come dobbiamo fare) scombussola l'ordinamento. Il risultato è che LEFT JOIN viene usato per unire sia la prima clausola/tabella che la seconda clausola/tabella - e il modo in cui WP mette tutto insieme fa sì che la tabella creata con 'compare' => 'EXISTS'
venga popolata con meta_values da QUALSIASI campo personalizzato, non solo il campo 'custom_author_name'
a cui siamo interessati. Quindi penso che ordinare per quella clausola/tabella darà i risultati desiderati solo se il particolare post_type 'news' ha un unico campo personalizzato.
La soluzione che ha funzionato nel mio caso è stata ordinare per l'altra clausola/tabella - quella NOT EXISTS. Sembra controintuitivo, lo so, ma a causa del modo in cui WP interpreta i query vars, è questa tabella dove meta_value
viene popolata solo dal campo personalizzato che ci interessa.
(L'unico modo in cui ho capito questo è stato eseguire l'equivalente di questa query per il mio caso:
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
Ho solo cambiato le colonne mostrate e rimosso la clausola GROUP BY. Questo mi ha mostrato cosa stava succedendo - che la colonna postmeta.meta_value stava prendendo valori da tutti i meta_keys, mentre la colonna mt1.meta_value prendeva solo meta_values dal campo personalizzato news.)
La Soluzione
Come dice @Manny Fleurmond, è la prima clausola che viene usata per l'orderby, quindi la risposta è semplicemente scambiare le clausole, ottenendo questo:
$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);
In alternativa, puoi rendere le clausole array associativi e ordinare per la chiave corrispondente, così:
$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);

Vale la pena sottolineare che se la meta chiave custom_author_name
viene impostata e poi rimossa, quel meta_key
risponderà a EXISTS
e l'effetto sarà che quei post verranno visualizzati insieme a quelli che effettivamente hanno un custom_author_name
. Nel mio caso ho una checkbox quindi sto usando "value" => "1"
invece di EXISTS
, ma per le stringhe sarà necessario un approccio diverso.

È effettivamente così che funziona.
Se vuoi ottenere lo stesso risultato senza aggiungere righe alla tabella, dovrai eseguire due query. Una con il meta_key che restituisce i risultati limitati, e l'altra che recupera l'intera lista; poi potrai usare PHP per confrontare i risultati delle due query (magari rimuovendo i risultati del meta_key dall'altra query per eliminare duplicati, o qualsiasi altra soluzione abbia senso nel tuo contesto specifico).

Sfortunatamente, non è così che funziona WP_Query
. Non appena aggiungi quel componente "meta", hai creato una sorta di filtro. Se esegui un dump di $query->request
capirai cosa intendo.
In secondo luogo, WP_Query
non supporta affatto l'ordinamento per una meta key. Puoi ordinare per un meta value di una chiave specifica, ma non per la chiave stessa. Anche in questo caso, esegui un dump della query per capire. Noterai che i componenti di "order" vengono ignorati se ci provi.
Il modo più pulito per far funzionare questo meccanismo, secondo me, è utilizzare un paio di filtri brevi:
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
