Définir l'alias pour les arguments meta_query dans get_posts ()

8

Existe-t-il un moyen de définir un alias sur les arguments meta_query lors de l'exécution d'un get_posts()? L'une de mes requêtes fonctionne mal. Pour optimiser, j'ai juste besoin de pouvoir réutiliser la même table jointe au lieu de rejoindre 3 tables lorsqu'une seule est nécessaire.

Mon exemple actuel ...

$args = array(
    'meta_query' => array(
        'relation' => 'AND',
        array(
            'key' => 'abc_type',
            'value' => array('puppy', 'kitten'),
            'compare' => 'IN',
        ),
        array(
            'relation' => 'OR',
            array(
                'relation' => 'AND',
                array(
                    'key' => 'abc_type',
                    'value' => 'puppy',
                    'compare' => '=',
                ),
                array(
                    'key' => 'abc_color',
                    'value' => 'pink',
                    'compare' => '=',
                ),
            ),
            array(
                'relation' => 'AND',
                array(
                    'key' => 'abc_type',
                    'value' => 'kitten',
                    'compare' => '=',
                ),
                array(
                    'key' => 'abc_size',
                    'value' => 'large',
                    'compare' => '=',
                ),
            ),
        ),
    )
);
get_posts($args);

ce qui se traduit essentiellement par ceci en SQL simple ...

SELECT posts.* FROM posts
INNER JOIN postmeta ON ( posts.ID = postmeta.post_id )
INNER JOIN postmeta AS mt1 ON ( posts.ID = mt1.post_id )
INNER JOIN postmeta AS mt2 ON ( posts.ID = mt2.post_id )
INNER JOIN postmeta AS mt3 ON ( posts.ID = mt3.post_id )
WHERE 1=1
AND
( 
  ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ) 
  AND 
  ( 
    ( 
      ( mt1.meta_key = 'abc_type' AND mt1.meta_value = 'puppy' ) 
      AND 
      ( mt2.meta_key = 'abc_color' AND mt2.meta_value > 'pink' )
    ) 
    OR 
    ( 
      ( mt3.meta_key = 'abc_type' AND mt3.meta_value = 'kitten' )
      AND
      ( mt4.meta_key = 'abc_size' AND mt4.meta_value = 'large' )
    )
  )
) AND posts.post_type = 'abc_mypost' AND ((posts.post_status = 'publish'))
GROUP BY posts.ID ORDER BY posts.post_title ASC;

Cependant, cela ajoute 2 jointures supplémentaires pour le champ méta personnalisé abc_typeet en tant que telles, les performances ont pris un grand coup. Existe-t-il un moyen de pouvoir référencer le même alias pour plusieurs arguments meta_query? Fondamentalement, mt1et mt3sont totalement inutiles, je devrais simplement pouvoir référencer le premier postmetatableau utilisé avec le premier ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ). Ou du moins, si je peux définir un alias personnalisé sur chacun d'eux, je pourrais y faire référence.

Une requête plus optimale serait ...

SELECT posts.* FROM posts
INNER JOIN postmeta ON ( posts.ID = postmeta.post_id )
INNER JOIN postmeta AS mt1 ON ( posts.ID = mt1.post_id )
INNER JOIN postmeta AS mt2 ON ( posts.ID = mt2.post_id )
WHERE 1=1
AND
( 
  ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ) 
  AND 
  ( 
    ( 
      ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value = 'puppy' ) 
      AND 
      ( mt1.meta_key = 'abc_color' AND mt1.meta_value > 'pink' )
    ) 
    OR 
    ( 
      ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value = 'kitten' )
      AND
      ( mt2.meta_key = 'abc_color' AND mt2.meta_value = 'green' )
    )
  )
) AND posts.post_type = 'abc_mypost' AND ((posts.post_status = 'publish'))
GROUP BY posts.ID ORDER BY posts.post_title ASC;

Pensées?

Pattes Scruffy
la source
Avez-vous trouvé une solution ou s'agit-il toujours d'un problème ouvert?
fuxia
C'est toujours un problème ouvert. Je ne suis pas sûr que ce soit possible pour le moment, cependant. J'ai fini par devoir utiliser une requête MySQL directe au lieu de passer par get_posts().
Scruffy Paws
1
Je pense que c'est une question très intéressante. Par conséquent, je l'ai épicé un peu. :)
fuxia
Le posts_wherefiltre pourrait être utile.
Nathan Johnson

Réponses:

4

