Установите псевдоним для аргументов meta_query в get_posts ()

8

Есть ли способ установить псевдоним для аргументов meta_query при запуске get_posts()? Один из моих запросов работает плохо. Для оптимизации мне просто нужно иметь возможность повторно использовать одну и ту же объединенную таблицу вместо объединения в 3 таблицы, когда нужна только одна.

Мой текущий пример ...

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

что в основном переводит на это в прямом SQL ...

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;

Тем не менее, это добавление 2 дополнительных объединений для пользовательского мета-поля, abc_typeи, как следствие, эта производительность получила большой успех. Есть ли способ иметь возможность ссылаться на один и тот же псевдоним для нескольких аргументов meta_query? По сути, mt1и mt3они совершенно не нужны, я должен просто ссылаться на первую postmetaтаблицу, которая используется с первой ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ). Или, по крайней мере, если я могу установить собственный псевдоним для каждого из них, я мог бы сослаться на это.

Более оптимальный запрос будет ...

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;

Мысли?

Неряшливые лапы
источник
Вы нашли решение, или это все еще открытая проблема?
fuxia
Это все еще открытая проблема. Я не уверен, что в данный момент это возможно. В итоге мне пришлось использовать прямой MySQL-запрос вместо прохождения get_posts().
Скраффи Лапс
1
Я думаю, что это очень интересный вопрос. Поэтому я немного приправил это. :)
fuxia
posts_whereФильтр может быть полезным.
Натан Джонсон

Ответы:

4

Посмотрите на meta_query_find_compatible_table_aliasфильтр, определенный в wp-includes/class-wp-meta-query.php. Документация этого фильтра:

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

Вполне вероятно, что вызывающая функция find_compatible_table_aliasвозвращает false, и поэтому запрос создает mt*псевдонимы. Вот пример кода, использующего этот фильтр, хотя я лично рекомендовал бы кое-что, что немного легче понять. Подобные изменения запросов могут привести к множеству головных болей в будущем, и вообще может быть неочевидно, где запрос испортится, особенно если вы привлечете других разработчиков в будущем. Это сказал ...

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

Это приводит к запросу как

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
источник
3

Вы можете использовать posts_whereи posts_joinфильтры для изменения запроса. Это не очень элегантно, но вы должны иметь возможность возиться с этими двумя фильтрами, чтобы ваш sql был более оптимизирован. Это грубо, но я не вижу лучшего способа в классе WP_Query. Это не значит, что нет.

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

Там должно быть несколько проверок, чтобы случайно не изменить другие запросы. Это оставлено в качестве упражнения для читателя.

Натан Джонсон
источник
0

Вы можете оптимизировать свой запрос, удалив первый мета-запрос, так как он избыточен, например:

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

Таким образом, вы получите только либо, pink puppyлибо large kitten, как вы думаете, я полагаю.

Что касается оптимизации внутренних MySQL-запросов WordPress, я полагаю, что вам следует избегать этого, поскольку вы подвергаете себя возможным побочным эффектам. Вы бы лучше полагались на тот факт, что запросы кэшируются и выполняли бы некоторую дополнительную обработку PHP для (большего) набора данных. Я считаю, что это приведет к повышению общей производительности, поскольку узким местом является не количество данных, которые вы извлекаете из базы данных, а сложность, с которой они собираются (сколько запросов). PHP довольно быстро работает с массивами.

Поэтому я полагаю, что такая ситуация быстрее, учитывая, что мета поста кэшируется:

$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;
        }
    }
}
Влад Олару
источник
-2

Я на самом деле не парень в базе данных, но однажды я играл его по телевизору ...

Не эта часть

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 )

лучше заменить на

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

Вероятно, это можно упростить еще больше ... с некоторым псевдонимом, который застрял там в нужном месте, чтобы вы могли использовать оставшуюся часть запроса.

Просто мысль...

Рик Хеллуэлл
источник
1
Его проблема в том, что он использует get_posts(), поэтому он сам не пишет запрос.
Джейкоб Питти
Оки Доки. Вот почему это «просто мысль», и почему я не парень в базе данных.
Рик Хеллуэлл
@RickHellewell Я не уверен на 100%, как это отвечает на вопрос или что он делает, хотя это интересно. Возможно, оставить полезные заметки в виде комментариев, связанных с гистами, чтобы избежать отрицательных голосов?
Том Дж. Новелл
Ну, @TomJNowell, этот ответ был 2 с половиной года назад .... и, возможно, я узнал немного больше с тех пор, и (надеюсь) иногда лучше в предоставлении ответов ....
Рик Хеллуэлл,