J'ai une méta-requête personnalisée qui est terriblement lente ou ne se charge même pas jusqu'à la fin. Avec jusqu’à trois arrays
dans 'meta_query'
, la requête fonctionne correctement, à partir de quatre, elle ne fonctionne plus.
Lors de la recherche d'une raison, j'ai trouvé ce message mais je ne suis absolument pas familier avec les requêtes de base de données personnalisées.
Toute aide est très appréciée! Je vous remercie!
<?php
$args = array(
'post_type' => $post_type,
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
array(
'key'=>'_author',
'value'=> $author_single["fullname"],
'compare' => '='
),
array(
'key'=>'_publisher',
'value'=> $author_single["fullname"],
'compare' => '='
),
array(
'key'=>'_contributor_1',
'value'=> $author_single["fullname"],
'compare' => '='
),
array(
'key'=>'_contributor_2',
'value'=> $author_single["fullname"],
'compare' => '='
),
array(
'key'=>'_contributor_3',
'value'=> $author_single["fullname"],
'compare' => '='
)
)
);
$posts = new WP_Query($args);
if( $posts->have_posts() ) : while( $posts->have_posts() ) : $posts->the_post(); ?>
<li><a href="<?php echo get_the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php endwhile; endif; ?>
- - - - -
Code mis à jour avec les ajouts faits par Boger:
page.php
<?php
$args = array(
'post_type' => $post_type,
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
array(
'key'=>'_author',
'value'=> $author_single["fullname"],
'compare' => '='
),
array(
'key'=>'_publisher',
'value'=> $author_single["fullname"],
'compare' => '='
),
array(
'key'=>'_contributor_1',
'value'=> $author_single["fullname"],
'compare' => '='
),
array(
'key'=>'_contributor_2',
'value'=> $author_single["fullname"],
'compare' => '='
),
array(
'key'=>'_contributor_3',
'value'=> $author_single["fullname"],
'compare' => '='
)
)
);
add_filter( 'posts_clauses', 'wpse158898_posts_clauses', 10, 2 );
$posts = new WP_Query($args);
if( $posts->have_posts() ) : while( $posts->have_posts() ) : $posts->the_post(); ?>
<li><a href="<?php echo get_the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php endwhile; endif;
remove_filter( 'posts_clauses', 'wpse158898_posts_clauses', 10 ); ?>
functions.php
function wpse158898_posts_clauses( $pieces, $query ) {
global $wpdb;
$relation = isset( $query->meta_query->relation ) ? $query->meta_query->relation : 'AND';
if ( $relation != 'OR' ) return $pieces; // Only makes sense if OR.
$prepare_args = array();
$key_value_compares = array();
foreach ( $query->meta_query->queries as $meta_query ) {
// Doesn't work for IN, NOT IN, BETWEEN, NOT BETWEEN, NOT EXISTS.
if ( ! isset( $meta_query['value'] ) || is_array( $meta_query['value'] ) ) return $pieces; // Bail if no value or is array.
$key_value_compares[] = '(pm.meta_key = %s AND pm.meta_value ' . $meta_query['compare'] . ' %s)';
$prepare_args[] = $meta_query['key'];
$prepare_args[] = $meta_query['value'];
}
$sql = ' JOIN ' . $wpdb->postmeta . ' pm on pm.post_id = ' . $wpdb->posts . '.ID'
. ' AND (' . implode( ' ' . $relation . ' ', $key_value_compares ) . ')';
array_unshift( $prepare_args, $sql );
$pieces['join'] = call_user_func_array( array( $wpdb, 'prepare' ), $prepare_args );
$pieces['where'] = preg_replace( '/ AND[^w]+wp_postmeta.*$/s', '', $pieces['where'] ); // Zap postmeta clauses.
return $pieces;
}
- - -
$posts->request
sorties
$args = array(
'post_type' => $post_type,
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
array(
'key'=>'_author',
'value'=> "Hanna Meier",
'compare' => '='
),
array(
'key'=>'_publisher',
'value'=> "Friedhelm Peters",
'compare' => '='
)
)
);
sans la requête personnalisée
SELECT wp_vacat_posts.* FROM wp_vacat_posts INNER JOIN wp_vacat_postmeta ON (wp_vacat_posts.ID = wp_vacat_postmeta.post_id)
INNER JOIN wp_vacat_postmeta AS mt1 ON (wp_vacat_posts.ID = mt1.post_id) WHERE 1=1 AND wp_vacat_posts.post_type = 'product' AND (wp_vacat_posts.post_status = 'publish' OR wp_vacat_posts.post_status = 'private') AND ( (wp_vacat_postmeta.meta_key = '_author' AND CAST(wp_vacat_postmeta.meta_value AS CHAR) = 'Hanna Meier')
OR (mt1.meta_key = '_publisher' AND CAST(mt1.meta_value AS CHAR) = 'Friedhelm Peters') ) GROUP BY wp_vacat_posts.ID ORDER BY wp_vacat_posts.post_date DESC
avec la requête personnalisée
SELECT wp_vacat_posts.* FROM wp_vacat_posts
JOIN wp_vacat_postmeta pm on pm.post_id = wp_vacat_posts.ID AND ((pm.meta_key = '_author' AND pm.meta_value = 'Hanna Meier') OR (pm.meta_key = '_publisher' AND pm.meta_value = 'Friedhelm Peters')) WHERE 1=1 AND wp_vacat_posts.post_type = 'product' AND (wp_vacat_posts.post_status = 'publish' OR wp_vacat_posts.post_status = 'private') AND ( (wp_vacat_postmeta.meta_key = '_author' AND CAST(wp_vacat_postmeta.meta_value AS CHAR) = 'Hanna Meier')
OR (mt1.meta_key = '_publisher' AND CAST(mt1.meta_value AS CHAR) = 'Friedhelm Peters') ) GROUP BY wp_vacat_posts.ID ORDER BY wp_vacat_posts.post_date DESC
J'ai rencontré ce problème et il semble que MySQL ne gère pas bien les jointures multiples vers la même table (wp_postmeta) et OR-ed WHERE que WP génère ici. Je l'ai traité en réécrivant la jointure et où, comme mentionné dans le message auquel vous vous connectez - voici une version qui devrait fonctionner dans votre cas ( mis à jour pour WP 4.1.1 ) ( mis à jour pour WP 4.2.4 ):
function wpse158898_posts_clauses( $pieces, $query ) {
global $wpdb;
$relation = isset( $query->meta_query->relation ) ? $query->meta_query->relation : 'AND';
if ( $relation != 'OR' ) return $pieces; // Only makes sense if OR.
$prepare_args = array();
$key_value_compares = array();
foreach ( $query->meta_query->queries as $key => $meta_query ) {
if ( ! is_array( $meta_query ) ) continue;
// Doesn't work for IN, NOT IN, BETWEEN, NOT BETWEEN, NOT EXISTS.
if ( $meta_query['compare'] === 'EXISTS' ) {
$key_value_compares[] = '(pm.meta_key = %s)';
$prepare_args[] = $meta_query['key'];
} else {
if ( ! isset( $meta_query['value'] ) || is_array( $meta_query['value'] ) ) return $pieces; // Bail if no value or is array.
$key_value_compares[] = '(pm.meta_key = %s AND pm.meta_value ' . $meta_query['compare'] . ' %s)';
$prepare_args[] = $meta_query['key'];
$prepare_args[] = $meta_query['value'];
}
}
$sql = ' JOIN ' . $wpdb->postmeta . ' pm on pm.post_id = ' . $wpdb->posts . '.ID'
. ' AND (' . implode( ' ' . $relation . ' ', $key_value_compares ) . ')';
array_unshift( $prepare_args, $sql );
$pieces['join'] = call_user_func_array( array( $wpdb, 'prepare' ), $prepare_args );
// Zap postmeta clauses.
$wheres = explode( "\n", $pieces[ 'where' ] );
foreach ( $wheres as &$where ) {
$where = preg_replace( array(
'/ +\( +' . $wpdb->postmeta . '\.meta_key .+\) *$/',
'/ +\( +mt[0-9]+\.meta_key .+\) *$/',
'/ +mt[0-9]+.meta_key = \'[^\']*\'/',
), '(1=1)', $where );
}
$pieces[ 'where' ] = implode( '', $wheres );
$pieces['orderby'] = str_replace( $wpdb->postmeta, 'pm', $pieces['orderby'] ); // Sorting won't really work but at least make it not crap out.
return $pieces;
}
et ensuite autour de votre requête:
add_filter( 'posts_clauses', 'wpse158898_posts_clauses', 10, 2 );
$posts = new WP_Query($args);
remove_filter( 'posts_clauses', 'wpse158898_posts_clauses', 10 );
Addendum:
Le correctif pour ceci, ticket 24093 , n’a pas été intégré à la version 4.0 ( et n’a pas résolu ce problème de toute façon ), si bien que j’ai tenté à l’origine une version généralisée de ce qui précède trop floconneux vraiment pour tenter une telle solution alors je l'ai enlevé ...
La réponse courte est que les métadonnées dans WordPress ne sont pas destinées à être utilisées pour des données relationnelles. Extraire des posts de plusieurs conditions dans ses métadonnées n’est pas l’idée derrière les métadonnées. Par conséquent, les requêtes, les structures de table et les index ne sont pas optimisés pour cela.
La réponse la plus longue:
Les résultats de votre méta-requête ressemblent à ceci:
SELECT wp_4_posts.* FROM wp_4_posts
INNER JOIN wp_4_postmeta ON (wp_4_posts.ID = wp_4_postmeta.post_id)
INNER JOIN wp_4_postmeta AS mt1 ON (wp_4_posts.ID = mt1.post_id)
INNER JOIN wp_4_postmeta AS mt2 ON (wp_4_posts.ID = mt2.post_id)
INNER JOIN wp_4_postmeta AS mt3 ON (wp_4_posts.ID = mt3.post_id)
INNER JOIN wp_4_postmeta AS mt4 ON (wp_4_posts.ID = mt4.post_id)
WHERE 1=1
AND wp_4_posts.post_type = 'post'
AND (wp_4_posts.post_status = 'publish' OR wp_4_posts.post_status = 'private')
AND ( (wp_4_postmeta.meta_key = '_author' AND CAST(wp_4_postmeta.meta_value AS CHAR) = 'Test')
OR (mt1.meta_key = '_publisher' AND CAST(mt1.meta_value AS CHAR) = 'Test')
OR (mt2.meta_key = '_contributor_1' AND CAST(mt2.meta_value AS CHAR) = 'Test')
OR (mt3.meta_key = '_contributor_2' AND CAST(mt3.meta_value AS CHAR) = 'Test')
OR (mt4.meta_key = '_contributor_3' AND CAST(mt4.meta_value AS CHAR) = 'Test') ) GROUP BY wp_4_posts.ID ORDER BY wp_4_posts.post_date DESC
Voyons comment MySQL traite cette requête (EXPLAIN
):
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE wp_4_posts range PRIMARY,type_status_date type_status_date 124 NULL 5 Using where; Using temporary; Using filesort
1 SIMPLE wp_4_postmeta ref post_id,meta_key post_id 8 wordpress.wp_4_posts.ID 1
1 SIMPLE mt1 ref post_id,meta_key post_id 8 wordpress.wp_4_posts.ID 1
1 SIMPLE mt2 ref post_id,meta_key post_id 8 wordpress.mt1.post_id 1 Using where
1 SIMPLE mt3 ref post_id,meta_key post_id 8 wordpress.wp_4_posts.ID 1
1 SIMPLE mt4 ref post_id,meta_key post_id 8 wordpress.wp_4_postmeta.post_id 1 Using where
Maintenant, ce que vous pouvez voir, MySQL fait un select sur wp_posts
et rejoint 5 fois la table wp_postmeta
. Le type ref
indique que MySQL doit examiner toutes les lignes de cette table, en faisant correspondre l'index (post_id, meta_key) comparant une valeur de colonne non indexée avec votre clause where
, et pour chaque combinaison de lignes. de la table précédente . Le manuel MySQL dit : "Si la clé utilisée ne correspond qu'à quelques lignes, il s'agit d'un bon type de jointure." Et c'est le premier problème: sur un système WordPress moyen, le nombre de post-métas par post peut facilement atteindre 30 à 40 enregistrements ou plus. L'autre clé possible meta_key
augmente avec votre nombre de messages. Donc, si vous avez 100 articles et que chacun a une méta _publisher
, il y a 100 lignes avec cette valeur comme meta_key
dans wp_postmeta
, bien sûr.
Pour gérer tous ces résultats possibles, mysql crée une table temporaire (using temporary
). Si cette table devient trop grande, le serveur la stocke généralement sur le disque au lieu de la mémoire. Un autre goulot d'étranglement possible.
Comme décrit dans les réponses existantes, vous pouvez essayer d’optimiser vous-même la requête. Cela pourrait bien répondre à vos préoccupations, mais pourrait créer des problèmes à mesure que les tables post/postmeta se développent.
Mais si vous souhaitez utiliser l'API WordPress Query, vous devez envisager d'utiliser les taxonomies pour stocker les données pour lesquelles vous souhaitez rechercher des publications.
Cela pourrait être un peu tard pour le jeu mais j'ai rencontré le même problème. Lors de la création d'un plugin pour gérer la recherche de propriétés, mon option de recherche avancée interroge jusqu'à 20 méta-entrées différentes pour chaque publication afin de trouver celles qui correspondent aux critères de recherche.
Ma solution consistait à interroger directement la base de données à l'aide de $wpdb
global. J'ai interrogé chaque méta-entrée individuellement et stocké le post_ids
des publications correspondant à chaque critère. J'ai ensuite fait une intersection sur chacun des ensembles correspondants pour obtenir le post_ids
qui correspond à tous les critères.
Mon cas était assez simple car je n'avais aucun élément OR
que je devais rendre compte, mais ils pouvaient assez facilement être inclus. En fonction de la complexité de votre requête, il s'agit d'une solution rapide et efficace. Bien que, j’admette, c’est une mauvaise option par rapport à la possibilité de faire une vraie requête relationnelle.
Le code ci-dessous a été grandement simplifié par rapport à ce que j’avais utilisé, mais vous pouvez en avoir l’idée.
class property_search{
public function get_results($args){
$potential_ids=[];
foreach($args as $key=>$value){
$potential_ids[$key]=$this->get_ids_by_query("
SELECT post_id
FROM wp_postmeta
WHERE meta_key = '".$key."'
AND CAST(meta_value AS UNSIGNED) > '".$value."'
");//a new operator would need to be created to handle each type of data and comparison.
}
$ids=[];
foreach($potential_ids as $key=>$temp_ids){
if(count($ids)==0){
$ids=$temp_ids;
}else{
$ids=array_intersect($ids,$temp_ids);
}
}
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args = array(
'posts_per_page'=> 50,
'post_type'=>'property',
'post_status'=>'publish',
'paged'=>$paged,
'post__in'=>$ids,
);
$search = new WP_Query($args);
return $search;
}
public function get_ids_by_query($query){
global $wpdb;
$data=$wpdb->get_results($query,'ARRAY_A');
$results=[];
foreach($data as $entry){
$results[]=$entry['post_id'];
}
return $results;
}
}
wp_postmeta a des index inefficaces. Voici une discussion sur ces traitements, ainsi que sur les traitements recommandés:
http://mysql.rjweb.org/doc.php/index_cookbook_mysql#speeding_up_wp_postmeta
Les salles d'opération sont vraiment chères.
Vous avez trop de clés, mais supposons que vous ne pouvez pas changer cela maintenant. L'autre chose que vous pouvez faire, sans trop de codage, est de changer le nombre de messages que vous recevez, de changer le 'posts_per_page'
en 10 ou un nombre plus important, et de voir dans quelle mesure les performances changent.