Cea mai eficientă modalitate de a obține articole cu postmeta

11 ian. 2012, 00:18:46
Vizualizări: 64.6K
Voturi: 48

Am nevoie să obțin mai multe articole cu metadatele lor. Bineînțeles că nu poți obține metadata cu o interogare standard pentru articole, așa că de obicei trebuie să folosești get_post_custom() pentru fiecare articol.

Încerc cu o interogare personalizată, astfel:

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

Pare să funcționeze. Se împiedică dacă folosești oricare dintre aceste câmpuri meta într-un mod care permite valori meta multiple pentru același articol. Nu-mi pot imagina un join care să rezolve asta.

Deci, întrebarea 1: Există un join, sub-interogare sau altceva pentru a aduce câmpuri meta cu valori multiple?

Dar întrebarea 2: Merită efortul? Câte join-uri la tabelul postmeta pot adăuga înainte ca o abordare cu 2 interogări să devină preferabilă? Aș putea obține toate datele articolelor într-o interogare, apoi să obțin toate metadatele relevante în alta, și să combin metadatele cu datele articolelor într-un set de rezultate în PHP. Ar fi mai rapid decât o singură interogare SQL din ce în ce mai complexă, dacă acest lucru este măcar posibil?

Întotdeauna mă gândesc: "Dă cât mai multă muncă posibilă bazei de date." Nu sunt sigur în acest caz!

4
Comentarii

Nu sunt sigur dacă chiar vrei să faci join-urile. Combinația dintre get_posts() și get_post_meta() îți oferă aceleași date. De fapt, este mai puțin eficient să folosești join-uri deoarece s-ar putea să preiei date pe care nu le vei folosi mai târziu.

rexposadas rexposadas
11 ian. 2012 01:00:38

Nu sunt meta-datelor postărilor deja stocate în cache în mod automat?

Manny Fleurmond Manny Fleurmond
11 ian. 2012 03:13:23

@rxn, dacă am câteva sute de postări care se întorc (sunt un tip personalizat de postare), nu este o încărcare destul de mare pentru baza de date să folosești get_posts(), apoi get_post_meta() pentru fiecare dintre ele?

@MannyFleurmond, este greu să găsești informații clare despre caching-ul integrat al WordPress, dar din câte știu eu, ar stoca datele pe perioada unei cereri. Apelul către server pentru a prelua aceste date este un apel AJAX și nu cred că altceva le va prelua înainte.

Steve Taylor Steve Taylor
11 ian. 2012 15:01:25

De fapt, optez pentru interogări multiple și stocarea în cache a rezultatelor. Se pare că nu avem nevoie doar de meta-informațiile postărilor, inclusiv câmpuri cu valori multiple, ci și de date despre utilizatorii conectați la postări prin câmpuri meta (două seturi de acestea), plus meta-informații despre acești utilizatori. SQL pur este cu siguranță exclus!

Steve Taylor Steve Taylor
11 ian. 2012 16:20:29
Toate răspunsurile la întrebare 8
0
64

Informațiile meta ale postărilor sunt stocate automat în cache în memorie pentru un WP_Query standard (și pentru interogarea principală), cu excepția cazului în care specifici să nu se întâmple acest lucru prin utilizarea parametrului update_post_meta_cache.

Prin urmare, nu ar trebui să scrii propriile interogări pentru acest lucru.

Cum funcționează cache-ul meta pentru interogările normale:

Dacă parametrul update_post_meta_cache al WP_Query nu este setat pe false, după ce postările sunt preluate din baza de date, funcția update_post_caches() va fi apelată, care la rândul ei apelează update_postmeta_cache().

Funcția update_postmeta_cache() este un wrapper pentru update_meta_cache() și în esență apelează o simplă interogare SELECT cu toate ID-urile postărilor preluate. Aceasta va obține toate metadatele postărilor, pentru toate postările din interogare, și va salva aceste date în cache-ul de obiecte (folosind wp_cache_add()).

Când faci ceva de genul get_post_custom(), acesta verifică mai întâi cache-ul de obiecte. Deci, nu face interogări suplimentare pentru a obține metadatele postării în acest moment. Dacă ai obținut postarea într-un WP_Query, atunci metadatele sunt deja în memorie și le obții direct de acolo.

Avantajele aici sunt mult mai mari decât realizarea unei interogări complexe, dar cel mai mare avantaj vine din utilizarea cache-ului de obiecte. Dacă folosești o soluție de cache persistentă în memorie precum XCache, memcached sau APC, și ai un plugin care poate lega cache-ul de obiecte de aceasta (de exemplu, W3 Total Cache), atunci întregul cache de obiecte este deja stocat în memorie rapidă. În acest caz, sunt zero interogări necesare pentru a obține datele; acestea sunt deja în memorie. Cache-ul persistent de obiecte este minunat din multe perspective.

