Cara paling efisien untuk mendapatkan postingan dengan postmeta

36

Saya perlu mendapatkan banyak posting dengan metadata mereka. Tentu saja Anda tidak bisa mendapatkan metadata dengan kueri posting standar, jadi Anda umumnya harus melakukan a get_post_custom()untuk setiap posting.

Saya mencoba dengan satu permintaan khusus, seperti ini:

$results = $wpdb->get_results("
    SELECT  p.ID,
        p.post_title,
        pm1.meta_value AS first_field,
        pm2.meta_value AS second_field,
        pm3.meta_value AS third_field
    FROM    $wpdb->posts p LEFT JOIN $wpdb->postmeta pm1 ON (
            pm1.post_id = p.ID  AND
            pm1.meta_key    = 'first_field_key'
        ) LEFT JOIN $wpdb->postmeta pm2 ON (
            pm2.post_id = p.ID  AND
            pm2.meta_key    = 'second_field_key'
        ) LEFT JOIN $wpdb->postmeta pm3 ON (
            pm3.post_id = p.ID  AND
            pm3.meta_key    = 'third_field_key'
        )
    WHERE   post_status = 'publish'
");

Tampaknya bekerja. Itu naik jika Anda menggunakan salah satu bidang meta dengan cara yang memungkinkan beberapa nilai meta untuk itu pada posting yang sama. Saya tidak bisa memikirkan bergabung untuk melakukan itu.

Jadi, pertanyaan 1: Apakah ada gabungan, sub-kueri, atau apa pun, untuk memasukkan bidang meta bernilai ganda?

Tetapi pertanyaan 2: Apakah itu sepadan? Berapa banyak postmetagabungan tabel yang saya tambahkan sebelum pendekatan 2-kueri lebih disukai? Saya bisa mengambil semua data pos dalam satu kueri, lalu mengambil semua postmeta yang relevan di yang lain, dan menggabungkan meta dengan data posting dalam satu resultset di PHP. Apakah itu akhirnya menjadi lebih cepat daripada satu query SQL yang semakin kompleks, jika itu mungkin?

Saya selalu berpikir, "Berikan sebanyak mungkin pekerjaan ke basis data." Tidak yakin dengan yang ini!

Steve Taylor
sumber
Saya tidak yakin apakah Anda ingin bergabung. kombinasi get_posts () dan get_post_meta () memberi Anda data yang sama kembali. Bahkan, kurang efisien menggunakan gabungan karena Anda mungkin mengambil data yang tidak akan Anda gunakan nanti.
rexposadas
2
Bukankah memposting data meta di-cache secara otomatis?
Manny Fleurmond
@ rxn, jika saya memiliki beberapa ratus posting kembali (mereka adalah jenis posting kustom), tentu itu adalah beban DB yang cukup berat get_posts(), lalu get_post_meta()untuk setiap orang? @ MannyFleurmond, sulit untuk menemukan info sulit tentang caching bawaan WP, tetapi AFAIK akan menyimpan hal-hal cache per permintaan. Panggilan ke server untuk mengambil data ini adalah panggilan AJAX, dan saya tidak berpikir hal lain akan mengambil hal-hal sebelumnya.
Steve Taylor
Sebenarnya, saya akan mencari beberapa pertanyaan dan menyimpan hasilnya. Ternyata kita tidak hanya membutuhkan meta pos, termasuk bidang yang memiliki beberapa nilai, kita juga membutuhkan data tentang pengguna yang terhubung ke pos melalui bidang meta (dua set ini), ditambah meta pengguna pada mereka. SQL murni jelas keluar dari jendela!
Steve Taylor

Jawaban:

58

Informasi pos meta secara otomatis di-cache dalam memori untuk standar WP_Query(dan permintaan utama), kecuali Anda secara khusus mengatakannya untuk tidak melakukannya dengan menggunakan update_post_meta_cacheparameter.

Karena itu, Anda tidak boleh menulis pertanyaan Anda sendiri untuk ini.

Cara kerja cache meta untuk kueri normal:

Jika update_post_meta_cacheparameter ke WP_Querytidak disetel ke false, maka setelah posting diambil dari DB, maka update_post_caches()fungsi akan dipanggil, yang pada gilirannya panggilan update_postmeta_cache().

The update_postmeta_cache()fungsi pembungkus untuk update_meta_cache(), dan itu pada dasarnya panggilan sederhana SELECTdengan semua ID dari tulisan diambil. Ini akan membuatnya mendapatkan semua postmeta, untuk semua posting di kueri, dan menyimpan data itu dalam cache objek (menggunakan wp_cache_add()).

Ketika Anda melakukan sesuatu seperti get_post_custom(), itu memeriksa objek cache terlebih dahulu. Jadi itu tidak membuat pertanyaan tambahan untuk mendapatkan meta pos pada saat ini. Jika Anda mendapatkan pos di a WP_Query, maka meta sudah ada dalam memori dan langsung mendapatkannya dari sana.

Keuntungan di sini berkali-kali lebih besar daripada membuat kueri yang kompleks, tetapi keuntungan terbesar berasal dari penggunaan cache objek. Jika Anda menggunakan solusi cache memori persisten seperti XCache atau memcached atau APC atau sesuatu seperti itu, dan memiliki plugin yang dapat mengikat cache objek Anda ke sana (W3 Total Cache, misalnya), maka seluruh cache objek Anda disimpan dalam memori cepat sudah. Dalam hal ini, ada nol query yang diperlukan untuk mengambil data Anda; itu sudah ada dalam memori. Caching objek yang persisten luar biasa dalam banyak hal.

Dengan kata lain, kueri Anda mungkin memuat dan memuat lebih lambat daripada menggunakan kueri yang tepat dan solusi memori persisten sederhana. Gunakan yang normal WP_Query. Selamatkan diri Anda sedikit usaha.

Tambahan: update_meta_cache() cerdas, BTW. Itu tidak akan mengambil informasi meta untuk posting yang sudah memiliki informasi meta yang di-cache. Tidak mendapatkan meta yang sama dua kali, pada dasarnya. Sangat efisien.

Tambahan tambahan: "Berikan sebanyak mungkin pekerjaan ke basis data." ... Tidak, ini web. Aturan yang berbeda berlaku. Secara umum, Anda selalu ingin memberikan sesedikit mungkin pekerjaan ke database, jika layak. Database lambat atau tidak terkonfigurasi dengan baik (jika Anda tidak mengonfigurasinya secara spesifik, Anda dapat bertaruh uang baik bahwa ini benar). Seringkali mereka dibagi di antara banyak situs, dan kelebihan beban sampai batas tertentu. Biasanya Anda memiliki lebih banyak server web daripada database. Secara umum, Anda hanya ingin mendapatkan data yang Anda inginkan dari DB secepat dan sesederhana mungkin, kemudian lakukan penyortiran dengan menggunakan kode sisi-server-web. Sebagai prinsip umum, tentu saja, kasus yang berbeda semuanya berbeda.

Otto
sumber
30

Saya akan merekomendasikan kueri pivot. Menggunakan contoh Anda:

SELECT  p.ID,   
        p.post_title, 
        MAX(CASE WHEN wp_postmeta.meta_key = 'first_field' then wp_postmeta.meta_value ELSE NULL END) as first_field,
        MAX(CASE WHEN wp_postmeta.meta_key = 'second_field' then wp_postmeta.meta_value ELSE NULL END) as second_field,
        MAX(CASE WHEN wp_postmeta.meta_key = 'third_field' then wp_postmeta.meta_value ELSE NULL END) as third_field,

 FROM    wp_posts p LEFT JOIN wp_postmeta pm1 ON ( pm1.post_id = p.ID)                      
GROUP BY
   wp_posts.ID,wp_posts.post_title
Ethan Seifert
sumber
Jawaban ini harus ditandai dengan benar.
Luke
Jika Anda mencari kueri basis data, inilah jawaban yang benar
Alex Popov
Kueri ini mengurangi waktu saya ketika saya menggunakan WP_Query dari ~ 25 detik hingga ~ 3 detik. Persyaratan saya adalah memecat ini hanya sekali sehingga caching tidak diperlukan.
Kush
11

Saya telah menemukan sebuah kasus di mana saya ingin juga ingin cepat mengambil banyak posting dengan informasi meta yang terkait. Saya perlu mengambil posting O (2000).

Saya mencoba menggunakan saran Otto - menjalankan WP_Query :: permintaan untuk semua posting, dan kemudian mengulang dan menjalankan get_post_custom untuk setiap posting. Ini membutuhkan, rata-rata, sekitar 3 detik untuk selesai .

Saya kemudian mencoba permintaan pivot Ethan (meskipun saya tidak suka harus secara manual meminta setiap meta_key yang saya minati). Saya masih harus mengulang semua posting yang diambil untuk membatalkan pengunaan meta_value. Ini membutuhkan, rata-rata, sekitar 1,3 detik untuk selesai .

Saya kemudian mencoba menggunakan fungsi GROUP_CONCAT, dan menemukan hasil terbaik. Ini kodenya:

global $wpdb;
$wpdb->query('SET SESSION group_concat_max_len = 10000'); // necessary to get more than 1024 characters in the GROUP_CONCAT columns below
$query = "
    SELECT p.*, 
    GROUP_CONCAT(pm.meta_key ORDER BY pm.meta_key DESC SEPARATOR '||') as meta_keys, 
    GROUP_CONCAT(pm.meta_value ORDER BY pm.meta_key DESC SEPARATOR '||') as meta_values 
    FROM $wpdb->posts p 
    LEFT JOIN $wpdb->postmeta pm on pm.post_id = p.ID 
    WHERE p.post_type = 'product' and p.post_status = 'publish' 
    GROUP BY p.ID
";

$products = $wpdb->get_results($query);

// massages the products to have a member ->meta with the unserialized values as expected
function massage($a){
    $a->meta = array_combine(explode('||',$a->meta_keys),array_map('maybe_unserialize',explode('||',$a->meta_values)));
    unset($a->meta_keys);
    unset($a->meta_values);
    return $a;
}

$products = array_map('massage',$products);

Ini memakan waktu rata-rata 0,7 detik . Itu sekitar seperempat waktu dari solusi get_post_custom (WP) dan sekitar setengah dari solusi kueri pivot.

Mungkin ini akan menarik bagi seseorang.

Trevor Mills
sumber
Saya akan tertarik pada hasil apa yang Anda dapatkan dengan solusi cache objek yang persisten. Cache objek kadang-kadang akan lebih lambat untuk kasus dasar, tergantung pada database dan konfigurasi Anda, tetapi hasil dunia nyata dengan mayoritas host akan memberikan hasil yang sangat bervariasi. Caching berbasis memori sangat cepat.
Otto
Hei @Otto. Terlepas dari metode apa yang saya gunakan untuk mendapatkan data, saya pasti ingin men-cache hasil. Saya sudah mencoba menggunakan API sementara untuk melakukannya, tapi saya mengalami masalah memori. String serial untuk objek 2000 saya jam di ~ 8M dan set_transient () gagal (kehabisan memori). Juga, harus mengubah pengaturan MySQL max_allowed_packet. Saya akan melihat caching ke file, tapi saya belum yakin kinerja di sana. Apakah ada cara untuk cache ke memori yang bertahan di seluruh permintaan?
Trevor Mills
Ya, jika Anda memiliki cache memori persisten (XCache, memcached, APC, dll), dan menggunakan plugin caching objek (W3 Total Cache mendukung banyak jenis cache memori), maka ia menyimpan semua cache objek dalam memori, memberi Anda speedup berkali-kali lipat dari segalanya.
Otto
Saya mengembalikan 6000 item untuk digunakan dalam skema penyaringan js backbone / garis bawah. Ini mengambil kueri kustom 6s yang bahkan tidak bisa saya jalankan sebagai WP_Query karena waktunya habis, dan menjadikannya kueri 2s. Meskipun array_map memperlambatnya kembali sedikit ...
Jake
Apakah ada dukungan untuk membangun dukungan kinerja tinggi untuk mengembalikan semua data meta dalam WP_Query?
atwellpub
2

Saya menemukan diri saya dalam situasi dimana saya perlu melakukan tugas ini untuk akhirnya membuat dokumen CSV, saya akhirnya bekerja secara langsung dengan mysql untuk melakukan ini. Kode saya bergabung dengan posting dan tabel meta untuk mengambil informasi harga woocommerce, solusi yang diposting sebelumnya mengharuskan saya menggunakan alias tabel di sql untuk bekerja dengan baik.

SELECT p.ID, p.post_title, 
    MAX(CASE WHEN pm1.meta_key = '_price' then pm1.meta_value ELSE NULL END) as price,
    MAX(CASE WHEN pm1.meta_key = '_regular_price' then pm1.meta_value ELSE NULL END) as regular_price,
    MAX(CASE WHEN pm1.meta_key = '_sale_price' then pm1.meta_value ELSE NULL END) as sale_price,
    MAX(CASE WHEN pm1.meta_key = '_sku' then pm1.meta_value ELSE NULL END) as sku
    FROM wp_posts p LEFT JOIN wp_postmeta pm1 ON ( pm1.post_id = p.ID)                 
    WHERE p.post_type in('product', 'product_variation') AND p.post_status = 'publish'
    GROUP BY p.ID, p.post_title

Berhati-hatilah, woocommerce membuat 300.000 baris di tabel meta saya, jadi itu sangat besar, dan karenanya sangat lambat.

Terry Kernan
sumber
1

TANPA SQL VERSI:

Dapatkan semua posting dan semua nilai meta (meta) mereka tanpa SQL:

Katakanlah Anda memiliki daftar ID kiriman yang disimpan sebagai larik ID, kira-kira seperti itu

$post_ids_list = [584, 21, 1, 4, ...];

Sekarang mendapatkan semua posting dan semua meta dalam 1 kueri tidak mungkin tanpa menggunakan setidaknya sedikit SQL, jadi kita harus melakukan 2 query (masih hanya 2):

1. Dapatkan semua posting (menggunakan WP_Query )

$request = new WP Query([
  'post__in' => $post_ids_list,
  'ignore_sticky_posts' => true, //if you want to ignore the "stickiness"
]);

(Jangan lupa menelepon wp_reset_postdata();jika Anda melakukan "putaran" sesudahnya;))

