web-dev-qa-db-fra.com

Meilleure façon d'obtenir des statistiques de balises?

J'ai plus de 4000 messages. J'essaie d'interroger toutes les publications et d'obtenir le nombre de balises que contient chaque publication. Le nombre de posts s'affiche correctement lorsque post_per_page est inférieur à 2000 mais au-delà de 2000, le délai d'expiration de la requête. Il montre simplement "0" pour tous.

Code

  $args = array(
    'posts_per_page' => 4000,
    'post_status'  => 'publish',
  );

  $zerotags = 0;
  $onetag = 0;
  $twotags = 0;
  $morethantwo = 0;
  $sixtags_plus = 0;

  $query = new WP_Query( $args );
  while ( $query->have_posts() ) : $query->the_post();

     $posttags = get_the_tags();
     $tag_count = 0;

     foreach($posttags as $tag) {
       $tag_count++;
     }
     if ($tag_count == 0) {
       $zerotags++;
     }
     if ($tag_count == 1) {
       $onetag++;
     }
     if ($tag_count == 2) {
       $twotags++;
     }
     if ($tag_count > 2 && $tag_count < 6) {
       $morethantwo++;
     }
     if ($tag_count >= 6) {
       $sixtags_plus++;
     }

 endwhile;

 echo 'Zero Tags : '.$zerotags.'posts';
 echo 'One Tag : '.$onetag.'posts';
 echo 'Two Tags : '.$twotags.'posts';
 echo 'More than 2 and less than 6 : '.$morethantwo.'posts';
 echo 'More than 6 tags : '.$sixtags_plus.'posts';

Existe-t-il une meilleure approche pour interroger ceci afin que le délai d'attente ne se produise pas?

4
user1437251

J'ai abordé un problème similaire il n'y a pas si longtemps - tout est en mémoire:

$post_ids = get_posts(
    array(
        'posts_per_page' => -1,
        'post_status'  => 'publish',
        'fields' => 'ids', // Just grab IDs instead of pulling 1000's of objects into memory
    )
);

update_object_term_cache( $post_ids, 'post' ); // Cache all the post terms in one query, memory should be ok

foreach ( $post_ids as $post_id ) {
    if ( ! $tags = get_object_term_cache( $post_id, 'post_tag' ) ) {
       $zerotags++;
    } else {
        $tag_count = count( $tags );

        if ( $tag_count === 1 ) {
            $onetag++;
        } elseif ( $tag_count === 2 ) {
            $twotags++;
        } elseif ( $tag_count >= 6 ) {
            $sixtags_plus++;
        }

        if ( $tag_count > 2 && $tag_count < 6 ) {
            $morethantwo++;
        }
    }
}

Mise à jour: Passez de get_the_tags à get_object_term_cache - sinon nous perdons tout notre travail! (L’ancien touche get_post, qui frappe la base de données à chaque itération et envoie l’objet post en mémoire - les accessoires @Pieter Goosen).

Mise à jour 2: Le deuxième argument de update_object_term_cache devrait être le type post , pas la taxonomie.

6
TheDeadMedic

Vous frappez la base de données avec un ouragan de 500 km/h, il n’est donc pas étonnant que votre requête expire.

Voici une idée ou deux pour accélérer les choses

  • Ajoutez 'fields' => 'ids', à vos arguments WP_Query. Cela accélérera considérablement votre requête. Cela ne renverra que l'identifiant de l'article, et c'est la seule chose dont vous avez réellement besoin

  • Utilisez wp_get_post_terms() pour obtenir les balises de publication. Le troisième paramètre prend un tableau d'arguments, l'un beign fields que vous pouvez également définir pour ne renvoyer que ids, ce qui accélérera également votre requête car il renverra également simplement l'ID de balise et non l'objet de balise complet.

  • Utilisez les éléments transitoires pour enregistrer vos résultats et les vider lorsqu'un nouveau message est publié ou lorsqu'un message est supprimé, non supprimé ou mis à jour. Utilisez transition_post_status

EDIT- IDEA CODE DE TRANSITION

Configurer la fonction pour supprimer le tansient si un nouveau message est publié, ou si un message est supprimé, non supprimé ou mis à jour

Dans votre functions.php

add_action( 'transition_post_status', function ()
{
        global $wpdb;
        $wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient%_tag_list_%')" );
        $wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient_timeout%_tag_list_%')" );
});

Obtenez le nombre de balises et ajoutez-le à un transitoire