Cu alte cuvinte, interogarea ta este probabil mult mai lentă decât utilizarea unei interogări corecte și a unei soluții simple de memorie persistentă. Folosește WP_Query normal. Economisește-ți efortul.

Suplimentar: update_meta_cache() este inteligent, apropo. Nu va prelua metadate pentru postările care au deja metadatele în cache. Practic, nu obține aceleași metadate de două ori. Super eficient.

Suplimentar suplimentar: "Dă cât mai multă muncă bazei de date."... Nu, aceasta este web-ul. Se aplică reguli diferite. În general, dorești întotdeauna să dai cât mai puțină muncă posibilă bazei de date, dacă este fezabil. Bazele de date sunt lente sau prost configurate (dacă nu ai configurat-o tu specific, poți paria că acest lucru este adevărat). Adesea, ele sunt partajate între mai multe site-uri și supraîncărcate într-o oarecare măsură. De obicei, ai mai multe servere web decât baze de date. În general, dorești să obții datele dorite din baza de date cât mai repede și simplu posibil, apoi să le sortezi folosind codul de pe partea serverului web. Ca principiu general, desigur, fiecare caz este diferit.

24 ian. 2012 07:56:35
4
45

Aș recomanda o interogare pivot. Folosind exemplul tău:

SELECT  p.ID,   
        p.post_title, 
        MAX(CASE WHEN pm1.meta_key = 'first_field' then pm1.meta_value ELSE NULL END) as first_field,
        MAX(CASE WHEN pm1.meta_key = 'second_field' then pm1.meta_value ELSE NULL END) as second_field,
        MAX(CASE WHEN pm1.meta_key = 'third_field' then pm1.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
   p.ID,p.post_title
24 ian. 2012 07:21:46
Comentarii

Acest răspuns ar trebui marcat ca corect.

Luke Luke
13 mar. 2012 08:59:26

Dacă cauți o interogare în baza de date, acesta este răspunsul corect

Alex Seidlitz Alex Seidlitz
9 apr. 2015 01:34:01

Această interogare mi-a redus timpul de la ~25 secunde la ~3 secunde când foloseam WP_Query. Cerința mea era să o rulez doar o singură dată, așa că nu era nevoie de caching.

Kush Kush
3 iun. 2018 18:58:55

Ce interogare elegantă. Left join-urile nu au funcționat în cazul meu pentru că sunt destul de lente... asta m-a ajutat să export mii de articole în mai puțin de o secundă. +1

GDY GDY
1 dec. 2021 14:29:30
6
12

Am întâlnit un caz în care am nevoie să preiau rapid o mulțime de articole împreună cu informațiile meta asociate. Trebuie să preiau aproximativ 2000 de articole.

Am încercat folosind sugestia lui Otto - rulând WP_Query::query pentru toate articolele, apoi parcurgând și rulând get_post_custom pentru fiecare articol. Această metodă a durat, în medie, aproximativ 3 secunde pentru a se finaliza.

Apoi am încercat interogarea pivot a lui Ethan (deși nu mi-a plăcut că trebuia să cer manual fiecare meta_key care mă interesa). Tot a trebuit să parcurg toate articolele returnate pentru a deserializa meta_value. Această metodă a durat, în medie, aproximativ 1.3 secunde pentru a se finaliza.

Apoi am încercat să folosesc funcția GROUP_CONCAT și am obținut cel mai bun rezultat. Iată codul:

global $wpdb;
$wpdb->query('SET SESSION group_concat_max_len = 10000'); // necesar pentru a obține mai mult de 1024 de caractere în coloanele GROUP_CONCAT de mai jos
$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);

// transformă produsele pentru a avea un membru ->meta cu valorile deserializate, așa cum era de așteptat
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);

Această metodă a durat, în medie, 0.7 secunde. Asta înseamnă aproximativ un sfert din timpul soluției WP get_post_custom() și aproximativ jumătate din timpul soluției cu interogare pivot.

Poate va fi de interes pentru cineva.

7 oct. 2012 18:23:05
Comentarii

Aș fi interesat să văd ce rezultate obțineți cu o soluție de cache persistent pentru obiecte. Cache-ul pentru obiecte poate fi uneori mai lent în cazul de bază, în funcție de baza de date și configurația dumneavoastră, dar rezultatele din lumea reală cu majoritatea gazdelor vor oferi rezultate foarte variate. Caching-ul bazat pe memorie este incredibil de rapid.

