Une Query Loop filtrée sur un seul champ personnalisé et une Query Loop filtrée sur deux champs combinés n’ont, en apparence, presque rien de différent dans leur configuration ; en pratique, la seconde peut multiplier le temps de réponse par un facteur bien supérieur au simple doublement qu’on pourrait attendre. La différence tient à la façon dont WP_Query traduit chaque clause meta_query en jointure SQL distincte sur la table wp_postmeta.
L’arborescence d’une requête à deux méta-champs
Une Query Loop qui filtre des fiches produits sur un statut de stock et une plage de prix construit, en interne, une requête dont la structure logique ressemble à ceci :
WP_Query
├── meta_query (relation: AND)
│ ├── clause 1 : stock_statut = "disponible"
│ │ └── jointure vers wp_postmeta AS mt1
│ └── clause 2 : prix BETWEEN 20 AND 80
│ └── jointure vers wp_postmeta AS mt2
└── requête finale
└── SELECT ... FROM wp_posts
INNER JOIN wp_postmeta AS mt1 ON ...
INNER JOIN wp_postmeta AS mt2 ON ...
WHERE mt1.meta_value = 'disponible'
AND mt2.meta_value BETWEEN 20 AND 80
Chaque clause ajoute sa propre jointure vers la même table, avec un alias distinct : la table wp_postmeta stocke chaque paire clé-valeur sur une ligne séparée, ce qui oblige à la joindre une fois par champ recherché plutôt qu’une seule fois pour l’ensemble.
Ce que la relation AND coûte de plus qu’une relation OR

Une relation OR entre deux clauses laisse à MySQL la liberté de satisfaire la condition via l’une ou l’autre jointure, ce qui limite souvent le nombre de lignes intermédiaires à combiner. Une relation AND, en revanche, exige que les deux jointures produisent un résultat cohérent pour chaque article, ce qui multiplie le nombre de combinaisons à évaluer sur un catalogue de plusieurs milliers de fiches — la différence devient nette dès que le volume dépasse quelques centaines d’entrées filtrées simultanément.
Une restructuration possible : fusionner au moment de l’enregistrement
Quand les deux champs filtrés varient toujours ensemble d’un point de vue métier — ici, un produit disponible dans une fourchette de prix précise correspond à une catégorie commerciale bien définie — il devient possible de précalculer un champ composite au moment de l’enregistrement de la fiche, plutôt que de recombiner les deux champs à chaque requête :
add_action( 'save_post_produit', function ( $post_id ) {
$statut = get_post_meta( $post_id, 'stock_statut', true );
$prix = (int) get_post_meta( $post_id, 'prix', true );
$tranche = $prix < 20 ? 'bas' : ( $prix <= 80 ? 'moyen' : 'haut' );
update_post_meta( $post_id, 'segment_recherche', $statut . '_' . $tranche );
} );
La Query Loop ne filtre alors plus que sur un seul champ, segment_recherche, avec une seule jointure au lieu de deux :
new WP_Query( array(
'post_type' => 'produit',
'meta_key' => 'segment_recherche',
'meta_value' => 'disponible_moyen',
) );
Ce que cette restructuration ne résout pas
Cette approche fonctionne bien pour des filtres discrets et prévisibles à l’avance, comme une tranche de prix découpée en catégories fixes. Elle devient inadaptée dès que le filtre doit accepter une plage continue arbitraire choisie par le visiteur, auquel cas la fusion en un seul champ perdrait la précision nécessaire au filtre réel. Dans ce cas, la relation AND reste incontournable, et la restructuration porte alors sur la réduction du nombre de résultats à filtrer en amont, par exemple via un filtre de taxonomie appliqué avant la clause meta_query.
Une variante intermédiaire
Quand seule la première clause peut être fusionnée mais pas la seconde, combiner un filtre de taxonomie natif — lui-même indépendant de wp_postmeta — avec une seule clause meta_query restante permet déjà de retirer une jointure sur les deux :
new WP_Query( array(
'post_type' => 'produit',
'tax_query' => array(
array( 'taxonomy' => 'disponibilite', 'field' => 'slug', 'terms' => 'en-stock' ),
),
'meta_key' => 'prix',
'meta_value' => array( 20, 80 ),
'meta_compare' => 'BETWEEN',
'meta_type' => 'NUMERIC',
) );
Avant d’ajouter une deuxième clause meta_query à une Query Loop existante, nous vérifions systématiquement si l’un des deux champs peut devenir une taxonomie plutôt qu’un champ personnalisé : la structure de requête qui en résulte reste presque toujours plus légère.
Ce qu’il faut retenir
Deux clauses meta_query combinées en AND ne coûtent pas simplement deux fois le prix d’une seule clause : elles multiplient les combinaisons évaluées par MySQL, en particulier sur un volume de contenu conséquent. Restructurer la donnée en amont, en fusionnant ce qui peut l’être ou en déplaçant un champ vers une taxonomie, reste souvent une solution plus durable que d’accepter la requête telle quelle et de chercher à en compenser le coût ailleurs.