get_posts()でmeta_query引数のエイリアスを設定します


8

を実行するときにmeta_query引数にエイリアスを設定する方法はありget_posts()ますか?クエリの1つでパフォーマンスが低下しています。最適化するには、1つだけ必要なときに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

3

posts_whereおよびposts_joinフィルターを使用して、クエリを変更できます。あまりエレガントではありませんが、SQLがより最適化されるように、これら2つのフィルターをいじることができるはずです。それは一種の総当たりですが、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られません。

WordPressの内部MySQLクエリを最適化することに関しては、起こりうる副作用にさらされるため、それを避けておくべきだと思います。クエリがキャッシュされ、(より大きな)データセットに対してさらに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

私は本当にデータベースの人ではありませんが、テレビで1回プレイしました...

この部分ではないでしょうか

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()しているため、クエリを自分で作成していないことです。
Jacob Peattie 2017

okie dokie。それが「単なる考え」であり、私がデータベースの専門家ではない理由です。
Rick Hellewell、2018年

@RickHellewell興味深いですが、これが質問にどのように答えるか、何をするかは100%わかりません。おそらく、反対票を避けるために要点にリンクするコメントとして役立つメモを残しますか?
トムJノーウェル

さて、@ TomJNowell、その答えは2 1/2年前でした。そして、おそらくそれ以降、私はもう少し学び、そして(うまくいけば)答えを提供するのがたまにうまくいくと思います...
Rick Hellewell
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.