Otto Otto
13 oct. 2012 06:07:26

Salut @Otto. Indiferent de metoda pe care o folosesc pentru a obține datele, cu siguranță vreau să cachez rezultatul. Am încercat să folosesc API-ul de transient pentru asta, dar întâmpin probleme de memorie. Șirul serializat pentru cele 2000 de obiecte ale mele ocupă ~8M și set_transient() eșuează (memorie epuizată). De asemenea, trebuie să modific setarea max_allowed_packet din MySQL. Voi încerca să le cachez în fișiere, dar nu sunt încă sigur de performanță în acest caz. Există vreo metodă de a cache în memorie care persistă între request-uri?

Trevor Mills Trevor Mills
29 oct. 2012 22:52:31

Da, dacă ai un cache persistent în memorie (XCache, memcached, APC, etc) și folosești un plugin pentru caching de obiecte (W3 Total Cache suportă multe tipuri de cache-uri în memorie), atunci acesta stochează întregul cache de obiecte în memorie, oferindu-ți o creștere de viteză de multe ori pentru aproape orice.

Otto Otto
10 nov. 2012 19:09:20

Returnez 6000 de articole pentru a le folosi într-o schemă de filtrare cu backbone/underscore js. Am reușit să reduc un query personalizat care dura 6s și pe care nu-l puteam rula ca WP_Query pentru că depășea timeout-ul, la un query de 2s. Deși array_map îl încetinește din nou destul de mult...

Jake Jake
14 dec. 2013 21:51:37

Există vreun suport pentru construirea unui sistem performant care să returneze toate metadatele în cadrul unui WP_Query?

atwellpub atwellpub
13 sept. 2014 00:45:28

Lucram la o soluție similară, păcat că SQL nu pare să poată returna array-uri sau obiecte grupate. Ai putea încerca cu select-uri imbricate, dar din experiența mea anterioară cu SQL, ele par să încetinească cererea prea mult.

Jonathan Joosten Jonathan Joosten
24 sept. 2014 10:50:51
Arată celelalte 1 comentarii
1

M-am aflat într-o situație în care a trebuit să efectuez această sarcină pentru a crea în final un document CSV. Am ajuns să lucrez direct cu MySQL pentru a realiza acest lucru. Codul meu unește tabelele post și meta pentru a prelua informațiile despre prețurile din WooCommerce, soluția postată anterior a necesitat să folosesc aliasuri pentru tabele în interogarea SQL pentru a funcționa corect.

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

Totuși, fiți atenți, WooCommerce a creat peste 300.000 de rânduri în tabela mea meta, așa că a fost foarte mare și, prin urmare, foarte lentă.

9 sept. 2015 16:19:48
Comentarii

pentru cei care încearcă să obțină performanțe mai bune din interogarea postmeta, acest lucru poate fi util: https://wordpress.stackexchange.com/a/392967/27357

Terry Kernan Terry Kernan
23 oct. 2021 08:00:12
0

VERSIUNE FĂRĂ SQL:

Obțineți toate articolele și toate valorile lor meta (metas) fără SQL:

Să presupunem că aveți o listă de ID-uri de articole stocate ca un array de ID-uri, ceva de genul:

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

Obținerea tuturor articolelor și a tuturor metadatelor într-o singură interogare nu este posibilă fără a folosi măcar un pic de SQL, așa că trebuie să facem 2 interogări (tot doar 2):

1. Obțineți toate articolele (folosind WP_Query)

$request = new WP Query([
  'post__in' => $post_ids_list,
  'ignore_sticky_posts' => true, //dacă doriți să ignorați "lipirea" articolelor
]);

(Nu uitați să apelați wp_reset_postdata(); dacă faceți o "buclă" după aceea ;) )

2. Actualizați cache-ul de meta

//nu vă confundați aici: "post" înseamnă tipul de conținut (post X user X ...), NU tipul de articol ;)
update_meta_cache('post', $post_ids_list);

Pentru a obține metadatele, folosiți standardul get_post_meta() care, după cum a menționat @Otto:
caută mai întâi în cache :)

Notă: Dacă nu aveți nevoie de alte date din articole (cum ar fi titlul, conținutul, ...) puteți face doar pasul 2. :-)

31 ian. 2017 14:27:15
0

folosind soluția lui Trevor și modificând-o să funcționeze cu SQL imbricat. Aceasta nu a fost testată.

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);
24 sept. 2014 11:01:35
0

