Modo più efficiente per ottenere i post con i postmeta

11 gen 2012, 00:18:46
Visualizzazioni: 64.6K
Voti: 48

Ho bisogno di ottenere un gruppo di post con i loro metadati. Ovviamente non è possibile ottenere i metadati con una query standard dei post, quindi generalmente si deve eseguire un get_post_custom() per ogni post.

Sto provando con una query personalizzata, in questo modo:

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

Sembra funzionare. Va in crisi se si utilizza uno di questi campi meta in modo da consentire valori meta multipli per lo stesso post. Non riesco a pensare a un join per gestire questo caso.

Quindi, domanda 1: Esiste un join, una sub-query o qualsiasi altra soluzione per includere campi meta con valori multipli?

Ma domanda 2: Ne vale la pena? Quanti join alla tabella postmeta devo aggiungere prima che un approccio a 2 query diventi preferibile? Potrei ottenere tutti i dati dei post in una query, poi prendere tutti i postmeta pertinenti in un'altra e combinare i meta con i dati dei post in un unico risultato in PHP. Sarebbe più veloce di una singola query SQL sempre più complessa, ammesso che sia possibile?

Di solito penso "Affida il più possibile il lavoro al database". Non ne sono sicuro in questo caso!

4
Commenti

Non sono sicuro che tu voglia effettivamente fare i join. La combinazione di get_posts() e get_post_meta() ti restituisce gli stessi dati. In effetti, è meno efficiente usare i join poiché potresti recuperare dati che non utilizzerai in seguito.

rexposadas rexposadas
11 gen 2012 01:00:38

I metadati dei post non vengono memorizzati automaticamente nella cache comunque?

Manny Fleurmond Manny Fleurmond
11 gen 2012 03:13:23

@rxn, se ho diverse centinaia di post in risposta (sono un custom post type), sicuramente è un carico piuttosto pesante per il DB fare get_posts(), poi get_post_meta() per ognuno di questi?

@MannyFleurmond, è difficile trovare informazioni precise sulla cache integrata di WP, ma per quanto ne so memorizzerebbe i dati per richiesta. La chiamata al server per recuperare questi dati è una chiamata AJAX e non credo che nient'altro recupererà dati prima di essa.

Steve Taylor Steve Taylor
11 gen 2012 15:01:25

In realtà, sto optando per query multiple e memorizzando i risultati nella cache. Si è scoperto che non abbiamo bisogno solo dei meta dei post, inclusi i campi che hanno valori multipli, ma anche dei dati sugli utenti collegati ai post tramite campi meta (due set di questi), più i meta degli utenti stessi. SQL puro è decisamente fuori discussione!

Steve Taylor Steve Taylor
11 gen 2012 16:20:29
Tutte le risposte alla domanda 8
0
64

Le informazioni sui meta dei post vengono memorizzate automaticamente nella cache per una normale WP_Query (e per la query principale), a meno che non si specifichi esplicitamente di non farlo utilizzando il parametro update_post_meta_cache.

Pertanto, non dovresti scrivere query personalizzate per questo scopo.

Come funziona la memorizzazione nella cache dei meta per le query normali:

Se il parametro update_post_meta_cache della WP_Query non è impostato su false, dopo che i post sono stati recuperati dal database, verrà chiamata la funzione update_post_caches(), che a sua volta chiama update_postmeta_cache().

La funzione update_postmeta_cache() è un wrapper per update_meta_cache(), e sostanzialmente esegue un semplice SELECT con tutti gli ID dei post recuperati. Questo permette di ottenere tutti i meta dei post, per tutti i post nella query, e salvare tali dati nella cache degli oggetti (utilizzando wp_cache_add()).

Quando fai qualcosa come get_post_custom(), prima controlla la cache degli oggetti. Quindi non esegue query aggiuntive per ottenere i meta dei post in questo momento. Se hai ottenuto il post tramite una WP_Query, i meta sono già in memoria e vengono recuperati direttamente da lì.

I vantaggi qui sono molte volte superiori rispetto alla creazione di una query complessa, ma il vantaggio maggiore deriva dall'utilizzo della cache degli oggetti. Se usi una soluzione di caching persistente in memoria come XCache, memcached, APC o simili, e hai un plugin che può collegare la tua cache degli oggetti ad essa (ad esempio W3 Total Cache), allora l'intera cache degli oggetti è già memorizzata in memoria veloce. In tal caso, sono necessarie zero query per recuperare i tuoi dati; sono già in memoria. Il caching persistente degli oggetti è fantastico sotto molti aspetti.

In altre parole, la tua query è probabilmente molto più lenta rispetto all'utilizzo di una query corretta e una semplice soluzione di memoria persistente. Usa la normale WP_Query. Risparmiati un po' di fatica.

Nota aggiuntiva: update_meta_cache() è intelligente, tra l'altro. Non recupera le informazioni sui meta per i post che hanno già i meta memorizzati nella cache. In pratica, non ottiene due volte gli stessi meta. Super efficiente.

