J'ai le wp_query suivant:
$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 résultats car il n'y a que 10 articles news
avec un meta_key = custom_author_name
. Mais il y a des centaines de messages news
qui n'ont pas de ligne post_meta avec cette méta_key spécifique. Veuillez noter qu'il n'y a pas de méta_query impliquée. Aucune méta_value n'est attribuée, car j'essaie uniquement de trier les publications par méta_key, et non de filtrer par meta_value.
Ne devrait-il pas commander par tous les articles? et juste les commander?
Si oui, pourquoi le résultat est-il filtré? Si la méta_key n'est pas trouvée, pourquoi ne pas simplement utiliser une chaîne vide ou une correspondance avec tous?
Si non pourquoi pas
Si j'entre une méta_key à chaque message (même s'il s'agit d'une chaîne vide), le résultat attendu est obtenu. Mais cela semble être un tas de rangées de table qui n'ont pas besoin d'être là.
Comme indiqué dans la réponse de @ ambroseya, son fonctionnement est supposé fonctionner de la sorte. Une fois que vous déclarez une méta-requête, même si vous ne recherchez pas une valeur spécifique, seules les publications avec cette clé méta déclarée seront interrogées. Si vous souhaitez inclure toutes les publications, triez-les à l'aide de la clé méta, utilisez le code suivant:
$args = array(
'post_type' => 'news',
'orderby' => 'meta_key',
'order' => 'ASC',
'meta_query' => array(
'relation' => 'OR',
array(
'key'=>'custom_author_name',
'compare' => 'EXISTS'
),
array(
'key'=>'custom_author_name',
'compare' => 'NOT EXISTS'
)
),
'post_per_page'=>-1
);
$query = new WP_Query($args);
echo $query->found_posts;
Cela utilise une méta-requête avancée qui recherche les publications qui ont et n'ont pas cette clé méta déclarée. Étant donné que celui avec EXISTS
est premier, lorsque vous triez par meta_value
, la première requête est utilisée.
J'ai essayé d'appliquer la réponse de @Manny Fleurmond et, comme @Jake, je ne pouvais pas la faire fonctionner même après avoir corrigé la faute de frappe que 'orderby' => 'meta_key'
devait être 'orderby' => 'meta_value'
. (Et pour être complet, il devrait s'agir de 'posts_per_page'
et non de 'post_per_page'
, mais cela n'affecte pas le problème examiné.)
Si vous regardez la requête SQL générée par la réponse de @Manny Fleurmond (après avoir corrigé les fautes de frappe), voici ce que vous obtenez:
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
Ceci illustre la façon dont WP analyse les vars de requête: il crée une table pour chaque clause meta_query, puis détermine comment les associer et comment commander. La commande fonctionnerait correctement si vous utilisiez une seule clause avec 'compare' => 'EXISTS'
, mais si vous joignez la seconde clause 'compare' => 'NOT EXISTS'
avec OR (comme il se doit), la commande est gâchée. Le résultat est que LEFT JOIN est utilisé pour joindre la première clause/table et la seconde clause/table - et la façon dont WP associe tout cela signifie que la table créée à l'aide de 'compare' => 'EXISTS'
est en fait remplie avec les méta_values de TOUT champ personnalisé, pas seulement le champ 'custom_author_name'
qui nous intéresse. Je pense donc que le classement par cette clause/table ne donnera les résultats souhaités que si le post_type particulier de 'news' ne contient qu'un seul champ personnalisé.
La solution qui a fonctionné dans mon cas était de passer commande par l’autre clause/table - celle qui n’existe pas. En apparence contre-intuitif, je le sais, mais à cause de la façon dont WP analyse les vars de requête, il s’agit de cette table où meta_value
est rempli uniquement par le champ personnalisé que nous recherchons.
(La seule façon dont j'ai compris cela était en exécutant l'équivalent de cette requête pour mon cas:
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
Tout ce que j'ai fait est de changer les colonnes affichées et de supprimer la clause GROUP BY. Cela m’a ensuite montré ce qui se passait - que la colonne postmeta.meta_value extrayait les valeurs de tous les meta_keys, tandis que la colonne mt1.meta_value extrayait uniquement les méta_valeurs du champ personnalisé news.)
La solution
Comme le dit @Manny Fleurmond, c'est la première clause qui est utilisée pour la commande, la solution consiste donc simplement à échanger les clauses, en donnant ceci:
$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);
Vous pouvez également créer des tableaux associatifs et des commandes par la clé correspondante, comme suit:
$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);
C'est comme ça que ça fonctionne.
Si vous souhaitez le faire sans ajouter de lignes, vous devrez effectuer deux requêtes. Un avec la meta_key qui a les résultats limités, et l'autre qui obtient la liste complète; Ensuite, utilisez PHP pour comparer les deux résultats de la requête (en supprimant éventuellement les résultats de la méta_key de l'autre requête pour supprimer les doublons, ou tout ce qui convient à votre configuration).
Malheureusement, ce n'est pas comme ça que WP_Query
fonctionne. Dès que vous ajoutez ce composant "méta", vous avez créé une sorte de filtre. Dump $query->request
et vous verrez ce que je veux dire.
Deuxièmement, WP_Query
ne prend pas en charge la commande par une méta clé . Vous pouvez commander par une méta valeur pour une clé particulière mais non par la clé elle-même. Encore une fois, videz la requête pour voir ce que je veux dire. Vous remarquerez que les composants "ordre" sont abandonnés si vous essayez.
Le moyen le plus propre de faire en sorte que cela fonctionne, à mon avis, consiste en deux filtres courts:
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