Establecer alias para argumentos meta_query en get_posts ()

8

¿Hay alguna manera de establecer un alias en los argumentos meta_query cuando se ejecuta un get_posts()? Una de mis consultas está funcionando mal. Para optimizar, solo necesito poder reutilizar la misma tabla unida en lugar de unirme en 3 tablas cuando solo se necesita una.

Mi ejemplo actual ...

$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 básicamente se traduce a esto en SQL directo ...

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;

Sin embargo, esto está agregando 2 combinaciones adicionales para el metacampo personalizado abc_typey, como tal, el rendimiento ha tenido un gran éxito. ¿Hay alguna manera de poder hacer referencia al mismo alias para múltiples argumentos meta_query? Básicamente, mt1y mt3son totalmente innecesarios, debería poder hacer referencia a la primera postmetatabla que se usa con la primera ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ). O al menos si puedo establecer un alias personalizado en cada uno de estos, podría hacer referencia a eso.

Una consulta más óptima sería ...

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;

Pensamientos?

Patas desaliñadas
fuente
¿Encontró una solución o sigue siendo un problema abierto?
fuxia
Esto sigue siendo un problema abierto. Sin embargo, no estoy seguro de que sea posible en este momento. Terminé teniendo que usar una consulta directa de MySQL en lugar de pasar get_posts().
Patas desaliñadas
1
Creo que es una pregunta muy interesante. Por lo tanto, solo lo condimenté un poco. :)
fuxia
El posts_wherefiltro puede ser útil.
Nathan Johnson el

Respuestas:

4

Echa un vistazo al meta_query_find_compatible_table_aliasfiltro definido en wp-includes/class-wp-meta-query.php. La documentación de este 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 );

Es probable que la función de llamada, find_compatible_table_aliasdevuelva false y, por lo tanto, la consulta crea los mt*alias. Aquí hay un código de muestra que usa este filtro, aunque personalmente recomendaría algo que sea un poco más fácil de entender. Modificar consultas como esta puede generar toneladas de dolores de cabeza en el futuro y puede que no sea evidente en absoluto dónde la consulta se desordenará, especialmente si trae otros desarrolladores en el futuro. Dicho eso ...

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

Esto da como resultado una 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
fuente
3

Puede usar los filtros posts_wherey posts_joinpara modificar la consulta. No es muy elegante, pero debería poder meterse con estos dos filtros para que su sql esté más optimizado. Es una especie de fuerza bruta, pero no puedo ver una mejor manera en la clase WP_Query. Sin embargo, eso no quiere decir que no haya.

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

Probablemente debería haber algunas comprobaciones allí para que no esté modificando accidentalmente otras consultas. Eso queda como ejercicio para el lector.

Nathan Johnson
fuente
0

Puede optimizar su consulta eliminando la primera meta consulta ya que es redundante, de esta manera:

$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 esta manera solo obtendrás uno pink puppyo large kitten, según tu intención, creo.

En cuanto a la optimización de las consultas internas de MySQL de WordPress, creo que debe mantenerse alejado de eso, ya que se expondría a posibles efectos secundarios. Sería mejor confiar en el hecho de que las consultas se almacenan en caché y realizan más procesamiento PHP en el conjunto de datos (más grande). Creo que esto conducirá a un mejor rendimiento general, ya que el cuello de botella no es la cantidad de datos que extrae de la base de datos, sino la dificultad con la que se recopila (cuántas consultas). PHP es bastante rápido como viene a través de matrices

Entonces, creo que una situación como esta es más rápida, teniendo en cuenta que el meta de la publicación se almacena en caché:

$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
fuente
-2

Realmente no soy un tipo de base de datos, pero jugué uno en la televisión una vez ...

¿No sería esta parte

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 mejor reemplazado con

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

Eso probablemente podría simplificarse aún más ... con algunos alias pegados allí en el lugar adecuado para que pueda usar el resto de su consulta.

Solo un pensamiento...

Rick Hellewell
fuente
1
Su problema es que está usando get_posts(), por lo que no está escribiendo la consulta él mismo.
Jacob Peattie
de acuerdo ♥. Es por eso que es 'solo un pensamiento', y por qué no soy un tipo de base de datos.
Rick Hellewell el
@RickHellewell No estoy 100% seguro de cómo esto responde la pregunta o qué hace, aunque es interesante. ¿Quizás dejar notas útiles como comentarios vinculados a lo esencial para evitar los votos negativos?
Tom J Nowell
Bueno, @TomJNowell, esa respuesta fue hace 2 años y medio ... y tal vez he aprendido un poco más desde entonces, y estoy (con suerte) ocasionalmente mejor en el suministro de respuestas ...
Rick Hellewell