Requête méta complexe avec 3 clés

8

Je pense que le problème concerne essentiellement la structure de requête SQL et je ne suis pas un expert ....

Je dois rechercher des publications (type de publication personnalisé) par 2 paramètres:

  1. pd_city

  2. pd_country

Veuillez noter que la relation meta_query est 'OU', donc si l'un des deux ci-dessus 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 être répertoriés en haut.

Voici donc 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" afin d'obtenir ceux sponsorisés en haut:

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 qu'avec cette requête TOUS LES POSTES de "sfp_post_category" sont retournés, pas seulement ceux qui correspondent à "pd_city" ou "pd_country" parce que TOUS LES POSTES ONT "clé_est_sponsored" (et valeur définie sur 1 ou 0). Encore une fois: "is_sponsored" est nécessaire pour le tri!

Lorsque var_dump

var_dump( $sfp_search->request );

... Le sql de WordPress 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?

Dameer
la source

Réponses:

6

Le coupable

Le coupable est que les méta-requêtes ne prennent pas en charge des relations différentes et / ou imbriquées - une lacune d'ailleurs, qui m'a également rendu fou. Dans un cas récent avec un scénario de recherche également.

Ce que vous voulez faire ne peut tout simplement pas être accompli avec WP_Queryune seule boucle.
Comme vous semblez l'avoir remarqué, que vous placiez la clé de tri dans le meta_querytableau ou à l'extérieur de celui-ci en tant qu'argument de requête générale ne fait aucune différence. Si vous définissez la relation de méta-requête sur ORet spécifiez un emplacement quelconque des arguments meta_keyde requête sans définir le meta_valueparamètre d' accompagnement , la requête renvoie toujours au moins toutes les publications où cette méta-clé est définie.
Soit dit en passant et par souci d'exhaustivité: lorsque vous utilisez une seule méta-requête avec !=comme valeur pour meta_compare, la requête renvoie tous les résultats avec l' meta_keyensemble et non égal à la donnée meta_value- il ne sera pasretourner tous les messages qui n'ont pas du tout meta_keyutilisé. Un autre point où les méta-requêtes échouent.

Solution 1

Je vois deux options. D'une part, vous pouvez omettre la is_sponsoredméta-clé de la requête, omettre également la pagination, obtenir les publications correctes et effectuer le tri avec une deuxième instance de WP_Query, en lui passant les ID de publication filtrés via le post__inparamètre:

$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 $orderbyparamètre de WP_Queryprendra plusieurs valeurs séparées par un espace. Votre modification de recherche peut être plus complexe que nécessaire.

Solution 2

Puisque j'aime votre idée de var_dumping la propriété de l'objet de requête request, permettez-moi de lancer une suggestion secondaire rapide - et, remarquez, non testée -:

Si vous avez légèrement modifié le SQL donné en changeant l'opérateur logique de OR mt2.meta_key = 'is_sponsored'en ANDet 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 parcourez le $sfp_post_idstableau avec un simple foreachet tirez les données de publication avec get_post()individuellement dans cette boucle, ou, si vous voulez les subtilités de la WP_Querypagination, des balises de modèle, etc., alimentez $sfp_post_idsle post__inparamètre comme dans la solution 1.

Johannes Pille
la source
2

Tout cela se produit en raison de la ORrelation meta_queryet de la façon dont WordPress génère la chaîne de requête réelle. J'ai fini par me connecter au posts_clausesfiltre pour modifier le whereet les orderbymorceaux 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 pour 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 );

Rappelez - vous de quitter le meta_keyet orderbydes args de requête.

Parham
la source
1

C'est compliqué :)

J'allais suggérer que vous n'avez peut-être pas besoin d'avoir la array( 'key' => 'is_sponsored' )valeur dans le tableau 'meta_query' et que vous pourriez le faire en ajoutant une 'meta_key' au tableau principal, mais il semble que vous ayez essayé. Avez-vous obtenu les mêmes résultats?

JOINs peut compliquer les choses. Vous avez accroché posts_orderby. Avez-vous envisagé de posts_fieldsvous connecter et d'ajouter une sous-requête qui vous permettrait d'obtenir votre méta-valeur?

(SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = 'is_sponsored' AND post_id = {$wpdb->posts}.ID) as is_sponsored

s_ha_dum
la source