function get_term_post_count( $taxonomy = 'post_tag', $post_type = 'post' )
{
    if ( false === ( $total_counts = get_transient( 'tag_list_' . md5( $taxonomy . $post_type ) ) ) ) {

        if ( !taxonomy_exists( $taxonomy ) )
            return $total_counts = null;

        $args = [
            'nopaging' => true, //Gets all posts
            'fields' => 'ids'
        ];
        $q = new WP_Query( $args );

        if ( empty( $q->posts ) )
              return $total_counts = null;

        update_object_term_cache( $q->posts, $post_type );

        foreach ( $q->posts as $single_post ) {

            $tags = get_object_term_cache( $single_post, $taxonomy );

            if ( empty( $tags ) ) {
                $no_tags[] = $single_post;
            } else {
                $count = count( $tags );
                if ( $count == 1 ) {
                    $one[] = $single_post;
                 } elseif ( $count == 2 ) {
                     $two[] = $single_post;
                 } elseif ( $count >= 3 && $count <= 6 ) {
                     $more_than_two[] = $single_post;
                 } elseif ( $count > 6 ) {
                      $more_than_six[] = $single_post;
                 }
            }
        }

       $total_counts = [
            'none' => isset( $no_tags ) ? ( (int) count( $no_tags ) ) : 0,
            'one' => isset( $one ) ? ( (int) count( $one ) ) : 0,
            'two' => isset( $two ) ? ( (int) count( $two ) ) : 0,
            'more_than_two' => isset( $more_than_two ) ? ( (int) count( $more_than_two ) ) : 0,
            'more_than_six' => isset( $more_than_six ) ? ( (int) count( $more_than_six) ) : 0
        ];


    set_transient( 'tag_list_' . md5( $taxonomy . $post_type ), $total_counts, 24 * HOUR_IN_SECONDS );

    return $total_counts;
}

Vous pouvez utiliser la fonction comme suit dans votre modèle

$q = get_term_post_count();
if ( $q !== null ) {
     echo 'Zero Tags : '.$q['none'].'posts </br>'; 
     echo 'One Tag : '.$q['one'].'posts </br>';
     echo 'Two Tags : '.$q['two'].'posts </br>';
     echo 'More than 2 and less than 6 : '.$q['more_than_two'].'posts </br>';
     echo 'More than 6 tags : '.$q['more_than_six'].'posts </br>';
}

QUELQUES NOTES IMPORTANTES

  • Le code ci-dessus n'a pas été testé et pourrait être un buggy

  • Nécessite PHP 5.4 +

  • Le premier paramètre est $taxonomy. Vous pouvez transmettre n'importe quelle taxonomie au code, la surdité est post_tag. Le deuxième paramètre est $post_type qui est défini sur la valeur par défaut post. Vous pouvez passer n'importe quel type de post au paramètre

  • Modifier et abuser comme bon vous semble

EDIT 1

Correction de quelques bugs mineurs, le code est maintenant testé et fonctionne

EDIT 2 - TEST DE PERFORMANCE

--- ÉCHAPPÉ ---

EDIT 3 grâce à @TheDeadMedic

J'ai aussi appris un peu de @TheDeadMedic sur update_object_term_cache et get_object_term_cache lequel augmente beaucoup la performance. J'ai mis à jour ( volé un peu de @TheDeadMedic, voté sa réponse en retour: -) ) ma réponse avec cette information. Cela frappe un peu la mémoire, mais ce problème est en partie résolu avec l'utilisation de transitoires.

Le code me donne maintenant

2 requêtes en 0.09766 secondes.

sans utiliser de transitoires

5
Pieter Goosen

Table de fréquences - requête SQL personnalisée:

Vous pouvez essayer la requête personnalisée suivante pour vos statistiques posts/terms pour une taxonomie, un statut et un type de publication donnés:

/**
 * Frequency data: Count how many posts have a given number of terms, 
 * for a given post type, post status and taxonomy.
 *
 * @param string   $taxonomy    Taxonomy slug
 * @param string   $post_status Post status (draft, publish, ...)
 * @param string   $post_type   Post type (post, page, ...)
 * @return array   Array containing freq. data with 'total' (posts) and 'term' counts
 */

function get_post_terms_stats_wpse_184993( 
    $taxonomy = 'post_tag', 
    $post_status = 'publish', 
    $post_type = 'post' 
){
    global $wpdb;
    $sql = " 
        SELECT COUNT( s.terms_per_post ) as total, s.terms_per_post 
        FROM ( 
            SELECT COUNT( tr.object_id ) terms_per_post, tr.object_id 
            FROM {$wpdb->term_relationships} tr 
            LEFT JOIN {$wpdb->term_taxonomy} tt USING( term_taxonomy_id ) 
            LEFT JOIN {$wpdb->posts} p ON p.ID = tr.object_id  
            WHERE     tt.taxonomy = '%s' 
                  AND p.post_status = '%s' 
                  AND p.post_type = '%s'
            GROUP BY tr.object_id 
         ) as s 
         GROUP by s.terms_per_post
         ORDER BY total DESC";

    return $wpdb->get_results( 
        $wpdb->prepare( $sql, $taxonomy, $post_status, $post_type ), 
        ARRAY_A 
    );
}

Exemple sur une installation avec ~ 10k posts:

Voici un exemple pour la catégorie dans publié posts :

$stats = get_post_terms_stats_wpse_184993( 
    $taxonomy    = 'category', 
    $post_status = 'publish', 
    $post_type   = 'post' 
);  

print_r( $stats );

avec la sortie suivante:

Array
(
    [0] => Array
        (
            [total] => 8173
            [terms_per_post] => 1
        )

    [1] => Array
        (
            [total] => 948
            [terms_per_post] => 2
        )

    [2] => Array
        (
            [total] => 94
            [terms_per_post] => 3
        )

    [3] => Array
        (
            [total] => 2
            [terms_per_post] => 4
        )

    [4] => Array
        (
            [total] => 1
            [terms_per_post] => 6
        )

    [5] => Array
        (
            [total] => 1
            [terms_per_post] => 8
        )

)

Nous pouvons le sortir dans un tableau HTML:

foreach( $stats as $row )
{
    $rows .= sprintf( 
        "<tr><td>%d</td><td>%d</td></tr>", 
        $row['total'], 
        $row['terms_per_post'] 
    );
}
printf( "<table><tr><th>#Posts</th><th>#Terms</th></tr>%s</table>", $rows );

avec la sortie suivante:

stats

Nous pouvons donc voir ici combien de messages ont un nombre donné de termes, dans l’ordre.

Actuellement, la requête SQL utilise temporaire et filesort, il existe donc certainement des possibilités de l'ajuster. Sur une installation de 10 000 postes, il a fallu moins de 0,2 secondes pour fonctionner sur mon petit VPS. Nous pourrions par exemple supprimer la jointure de la table des publications pour la rendre plus rapide, mais cela serait moins flexible.

Nous pouvons également mettre en cache la sortie, par exemple avec l'API transitoires , comme mentionné par @Pieter Goosen dans sa réponse.

2
birgire