Nota ulteriormente aggiuntiva: "Affida più lavoro possibile al database."... No, questo è il web. Si applicano regole diverse. In generale, si vuole sempre affidare il minimo lavoro possibile al database, se è fattibile. I database sono lenti o mal configurati (se non li hai configurati specificamente, puoi scommetterci che è vero). Spesso sono condivisi tra molti siti e sovraccaricati in una certa misura. Di solito hai più server web che database. In generale, vuoi semplicemente ottenere i dati che ti interessano dal database nel modo più veloce e semplice possibile, quindi elaborarli utilizzando il codice lato server web. Come principio generale, ovviamente, casi diversi sono tutti diversi.

24 gen 2012 07:56:35
4
45

Consiglierei una query pivot. Usando il tuo esempio:

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 gen 2012 07:21:46
Commenti

Questa risposta dovrebbe essere contrassegnata come corretta.

Luke Luke
13 mar 2012 08:59:26

Se stai cercando una query per il database, questa è la risposta corretta

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

Questa query ha ridotto il mio tempo da ~25 sec a ~3 sec quando stavo usando WP_Query. La mia necessità era di eseguirla solo una volta, quindi non era necessaria alcuna cache.

Kush Kush
3 giu 2018 18:58:55

Che query elegante. I left join non funzionavano nel mio caso perché sono piuttosto lenti... questo mi ha aiutato a esportare migliaia di post in meno di un secondo. +1

GDY GDY
1 dic 2021 14:29:30
6
12

Mi sono imbattuto in un caso in cui ho bisogno di recuperare rapidamente un gran numero di post con le relative meta informazioni. Devo recuperare circa O(2000) post.

Ho provato utilizzando il suggerimento di Otto - eseguendo WP_Query::query per tutti i post e poi ciclando e richiamando get_post_custom per ogni post. Questo ha richiesto, in media, circa 3 secondi per completarsi.

Ho poi provato la query pivot di Ethan (anche se non mi piaceva dover chiedere manualmente ogni meta_key di interesse). Ho comunque dovuto ciclare tutti i post recuperati per deserializzare il meta_value. Questo ha richiesto, in media, circa 1.3 secondi per completarsi.

Ho infine provato utilizzando la funzione GROUP_CONCAT, ottenendo il risultato migliore. Ecco il codice:

global $wpdb;
$wpdb->query('SET SESSION group_concat_max_len = 10000'); // necessario per ottenere più di 1024 caratteri nelle colonne GROUP_CONCAT sotto
$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);

// elabora i prodotti per avere un membro ->meta con i valori deserializzati come previsto
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);

Questo ha richiesto in media 0.7 secondi. Circa un quarto del tempo della soluzione WP get_post_custom() e circa la metà della soluzione con query pivot.

Spero possa essere utile a qualcuno.

7 ott 2012 18:23:05
Commenti

Sarei interessato a vedere quali risultati otterresti con una soluzione di cache oggetti persistente. La cache oggetti a volte può essere più lenta nel caso base, a seconda del tuo database e della configurazione, ma i risultati nel mondo reale con la maggior parte degli host mostreranno risultati molto variabili. La cache basata sulla memoria è incredibilmente veloce.

Otto Otto
13 ott 2012 06:07:26

Ciao @Otto. Indipendentemente dal metodo che uso per ottenere i dati, voglio assolutamente memorizzare nella cache il risultato. Ho provato a usare l'API transient per farlo, ma sto riscontrando problemi di memoria. La stringa serializzata per i miei 2000 oggetti occupa circa ~8M e set_transient() fallisce (memoria esaurita). Inoltre, devo modificare l'impostazione max_allowed_packet di MySQL. Valuterò l'opzione di memorizzarli su file, ma non sono ancora sicuro delle prestazioni in quel caso. Esiste un modo per memorizzare nella cache in memoria che persista tra le richieste?

Trevor Mills Trevor Mills
29 ott 2012 22:52:31

Sì, se hai una cache di memoria persistente (XCache, memcached, APC, ecc.) e usi un plugin per la cache oggetti (W3 Total Cache supporta molti tipi di cache in memoria), allora memorizza tutta la cache oggetti in memoria, garantendoti un aumento di velocità di svariate volte praticamente per tutto.

Otto Otto
10 nov 2012 19:09:20

Sto restituendo 6000 elementi da utilizzare in uno schema di filtraggio con backbone/underscore js. Questo ha trasformato una query personalizzata di 6s che non potevo nemmeno eseguire come WP_Query perché andava in timeout, in una query di 2s. Anche se l'array_map la rallenta di nuovo parecchio...

Jake Jake
14 dic 2013 21:51:37

Esiste un supporto per costruire soluzioni ad alte prestazioni per restituire tutti i metadati all'interno di una WP_Query?

atwellpub atwellpub
13 set 2014 00:45:28

Stavo lavorando a una soluzione simile, peccato che SQL non sembri in grado di restituire array o oggetti raggruppati. Potresti provare con select annidati, ma dalla mia precedente esperienza con SQL, sembrano rallentare troppo la richiesta.

