Defina Alias ​​para argumentos meta_query em get_posts ()

8

Existe uma maneira de definir um alias nos argumentos meta_query ao executar um get_posts()? Uma das minhas consultas está com um desempenho ruim. Para otimizar, só preciso reutilizar a mesma tabela unida em vez de ingressar em 3 tabelas quando apenas uma é necessária.

Meu exemplo atual ...

$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);

que basicamente se traduz em SQL direto ...

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;

No entanto, isso inclui 2 junções extras para o meta campo personalizado abc_typee, como tal, o desempenho teve um grande sucesso. Existe uma maneira de poder fazer referência ao mesmo alias para vários argumentos meta_query? Basicamente, mt1emt3 são totalmente desnecessários, eu deveria ser capaz de fazer referência à primeira postmetatabela usada com a primeira ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ). Ou, pelo menos, se eu puder definir um alias personalizado em cada um deles, eu poderia fazer referência a isso.

Uma consulta mais ideal seria ...

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;

Pensamentos?

Patas Desalinhadas
fonte
Você encontrou uma solução ou ainda é um problema em aberto?
fuxia
Este ainda é um problema em aberto. Não tenho certeza se é possível no momento. Acabei tendo que usar uma consulta MySQL direta em vez de passar get_posts().
Patas desalinhadas
1
Eu acho que é uma pergunta muito interessante. Portanto, apenas apimentei um pouco. :)
fuxia
O posts_wherefiltro pode ser útil.
Nathan Johnson

Respostas:

4

Dê uma olhada no meta_query_find_compatible_table_aliasfiltro definido em wp-includes/class-wp-meta-query.php. A documentação deste filtro:

/**
 * 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 );

É provável que a função de chamada,, find_compatible_table_aliasesteja retornando false e, portanto, a consulta crie os mt*aliases. Aqui está um exemplo de código usando esse filtro, embora eu pessoalmente defenda algo que seja um pouco mais fácil de entender. Modificar consultas como essa pode levar a muitas dores de cabeça no caminho e pode não ser aparente em todos os lugares onde a consulta está sendo confusa, especialmente se você chamar outros desenvolvedores no futuro. Dito isto...

// 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;

Isso resulta em uma consulta como

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
fonte
3

Você pode usar os filtros posts_wheree posts_joinpara modificar a consulta. Não é muito elegante, mas você poderá mexer com esses dois filtros para que seu sql seja mais otimizado. É meio brutal, mas não consigo ver uma maneira melhor na classe WP_Query. Isso não está dizendo que não há.

//* 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;
});

Provavelmente, deve haver algumas verificações para que você não modifique acidentalmente outras consultas. Isso é deixado como um exercício para o leitor.

Nathan Johnson
fonte
0

Você pode otimizar sua consulta removendo a primeira meta consulta, pois ela é redundante, da seguinte forma:

$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);

Dessa forma, você só receberá um pink puppyou large kitten, como pretende, acredito.

Quanto à otimização das consultas internas do MySQL do WordPress, acredito que você deve ficar longe disso, pois se exporia a possíveis efeitos colaterais. É melhor confiar no fato de que as consultas são armazenadas em cache e fazer um pouco mais de processamento PHP no conjunto de dados (maior). Acredito que isso levará a um melhor desempenho geral, pois o gargalo não é a quantidade de dados que você está extraindo do banco de dados, mas a dificuldade com a qual eles são coletados (quantas consultas). O PHP é bastante rápido como se fosse através de matrizes.

Então, acredito que uma situação como essa é mais rápida, considerando que a meta meta é armazenada em 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
fonte
-2

Eu não sou realmente um cara de banco de dados, mas joguei um na TV uma vez ...

Esta parte não

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 )

ser melhor substituído por

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 ))

Isso provavelmente poderia ser simplificado ainda mais ... com alguns apelidos presos no local apropriado para que você possa usar o restante da sua consulta.

Apenas um pensamento...

Rick Hellewell
fonte
1
Seu problema é que ele está usando get_posts(), então ele não está escrevendo a consulta.
Jacob Peattie
okie dokie. É por isso que é "apenas um pensamento" e por que eu não sou um cara de banco de dados.
precisa saber é o seguinte
@ RickHellewell Não tenho 100% de certeza de como isso responde à pergunta ou o que ela faz, embora seja interessante. Talvez deixe notas úteis como comentários com links para críticas para evitar os votos negativos?
Tom J Nowell
Bem, @ TomJNowell, essa resposta foi de dois anos e meio atrás ... e talvez eu tenha aprendido um pouco mais desde então, e sou (espero) ocasionalmente melhor em fornecer respostas ...
Rick Hellewell