Această întrebare a fost pusă acum aproximativ 12 ani, dar am dat peste această problemă recent când a trebuit să caut tipuri de postări cu un criteriu mai complex decât ceea ce poate fi realizat cu o interogare WP_Query de bază. Am folosit codul de mai jos pentru a face ceva foarte similar.

SELECT p.ID,p.post_title,pm1.meta_value as lat,pm2.meta_value as lng,pm3.meta_value as city,pm4.meta_value as state,pm5.meta_value as zip
FROM `wp_posts` p
LEFT JOIN `wp_postmeta` pm1 ON pm1.post_id = p.ID AND pm1.meta_key = '_lat'
LEFT JOIN `wp_postmeta` pm2 ON pm2.post_id = p.ID AND pm2.meta_key = '_lng'
LEFT JOIN `wp_postmeta` pm3 ON pm3.post_id = p.ID AND pm3.meta_key = '_city'
LEFT JOIN `wp_postmeta` pm4 ON pm4.post_id = p.ID AND pm4.meta_key = '_state'
LEFT JOIN `wp_postmeta` pm5 ON pm5.post_id = p.ID AND pm5.meta_key = '_zip'
WHERE post_type = "{posttype}" and post_status = "publish";

Alte răspunsuri la această întrebare încearcă ceva diferit față de simpla utilizare a LEFT JOIN așa cum a făcut autorul inițial. Prin câteva teste, se pare că este cea mai rapidă metodă de a obține datele.

SELECT  p.ID,   
p.post_title, 
MAX(CASE WHEN pm1.meta_key = '_lat' then pm1.meta_value ELSE NULL END) as lat,
MAX(CASE WHEN pm1.meta_key = '_lng' then pm1.meta_value ELSE NULL END) as lng,
MAX(CASE WHEN pm1.meta_key = '_city' then pm1.meta_value ELSE NULL END) as city,
MAX(CASE WHEN pm1.meta_key = '_state' then pm1.meta_value ELSE NULL END) as state,
MAX(CASE WHEN pm1.meta_key = '_zip' then pm1.meta_value ELSE NULL END) as zip
FROM    
    wp_posts p LEFT JOIN wp_postmeta pm1 ON pm1.post_id = p.ID WHERE p.post_type = '{posttype}' AND p.post_status = 'publish'                     
GROUP BY
   p.ID,p.post_title;

Această interogare este foarte similară cu un alt răspuns din acest post de Terry și Ethan și a durat 0,0228 secunde pentru a se finaliza, în timp ce prima interogare a durat aproximativ 0,0034 secunde sau de aproximativ 6,7 ori mai rapid.

Acest lucru a fost realizat pe o bază de date cu doar 322 de postări, dar când a fost testat fără a limita la tipul de postare personalizată și interogând 5234 de postări, interogarea de mai sus a obținut un timp de 0,2408s, iar prima interogare a obținut un timp de 0,0029s sau de aproximativ 83 de ori mai rapid. Nu sunt sigur cum a fost mai rapid cu 5234 de postări față de 332 de postări (am testat-o de două ori pentru a fi sigur).

TLDR: Pentru oricine caută cea mai rapidă soluție în 2024 cu o configurație implicită WordPress, utilizarea simplă a interogării LEFT JOIN de mai sus pare a fi cea mai rapidă soluție.

25 apr. 2024 00:09:04
1
-1

Am întâmpinat și eu problema câmpurilor meta cu valori multiple. Problema este în WordPress însuși. Verificați în wp-includes/meta.php. Căutați această linie:

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

Problema este cu instrucțiunea CAST. Într-o interogare pentru valori meta, variabila $meta_type este setată la CHAR. Nu cunosc detaliile despre cum afectează CAST-ul valoarea ca șir de caractere serializat, dar pentru a remedia problema, puteți elimina CAST-ul astfel încât SQL-ul să arate astfel:

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

Acum, chiar dacă această soluție funcționează, modificați componentele interne ale WordPress, așa că alte lucruri s-ar putea să nu mai funcționeze, iar soluția nu este permanentă, mai ales dacă aveți nevoie să actualizați WordPress.

Modul în care am rezolvat eu problema a fost să copiez SQL-ul generat de WordPress pentru interogarea meta dorită și apoi să scriu niște PHP pentru a adăuga instrucțiuni AND suplimentare pentru meta_valorile pe care le caut și să folosesc $wpdb->get_results($sql) pentru rezultatul final. O soluție improvizată, dar funcționează.

23 apr. 2012 22:25:59
Comentarii

Nu am încercat, dar utilizarea filtrului get_meta_sql care urmează după această linie ar fi, desigur, de preferat în loc să modificăm codul de bază.

Steve Taylor Steve Taylor
25 apr. 2012 13:29:14