web-dev-qa-db-fra.com

Méta requête complexe avec 3 clés

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:

  1. pd_city

  2. 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?

8
Dameer

Le coupable

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.

Solution 1

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.

Solution 2

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.

6
Johannes Pille

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.

2
Parham

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?

JOINs 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

1
s_ha_dum