Je pense que le problème concerne essentiellement la structure de requête SQL et je ne suis pas un expert ....
J'ai besoin de rechercher des articles (type d'article personnalisé) selon 2 paramètres:
pd_city
pd_country
Veuillez noter que la relation meta_query est 'OU', donc si l'un des deux précédents est LIKE, nous devrions avoir des résultats.
La troisième clé (is_sponsored) est utilisée pour trier les messages! Il peut être 1 ou 0 et les articles dont la valeur "is_sponsored" est égale à 1 doivent figurer en haut de la liste.
Alors, voici le truc WordPress:
$sfp_query_args = array(
'sfp_complex_search' => 'yeap',
'tax_query' => array( array( 'taxonomy' => 'sfp_post_category', 'terms' => $term_id ) ),
//'meta_key' => 'is_sponsored',
'post_type' => 'sfpposts',
'post_status' => 'publish',
'showposts' => (int)$per_page,
'paged' => $paged,
'meta_query' => array( 'relation' => 'OR',
array( 'key' => 'pd_city', 'value' => $sfp_search_meta, 'compare' => 'LIKE' ),
array( 'key' => 'pd_country', 'value' => $sfp_search_meta, 'compare' => 'LIKE' ),
array( 'key' => 'is_sponsored' )
)
);
$sfp_search = new WP_Query( $sfp_query_args );
J'ai également besoin de filtrer les résultats avec "posts_orderby" pour que ceux qui sont parrainés soient au top:
add_filter( 'posts_orderby', 'sfp_modify_search' );
function sfp_modify_search( $orderby ) {
if( !is_admin() && is_page( $this->options[ 'sfp_page_entries_search' ] ) ) {
global $wpdb;
$orderby = " CASE WHEN mt2.meta_value = 0 THEN 1 END, $wpdb->posts.post_date DESC ";
}
return $orderby;
}
Le vrai problème réside en fait dans le fait qu'avec cette requête, TOUS les POSTS de "sfp_post_category" sont renvoyés, pas seulement ceux qui correspondent à "pd_city" ou "pd_country" car ALL POSTS A "is_sponsored" (et la valeur est définie sur 1 ou 0). Encore une fois: "is_sponsored" est nécessaire pour le tri!
Quand var_dump
var_dump( $sfp_search->request );
... WordPress 'sql ressemble à ceci:
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)
INNER JOIN wp_postmeta AS mt2 ON (wp_posts.ID = mt2.post_id)
WHERE 1=1
AND ( wp_term_relationships.term_taxonomy_id IN (77) )
AND wp_posts.post_type = 'sfpposts'
AND (wp_posts.post_status = 'publish')
AND ( (wp_postmeta.meta_key = 'pd_city'
AND CAST(wp_postmeta.meta_value AS CHAR)
LIKE '%something%')
OR (mt1.meta_key = 'pd_country'
AND CAST(mt1.meta_value AS CHAR)
LIKE '%something%')
OR mt2.meta_key = 'is_sponsored' )
GROUP BY wp_posts.ID
ORDER BY CASE WHEN mt2.meta_value = 0 THEN 1 END, wp_posts.post_date DESC
LIMIT 0, 10
Comment puis-je éliminer tous les messages qui ne correspondent pas à "pd_city" ou "pd_country" des résultats?
Le problème réside dans les méta-requêtes qui ne prennent pas en charge des relations différentes et/ou imbriquées - une lacune en passant, qui m’a également rendu fou auparavant. Dans une instance récente avec un scénario de recherche également.
Ce que vous voulez faire ne peut tout simplement pas être accompli avec WP_Query
ainsi qu’une seule boucle.
Comme vous semblez l'avoir remarqué, le fait de placer la clé de tri dans le tableau meta_query
ou à l'extérieur de celui-ci en tant qu'argument de requête général ne fait aucune différence. Si vous définissez la relation de méta-requête sur OR
et spécifiez un meta_key
n'importe où dans les arguments de la requête sans définir le paramètre meta_value
qui l'accompagne, la requête always renverra au moins toutes les publications où cette méta_key est définie.
Soit dit en passant et par souci d’exhaustivité: lorsque vous utilisez une seule méta_query avec !=
comme valeur pour meta_compare
, la requête renverra tous les résultats avec le meta_key
défini et non égal au meta_value
donné - not renvoie les articles pour lesquels le meta_key
n'est pas utilisé. Un autre point où les méta-requêtes échouent.
Je vois deux options. D'une part, vous pouvez omettre la clé méta is_sponsored
de la requête, ainsi que la pagination, obtenir les publications correctes et effectuer le tri avec une seconde instance de WP_Query
, en lui transmettant les identifiants de publication filtrés via le paramètre post__in
:
$sfp_search_args = array(
'sfp_complex_search' => 'yeap',
'tax_query' => array( array( 'taxonomy' => 'sfp_post_category', 'terms' => $term_id ) ),
'post_type' => 'sfpposts',
'post_status' => 'publish',
'meta_query' => array(
'relation' => 'OR',
array( 'key' => 'pd_city', 'value' => $sfp_search_meta, 'compare' => 'LIKE' ),
array( 'key' => 'pd_country', 'value' => $sfp_search_meta, 'compare' => 'LIKE' )
)
);
$sfp_search = new WP_Query( $sfp_search_args );
$post_ids = array();
while ( $sfp_search->have_posts() ) : $sfp_search->next_post();
$post_ids[] = $sfp_search->post->ID;
endwhile;
$sfp_ordered_args(
'post__in' => $post_ids,
// note that 'showposts' is deprected
'posts_per_page' => (int)$per_page,
'paged' => $paged,
'meta_key' => 'is_sponsored',
'order' => 'DESC',
'orderby' => 'meta_value_num date'
);
$sfp_ordered = new WP_Query( $sfp_ordered_args );
while ( $sfp_ordered->have_posts() ) : $sfp_ordered->next_post();
// display posts
endwhile;
Notez que le paramètre $orderby
de WP_Query
prendra plusieurs valeurs séparées par un espace. La modification de votre recherche est peut-être plus complexe que nécessaire.
Puisque vous aimez votre idée de var_dumping la propriété request
de l'objet de requête, laissez-moi déclencher une suggestion secondaire rapide - et, notons-le, non testée:
Si vous avez légèrement modifié le code SQL donné en remplaçant l'opérateur logique OR mt2.meta_key = 'is_sponsored'
par AND
et en le déplaçant en conséquence, vous pouvez extraire les publications avec $wpdb
:
$sfp_post_ids = $wpdb->get_col(
"
SELECT wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)
INNER JOIN wp_postmeta AS mt2 ON (wp_posts.ID = mt2.post_id)
WHERE 1=1
AND ( wp_term_relationships.term_taxonomy_id = $term_id )
AND wp_posts.post_type = 'sfpposts'
AND (wp_posts.post_status = 'publish')
AND ( (wp_postmeta.meta_key = 'pd_city'
AND CAST(wp_postmeta.meta_value AS CHAR)
LIKE '%$sfp_search_meta%')
OR (mt1.meta_key = 'pd_country'
AND CAST(mt1.meta_value AS CHAR)
LIKE '%$sfp_search_meta%') )
AND mt2.meta_key = 'is_sponsored'
GROUP BY wp_posts.ID
ORDER BY CASE WHEN mt2.meta_value = 0 THEN 1 END, wp_posts.post_date DESC
"
);
À ce stade, vous avez également deux options:
Soit itérer sur le tableau $sfp_post_ids
avec une simple foreach
et extraire les données de publication avec get_post()
individuellement dans cette boucle, ou, si vous voulez les finesses de WP_Query
- pagination, balises de modèle, etc. $sfp_post_ids
au paramètre post__in
comme dans la solution 1.
Tout cela se produit à cause de la relation OR
sur meta_query
et de la façon dont WordPress génère la chaîne de requête réelle. J'ai fini par me connecter au filtre posts_clauses
pour modifier les éléments where
et orderby
de la requête:
public function wpse_68002_orderby_fix($pieces){
global $wpdb;
$pieces['where'] .= " AND $wpdb->postmeta.meta_key = 'your_meta_key'"; // <--- update here with your meta_key name
$pieces['orderby'] = "$wpdb->postmeta.meta_value ASC";
return $pieces;
}
Ajoutez simplement le filtre avant de configurer votre objet WP_Query, puis assurez-vous de le supprimer après avoir exécuté votre requête afin de ne pas affecter les autres requêtes:
add_filter( 'posts_clauses', 'wpse_68002_orderby_fix', 20, 1 );
$query = new WP_Query($args);
$result = $query->get_posts();
remove_filter( 'posts_clauses', 'wpse_68002_orderby_fix', 20 );
N'oubliez pas de laisser meta_key
et orderby
dans les arguments de la requête.
C'est compliqué :)
J'allais suggérer que vous n'avez peut-être pas besoin d'avoir la valeur array( 'key' => 'is_sponsored' )
dans le tableau 'meta_query' et que vous pouvez le faire en ajoutant une 'méta_key' au tableau principal, mais il semble que vous ayez déjà essayé. Avez-vous eu les mêmes résultats?
JOIN
s peut rendre les choses compliquées. Vous avez accroché à posts_orderby
. Avez-vous envisagé de vous connecter à posts_fields
et d'ajouter une sous-requête qui vous donnerait votre méta_value?
(SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = 'is_sponsored' AND post_id = {$wpdb->posts}.ID) as is_sponsored