2. Perbarui meta cache

//don't be confused here: "post" means content type (post X user X ...), NOT post type ;)
update_meta_cache('post', $post_ids_list);

Untuk mendapatkan data meta cukup gunakan standar get_post_meta()yang, seperti yang ditunjukkan @Otto:
lihat ke cache terlebih dahulu :)

Catatan: Jika Anda tidak benar-benar membutuhkan data lain dari pos (seperti judul, konten, ...) Anda dapat melakukannya hanya 2. :-)

jave.web
sumber
0

menggunakan bentuk solusi trevor dan memodifikasinya untuk bekerja dengan SQL bersarang. Ini tidak diuji.

global $wpdb;
$query = "
    SELECT p.*, (select pm.* From $wpdb->postmeta AS pm WHERE pm.post_id = p.ID)
    FROM $wpdb->posts p 
    WHERE p.post_type = 'product' and p.post_status = 'publish' 
";
$products = $wpdb->get_results($query);
Jonathan Joosten
sumber
-1

Saya berlari ke masalah beberapa bidang meta nilai juga. Masalahnya adalah dengan WordPress itu sendiri. Lihat di wp-include / meta.php. Cari baris ini:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value );

Masalahnya dengan pernyataan CAST. Dalam kueri untuk nilai meta, variabel $ meta_type disetel ke CHAR. Saya tidak tahu detail tentang bagaimana CASTing nilai ke CHAR mempengaruhi string serial, tetapi untuk memperbaikinya, Anda bisa menghapus para pemain sehingga SQL terlihat seperti ini:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "$alias.meta_value {$meta_compare} {$meta_compare_string})", $meta_value );

Sekarang, meskipun itu berhasil, Anda mucking dengan internal WordPress, sehingga hal-hal lain mungkin rusak, dan itu bukan perbaikan permanen, dengan asumsi Anda harus meng-upgrade WordPress.

Cara saya memperbaikinya adalah dengan menyalin SQL yang dihasilkan oleh WordPress untuk kueri meta yang saya inginkan dan kemudian menulis beberapa PHP untuk menempel pada pernyataan AND tambahan untuk meta_values ​​yang saya cari dan gunakan $ wpdb-> get_results ($ sql ) untuk hasil akhir. Meretas, tetapi berhasil.

Harry Love
sumber
Saya belum mencobanya, tetapi meningkatkan get_meta_sqlfilter yang mengikuti baris ini tentu saja lebih baik daripada meretas kode inti.
Steve Taylor