web-dev-qa-db-fra.com

Optimiser les termes de taxonomie multiple Requête MySQL?

( Note du modérateur: Le titre était à l'origine: "requête/optimisation de la base de données")

J'écris une fonction pour un panneau de recherche "filtre" personnalisé qui permet aux utilisateurs de sélectionner des termes à partir de quatre taxonomies personnalisées. J'exécute les requêtes directement sur la base de données et la requête prend en moyenne une demi-seconde à exécuter (avec les termes des quatre taxonomies et un résultat renvoyé).

Cela me semble assez lent. Je me demandais si je pouvais faire quelque chose pour optimiser la requête ou même la base de données afin de rendre cela plus efficace/plus rapide. Peut-être écrire une vue, même? J'ai de l'expérience avec MS-SQL, mais pas beaucoup avec MySQL et je ne suis pas certain de la différence.

Voici mon code de fonction:

    function filter_resources($phase,$wa,$aus,$topics){
    global $wpdb;
    $querystr="
    SELECT * 
        FROM $wpdb->posts A
            LEFT JOIN $wpdb->term_relationships B ON(A.ID = B.object_id)
            LEFT JOIN $wpdb->term_taxonomy C ON(B.term_taxonomy_id = C.term_taxonomy_id)
            LEFT JOIN $wpdb->terms D ON(C.term_id = D.term_id)

        LEFT JOIN $wpdb->term_relationships BB ON(A.ID = BB.object_id)
            LEFT JOIN $wpdb->term_taxonomy CC ON(BB.term_taxonomy_id = CC.term_taxonomy_id)
            LEFT JOIN $wpdb->terms DD ON(CC.term_id = DD.term_id)

        LEFT JOIN $wpdb->term_relationships BBB ON(A.ID = BBB.object_id)
            LEFT JOIN $wpdb->term_taxonomy CCC ON(BBB.term_taxonomy_id = CCC.term_taxonomy_id)
            LEFT JOIN $wpdb->terms DDD ON(CCC.term_id = DDD.term_id)

        LEFT JOIN $wpdb->term_relationships BBBB ON(A.ID = BBBB.object_id)
            LEFT JOIN $wpdb->term_taxonomy CCCC ON(BBBB.term_taxonomy_id = CCCC.term_taxonomy_id)
            LEFT JOIN $wpdb->terms DDDD ON(CCCC.term_id = DDDD.term_id)

        WHERE A.post_type = 'resources' 
            AND A.post_status = 'publish'
            AND C.taxonomy = 'phase-of-learning'
            AND D.term_id = '$phase'
            AND CC.taxonomy = 'wa-curriculum'
            AND DD.term_id = '$wa'
            AND CCC.taxonomy = 'australian-curriculum'
            AND DDD.term_id = '$aus'
            AND CCCC.taxonomy = 'topics'
            AND DDDD.term_id = '$topics'
        ORDER BY A.post_date DESC";
    return $wpdb->get_results($querystr,OBJECT);
}

Merci!

4
goatlady

Bien que ce soit vraiment une question MySQL, il est utile de comprendre le schéma SQL de WordPress et j'aime également essayer d'optimiser les requêtes SQL, alors plutôt que de vous envoyer vers StackOverflow je vais essayer de vous répondre ici. Vous voudrez peut-être quand même l'afficher là-bas pour avoir d'autres opinions.

Et bien que je ne comprenne pas bien votre exigence, je pense comprendre ce que vous demandez, mais je voudrais présenter ce qui suit pour voir si cela répond mieux à vos besoins. Je n'ai pas vos données, donc c'est un peu difficile pour moi de dire que cela fonctionne, mais comme je l'ai dit, je pense que cela répond à vos besoins:

function filter_resources($phase,$wa,$aus,$topics){
  global $wpdb;
  $sql =<<<SQL
SELECT
  t.slug,p.*
FROM
  wp_posts p
  INNER JOIN wp_term_relationships tr ON p.ID=tr.object_id
  INNER JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  INNER JOIN wp_terms t ON tt.term_id = t.term_id
WHERE 1=1
  AND p.post_type = 'resources'
  AND p.post_status = 'publish'
  AND t.term_id IN (%d,%d,%d,%d)
  AND CONCAT(tt.taxonomy,'/',t.term_id) IN (
    'phase-of-learning/%s',
    'wa-curriculum/%s',
    'australian-curriculum/%s',
    'topics/%s'
  )
GROUP BY
  p.ID
HAVING
  COUNT(*)=4
ORDER BY
  p.post_date DESC
SQL;
  $sql = $wpdb->prepare($sql,
    $phase,$wa,$aus,$topics,  // For the %d replacements
    $phase,$wa,$aus,$topics   // For the %s replacements
  );
  $results = $wpdb->get_results($sql,OBJECT);
  return $results;
}

Fondamentalement, cela vous donne toutes les publications dans lesquelles tous vos termes de taxonomie sont appliqués. Pour ce faire, une requête de forme libre permet de faire correspondre toutes les publications auxquelles la taxonomie/termes sont appliqués, mais limite uniquement les publications pour lesquelles tous les termes sont appliqués. Groupement par wp_post.ID et recherche tous les enregistrements pour lesquels le poste est rejoint 4 fois. Lorsque vous exécutez MySQL EXPLAIN, l’optimisation est plutôt satisfaisante comparée à celle que vous aviez; beaucoup moins de tables jointes. Espérons que c'était la logique dont vous aviez besoin.

Mise en cache avec l'API Transients

Et si vous essayez d’améliorer les performances, vous pouvez également envisager de mettre en cache les résultats dans un "transitoire" pendant un temps limité (1 heure, 4 heures, 12 heures ou plus?)} _ Ce billet de blog explique comment utiliser l'API WordPress Transients :