Jetez un œil au meta_query_find_compatible_table_aliasfiltre défini dans wp-includes/class-wp-meta-query.php. La documentation de ce filtre:

/**
 * Filters the table alias identified as compatible with the current clause.
 *
 * @since 4.1.0
 *
 * @param string|bool $alias        Table alias, or false if none was found.
 * @param array       $clause       First-order query clause.
 * @param array       $parent_query Parent of $clause.
 * @param object      $this         WP_Meta_Query object.
 */
return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this );

Il est probable que la fonction appelante find_compatible_table_aliasretourne false et donc la requête crée les mt*alias. Voici un exemple de code utilisant ce filtre, bien que je plaide personnellement pour quelque chose d'un peu plus facile à comprendre. La modification de requêtes comme celle-ci peut entraîner des tonnes de maux de tête sur la route et il peut ne pas être évident du tout où la requête est gâchée, surtout si vous faites appel à d'autres développeurs à l'avenir. Cela dit...

// Reuse the same alias for the abc_type meta key.
function pets_modify_meta_query( $alias, $meta_query ) {
    if ( 'abc_type' === $meta_query['key'] ) {
        return 'mt1';
    }

    return $alias;
}

// Filter the query.
add_filter( 'meta_query_find_compatible_table_alias', 'pets_modify_meta_query', 10, 2 );

$args = array(
    'meta_query' => array(
        'relation' => 'AND',
        array(
            'key' => 'abc_type',
            'value' => array('puppy', 'kitten'),
            'compare' => 'IN',
        ),
        array(
            'relation' => 'OR',
            array(
                'relation' => 'AND',
                array(
                    'key' => 'abc_type',
                    'value' => 'puppy',
                    'compare' => '=',
                ),
                array(
                    'key' => 'abc_color',
                    'value' => 'pink',
                    'compare' => '=',
                ),
            ),
            array(
                'relation' => 'AND',
                array(
                    'key' => 'abc_type',
                    'value' => 'kitten',
                    'compare' => '=',
                ),
                array(
                    'key' => 'abc_size',
                    'value' => 'large',
                    'compare' => '=',
                ),
            ),
        ),
    )
);

$q = new WP_Query($args);
echo '<pre>', print_r($q->request, true); die;

Il en résulte une requête comme

SELECT SQL_CALC_FOUND_ROWS
    wp_posts.ID
FROM wp_posts
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 )
WHERE
    1=1
AND
(
    ( mt1.meta_key = 'abc_type' AND mt1.meta_value IN ('puppy','kitten') )
    AND
    (
        (
            ( mt1.meta_key = 'abc_type' AND mt1.meta_value = 'puppy' )
            AND
            ( wp_postmeta.meta_key = 'abc_color' AND wp_postmeta.meta_value = 'pink' )
        )
        OR
        (
            ( mt1.meta_key = 'abc_type' AND mt1.meta_value = 'kitten' )
            AND
            ( mt1.meta_key = 'abc_size' AND mt1.meta_value = 'large' )
        )
    )
)
AND
    wp_posts.post_type = 'post'
AND (
    wp_posts.post_status = 'publish'
    OR
    wp_posts.post_status = 'future'
    OR
    wp_posts.post_status = 'draft'
    OR wp_posts.post_status = 'pending'
)
GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 10
phatskat
la source
3

Vous pouvez utiliser les filtres posts_whereet posts_joinpour modifier la requête. Ce n'est pas très élégant, mais vous devriez pouvoir jouer avec ces deux filtres pour que votre sql soit plus optimisé. C'est une sorte de force brute, mais je ne vois pas de meilleure façon dans la classe WP_Query. Cela ne veut pas dire qu'il n'y en a pas.

//* Make sure to not suppress filters
$args = array(
  'suppress_filters' => false,
  //* rest of args unchanged
);

add_filter( 'posts_where', function( $sql ) {
  $sql = str_replace(
    "( mt1.meta_key = 'abc_type' AND mt1.meta_value = 'puppy' )",
    "( postmeta.meta_key = 'abc_type' AND postmeta.meta_value = 'puppy' )",
    $sql
  );

  $sql = str_replace(
    "( mt3.meta_key = 'abc_type' AND mt3.meta_value = 'kitten' )",
    "( postmeta.meta_key = 'abc_type' AND postmeta.meta_value = 'kitten' )",
    $sql
  );

  $sql = str_replace( [ 'mt2', 'mt4' ], [ 'mt1', 'mt2' ], $sql );
  return $sql;
});