Jonathan Joosten Jonathan Joosten
24 set 2014 10:50:51
Mostra i restanti 1 commenti
1

Mi sono trovato in una situazione in cui avevo bisogno di svolgere questo compito per creare alla fine un documento CSV, alla fine ho lavorato direttamente con MySQL per farlo. Il mio codice unisce le tabelle dei post e dei meta per recuperare le informazioni sui prezzi di WooCommerce, la soluzione precedentemente pubblicata richiedeva che utilizzassi degli alias di tabella nella query SQL per funzionare correttamente.

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

Tuttavia, attenzione: WooCommerce ha creato più di 300.000 righe nella mia tabella meta, quindi era molto grande e di conseguenza molto lenta.

9 set 2015 16:19:48
Commenti

per chi sta cercando di ottenere ulteriori miglioramenti nelle prestazioni della query postmeta, anche questo: https://wordpress.stackexchange.com/a/392967/27357

Terry Kernan Terry Kernan
23 ott 2021 08:00:12
0

VERSIONE SENZA SQL:

Ottieni tutti i post e tutti i loro valori meta (metadati) senza usare SQL:

Supponiamo che tu abbia una lista di ID di post memorizzati come un array di ID, qualcosa come:

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

Ottenere tutti i post e tutti i metadati in una singola query non è possibile senza usare almeno un po' di SQL, quindi dobbiamo fare 2 query (sempre e solo 2):

1. Ottieni tutti i post (usando WP_Query)

$request = new WP Query([
  'post__in' => $post_ids_list,
  'ignore_sticky_posts' => true, //se vuoi ignorare i post "in evidenza"
]);

(Non dimenticare di chiamare wp_reset_postdata(); se stai per fare un "loop" dopo ;) )

2. Aggiorna la cache dei metadati

//non confonderti qui: "post" si riferisce al tipo di contenuto (post X utente X ...), NON al post type ;)
update_meta_cache('post', $post_ids_list);

Per ottenere i metadati usa semplicemente la funzione standard get_post_meta() che, come ha sottolineato @Otto:
cerca prima nella cache :)

Nota: Se non ti servono altri dati dei post (come titolo, contenuto, ...) puoi fare solo il punto 2. :-)

31 gen 2017 14:27:15
0

utilizzando la soluzione di Trevor e modificandola per funzionare con SQL annidato. Questo codice non è stato testato.

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 set 2014 11:01:35
0

questa domanda è stata posta circa 12 anni fa, ma mi sono imbattuto in questo problema recentemente quando ho dovuto cercare tipi di post con criteri più complessi di quelli gestibili con una semplice WP_Query. Ho utilizzato il codice qui sotto per fare qualcosa di molto simile.

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

Altre risposte a questa domanda provano approcci diversi dall'uso del LEFT JOIN come fatto originariamente dall'autore. Dopo alcuni test, sembra effettivamente essere il modo più veloce per ottenere i dati.

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;

Questa query è molto simile a un'altra risposta in questo post di Terry ed Ethan e ha impiegato 0.0228 secondi per completarsi, mentre la prima query ha impiegato circa 0.0034 secondi, ovvero circa 6.7 volte più veloce.

Il test è stato eseguito su un database con solo 322 post, ma quando testato senza limitarlo al custom post type e interrogando 5234 post, la query sopra ha ottenuto un tempo di 0.2408s mentre la prima query ha ottenuto 0.0029s, ovvero circa 83 volte più veloce. Non sono sicuro del perché sia stata più veloce con 5234 post rispetto a 332 post (ho testato due volte per esserne certo).

TLDR: Per chiunque cerchi la soluzione più veloce nel 2024 con un setup WordPress standard, usare semplicemente la query LEFT JOIN sopra sembra essere la soluzione più performante.

25 apr 2024 00:09:04
1
-1

Anch'io mi sono imbattuto nel problema dei campi meta con valori multipli. Il problema è nello stesso WordPress. Guarda in wp-includes/meta.php. Cerca questa riga:

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

Il problema è con l'istruzione CAST. In una query per i valori meta, la variabile $meta_type è impostata su CHAR. Non conosco i dettagli su come il CAST del valore in CHAR influisca sulla stringa serializzata, ma per risolverlo, puoi rimuovere il cast così che l'SQL risulti così:

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

Ora, anche se funziona, stai modificando gli interni di WordPress, quindi altre cose potrebbero rompersi, e non è una soluzione permanente, supponendo che dovrai aggiornare WordPress.

Il modo in cui l'ho risolto è copiare l'SQL generato da WordPress per la meta query che desidero e poi scrivere del PHP per aggiungere ulteriori istruzioni AND per i meta_values che sto cercando e usare $wpdb->get_results($sql) per l'output finale. Un po' rozzo, ma funziona.

23 apr 2012 22:25:59
Commenti

Non l'ho provato, ma sfruttare il filtro get_meta_sql che segue questa riga sarebbe ovviamente preferibile rispetto a modificare il codice core.

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