Voici la logique de base pour les transitoires:

define('NUM_HOURS',4); // Time to cache set for your use case
$data = get_transient( 'your_transient_key' );
if( !$data ) {
  $data = // Do something to get your data here
  set_transient( 'your_transient_key', $data, 60 * 60 * NUM_HOURS );
}  

Pour utiliser les transitoires dans votre fonction filter_resources(), cela pourrait ressembler à ceci:

define('RESOURCE_CACHE_HOURS',4);
function filter_resources($phase,$wa,$aus,$topics){
  $resources = get_transient( 'yoursite_filtered_resources' );
  if(!$resources) {
    global $wpdb;
    $sql =<<<SQL
SELECT
  t.slug,p.*
FROM
  wp_posts p
  INNER JOIN wp_term_relationships tr ON p.ID=tr.object_id
  INNER JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  INNER JOIN wp_terms t ON tt.term_id = t.term_id
WHERE 1=1
  AND p.post_type = 'resources'
  AND p.post_status = 'publish'
  AND t.term_id IN (%d,%d,%d,%d)
  AND CONCAT(tt.taxonomy,'/',t.term_id) IN (
    'phase-of-learning/%s',
    'wa-curriculum/%s',
    'australian-curriculum/%s',
    'topics/%s'
  )
GROUP BY
  p.ID
HAVING
  COUNT(*)=4
ORDER BY
  p.post_date DESC
SQL;
    $sql = $wpdb->prepare($sql,
      $phase,$wa,$aus,$topics,  // For the %d replacements
      $phase,$wa,$aus,$topics   // For the %s replacements
    );
    $resources = $wpdb->get_results($sql,OBJECT);
    $hours = RESOURCE_CACHE_HOURS * 60 * 60;
    set_transient( 'yoursite_filtered_resources', $resources, $hours);
  }  
  return $resources;
}

METTRE À JOUR

Voici une autre version du code qui tente de gérer les cas dans lesquels moins de quatre critères sont sélectionnés par l'utilisateur:

define('RESOURCE_CACHE_HOURS',4);
function filter_resources($phase,$wa,$aus,$topics){
  $resources = get_transient( 'yoursite_filtered_resources' );
  if(!$resources) {
    $terms = $taxterms = array();
    if (!empty($phase))
      $taxterms[$phase] = 'phase-of-learning/%s';
    if (!empty($wa)) 
      $taxterms[$wa] = 'wa-curriculum/%s';
    if (!empty($aus))
      $taxterms[$aus] = 'axustralian-curriculum/%s';
    if (!empty($topics))
      $taxterms[$topics] = 'topics/%s';
    $count = count($taxterms);
    $having = ($count==0 ? '' : "HAVING COUNT(*)={$count}");
    $values = array_keys(array_flip($tax_terms));
    $values = array_merge($values,$values);  // For %d and $s
    $taxterms =  implode("','",$taxterms);
    $terms = implode(',',array_fill(0,$count,'d%'));
    global $wpdb;
    $sql =<<<SQL
SELECT
  t.slug,p.*
FROM
  wp_posts p
  INNER JOIN wp_term_relationships tr ON p.ID=tr.object_id
  INNER JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  INNER JOIN wp_terms t ON tt.term_id = t.term_id
WHERE 1=1
  AND p.post_type = 'resources'
  AND p.post_status = 'publish'
  AND t.term_id IN ({$terms})
  AND CONCAT(tt.taxonomy,'/',t.term_id) IN ('{$taxterms}')
GROUP BY
  p.ID
{$having}
ORDER BY
  p.post_date DESC
SQL;
    $sql = $wpdb->prepare($sql,$values);
    $resources = $wpdb->get_results($sql,OBJECT);
    $hours = RESOURCE_CACHE_HOURS * 60 * 60;
    set_transient( 'yoursite_filtered_resources', $resources, $hours);
  }  
  return $resources;
}
6
MikeSchinkel

À moins que vous ne deviez rester compatible avec la version antérieure de WP 3.0, vous pouvez simplement exploiter les requêtes de taxonomie avancées support dans WP 3.1.

Le code qui génère le code SQL peut être trouvé dans wp-includes/taxonomy.php

5
scribu

D'abord et avant tout, utilisez des jointures internes, pas des jointures à gauche. Les jointures à gauche forceront un plan de requête qui analyse toute la table des publications jusqu'à ce qu'il trouve une publication correspondante dans vos filtres de terme.

Deuxièmement, vous pouvez réduire le nombre de jointures nécessaires en effectuant une pré-extraction des termes à l'aide de get_term ().

En combinant les deux, votre requête devient quelque chose comme:

SELECT * 
FROM $wpdb->posts posts
JOIN $wpdb->term_relationships termA
ON posts.ID = termA.object_id
AND termA.term_taxonomy_id = $termA_taxid
JOIN $wpdb->term_relationships termB
ON posts.ID = termB.object_id
AND termB.term_taxonomy_id = $termB_taxid
JOIN $wpdb->term_relationships termC
ON posts.ID = termC.object_id
AND termC.term_taxonomy_id = $termC_taxid
JOIN $wpdb->term_relationships termD
ON posts.ID = termD.object_id
AND termD.term_taxonomy_id = $termD_taxid
WHERE ...

Ce qui devrait donner les mêmes résultats avec 5 tables jointes au lieu de 13, et avec un plan de requête qui commence par rechercher les publications liées à l'un des termes qui surviennent le moins fréquemment dans term_relationships.

1
Denis de Bernardy