add_filter( 'posts_join', function( $sql ) {
  $sql = str_replace(
    " INNER JOIN wp_postmeta AS mt4 ON ( wp_posts.ID = mt4.post_id )",
    "",
    $sql
  );
  $sql = str_replace(
    " INNER JOIN wp_postmeta AS mt3 ON ( wp_posts.ID = mt3.post_id )",
    "",
    $sql
  );
  return $sql;
});

Il devrait probablement y avoir des vérifications afin de ne pas modifier accidentellement d'autres requêtes. Cela reste un exercice pour le lecteur.

Nathan Johnson
la source
0

Vous pouvez optimiser votre requête en supprimant la première méta-requête car elle est redondante, comme ceci:

$args = array(
    'meta_query' => array(
        'relation' => 'OR',
        array(
            'relation' => 'AND',
            array(
                'key' => 'abc_type',
                'value' => 'puppy',
                'compare' => '=',
            ),
            array(
                'key' => 'abc_color',
                'value' => 'pink',
                'compare' => '=',
            ),
        ),
        array(
            'relation' => 'AND',
            array(
                'key' => 'abc_type',
                'value' => 'kitten',
                'compare' => '=',
            ),
            array(
                'key' => 'abc_size',
                'value' => 'large',
                'compare' => '=',
            ),
        ),
    ),
);
get_posts($args);

De cette façon, vous n'obtiendrez que l'un pink puppyou l' autre large kitten, comme vous le souhaitez, je crois.

En ce qui concerne l'optimisation des requêtes MySQL internes de WordPress, je pense que vous devriez rester à l'écart de cela car vous vous exposeriez à des effets secondaires possibles. Vous feriez mieux de compter sur le fait que les requêtes sont mises en cache et effectuent un traitement PHP supplémentaire sur l'ensemble de données (plus grand). Je crois que cela conduira à de meilleures performances dans l'ensemble car le goulot d'étranglement n'est pas la quantité de données que vous extrayez de la base de données, mais la difficulté avec laquelle elles sont collectées (combien de requêtes). PHP est assez rapide en passant par les tableaux.

Je pense donc qu'une situation comme celle-ci est plus rapide, étant donné que la méta post est mise en cache:

$args = array(
    'meta_query' => array( 
        array(
            'key' => 'abc_type',
            'value' => array('puppy', 'kitten'),
            'compare' => 'IN',
        ),
    ),
);

$final_posts = array();
foreach( $get_posts($args) as $post ) {
    if ( 'puppy' === get_post_meta( $post->ID, 'abc_type', true ) ) {
        if ( 'pink' === get_post_meta( $post->ID, 'abc_color', true ) ) {
            $final_posts[] = $post;
        }
    } else {
        // This is definitely a kitten
        if ( 'large' === get_post_meta( $post->ID, 'abc_size', true ) ) {
            $final_posts[] = $post;
        }
    }
}
Vlad Olaru
la source
-2

Je ne suis pas vraiment un type de base de données, mais j'en ai joué un à la télévision une fois ...

Cette partie ne serait-elle pas

SELECT posts.* FROM posts
INNER JOIN postmeta ON ( posts.ID = postmeta.post_id )
INNER JOIN postmeta AS mt1 ON ( posts.ID = mt1.post_id )
INNER JOIN postmeta AS mt2 ON ( posts.ID = mt2.post_id )

être mieux remplacé par

SELECT posts.* FROM posts
INNER JOIN postmeta ON (( posts.ID = postmeta.post_id ) and 
( posts.ID = mt1.post_id ) and
( posts.ID = mt2.post_id ))

Cela pourrait probablement être encore plus simplifié ... avec un alias coincé là au bon endroit afin que vous puissiez utiliser le reste de votre requête.

Juste une pensée...

Rick Hellewell
la source
1
Son problème est qu'il utilise get_posts(), donc il n'écrit pas lui-même la requête.
Jacob Peattie
Oki Doki. C'est pourquoi c'est «juste une pensée», et pourquoi je ne suis pas un type de base de données.
Rick Hellewell
@RickHellewell Je ne sais pas à 100% comment cela répond à la question ou ce qu'il fait, bien que ce soit intéressant. Peut-être laisser des notes utiles sous forme de commentaires reliant des informations essentielles pour éviter les votes négatifs?
Tom J Nowell
Eh bien, @TomJNowell, cette réponse était il y a 2 ans et demi .... et j'ai peut-être appris un peu plus depuis, et je suis (espérons-le) parfois mieux à même de fournir des réponses ....
Rick Hellewell