Setel Alias ​​untuk argumen meta_query di get_posts ()

8

Apakah ada cara untuk mengatur alias pada argumen meta_query saat menjalankan get_posts()? Salah satu pertanyaan saya berkinerja buruk. Untuk mengoptimalkan saya hanya perlu dapat menggunakan kembali tabel bergabung yang sama alih-alih bergabung dalam 3 tabel ketika hanya satu yang diperlukan.

Contoh saya saat ini ...

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

yang pada dasarnya diterjemahkan ke dalam ...

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;

Namun, ini menambahkan 2 penggabungan tambahan untuk bidang meta khusus abc_typedan karena itu kinerja telah mendapat pukulan besar. Apakah ada cara untuk dapat merujuk alias yang sama untuk beberapa argumen meta_query? Pada dasarnya, mt1dan mt3sama sekali tidak perlu, saya seharusnya hanya bisa referensi postmetatabel pertama yang digunakan dengan yang pertama ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ). Atau setidaknya jika saya dapat mengatur alias kustom pada masing-masing ini saya bisa referensi itu.

Kueri yang lebih optimal adalah ...

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;

Pikiran?

Cakar Berantakan
sumber
Apakah Anda menemukan solusi, atau ini masih merupakan masalah terbuka?
fuxia
Ini masih merupakan masalah terbuka. Saya tidak yakin itu mungkin saat ini. Saya akhirnya harus menggunakan query MySQL langsung daripada melalui get_posts().
Cakar Berantakan
1
Saya pikir itu pertanyaan yang sangat menarik. Karena itu saya sedikit membumbui itu. :)
fuxia
The posts_whereFilter mungkin berguna.
Nathan Johnson

Jawaban:

4

Lihat meta_query_find_compatible_table_aliasfilter yang ditentukan dalam wp-includes/class-wp-meta-query.php. Dokumentasi filter ini:

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

Kemungkinan fungsi panggil find_compatible_table_alias,, mengembalikan false dan dengan demikian kueri membuat mt*alias. Berikut adalah beberapa kode contoh menggunakan filter ini, meskipun saya pribadi akan menyarankan sesuatu yang sedikit lebih mudah dimengerti. Memodifikasi kueri seperti ini dapat menyebabkan berton-ton sakit kepala dan mungkin tidak terlihat sama sekali di mana kueri semakin kacau, terutama jika Anda membawa pengembang lain di masa depan. Yang mengatakan ...

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

Ini menghasilkan permintaan seperti

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

Anda dapat menggunakan posts_wheredan posts_joinmemfilter untuk memodifikasi kueri. Ini tidak terlalu elegan, tetapi Anda harus dapat mengacaukan kedua filter ini sehingga sql Anda lebih optimal. Ini agak kasar, tapi saya tidak bisa melihat cara yang lebih baik di kelas WP_Query. Itu tidak mengatakan tidak ada.

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

Mungkin harus ada beberapa pemeriksaan di sana sehingga Anda tidak secara tidak sengaja memodifikasi pertanyaan lain. Itu dibiarkan sebagai latihan untuk pembaca.

Nathan Johnson
sumber
0

Anda bisa mengoptimalkan kueri dengan menghapus kueri meta pertama karena mubazir, seperti:

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

Dengan cara ini Anda hanya akan mendapatkan salah satu pink puppyatau large kitten, seperti yang Anda inginkan, saya percaya.

Adapun untuk mengoptimalkan query MySQL internal WordPress, saya percaya Anda harus tetap jelas tentang hal itu karena Anda akan mengekspos diri Anda terhadap kemungkinan efek samping. Anda akan lebih baik mengandalkan fakta bahwa kueri di-cache dan melakukan lebih banyak pemrosesan PHP pada kumpulan data (lebih besar). Saya percaya ini akan mengarah pada kinerja yang lebih baik secara keseluruhan karena bottleneck bukanlah jumlah data yang Anda ekstrak dari database, tetapi kesulitan dengan mana ia dikumpulkan (berapa banyak pertanyaan). PHP cukup cepat karena datang melalui array.

Jadi saya percaya situasi seperti ini lebih cepat, mengingat meta posting di-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
sumber
-2

Saya bukan orang yang punya basis data, tapi saya pernah memainkannya di TV ...

Bukankah bagian ini

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 )

lebih baik diganti dengan

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

Itu mungkin dapat disederhanakan bahkan .. dengan beberapa alias terjebak di sana di tempat yang tepat sehingga Anda dapat menggunakan sisa permintaan Anda.

Hanya pemikiran saja...

Rick Hellewell
sumber
1
Masalahnya adalah dia menggunakan get_posts(), jadi dia tidak menulis kueri sendiri.
Jacob Peattie
oke. Itu sebabnya itu 'hanya pemikiran', dan mengapa saya bukan orang basis data.
Rick Hellewell
@ RickHellewell Saya tidak 100% yakin bagaimana ini menjawab pertanyaan atau apa fungsinya, meskipun menarik. Mungkin meninggalkan catatan bermanfaat sebagai komentar yang menghubungkan ke intisari untuk menghindari downvotes?
Tom J Nowell
Nah, @TomJNowell, jawaban itu 2 1/2 tahun yang lalu .... dan mungkin saya telah belajar sedikit lebih banyak sejak itu, dan saya (semoga) kadang-kadang lebih baik dalam memberikan jawaban ....
Rick Hellewell