Il modo più efficace per ottenere post con postmeta


36

Ho bisogno di ricevere un sacco di post con i loro metadati. Ovviamente non puoi ottenere metadati con una query di post standard, quindi in genere devi fare 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. Si inciampa se si utilizza uno di questi meta-campi in un modo che consente più metadati per esso sullo stesso post. Non riesco a pensare a un join per farlo.

Quindi, domanda 1: esiste un join, una sottoquery o qualsiasi altra cosa per portare in meta field a più valori?

Ma domanda 2: ne vale la pena? Quanti postmetajoin di tabella devo aggiungere prima che diventi preferibile un approccio a 2 query? Potrei prendere tutti i dati di post in una query, quindi prendere tutti i postmeta rilevanti in un altro e combinare il meta con i dati di post in un gruppo di risultati in PHP. Sarebbe più veloce di una singola query SQL sempre più complessa, se ciò fosse possibile?

Penso sempre: "Dai più lavoro possibile al database". Non sono sicuro riguardo a questo!


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

2
I metadati post non vengono comunque memorizzati automaticamente nella cache?
Manny Fleurmond,

@rxn, se dovessi tornare diverse centinaia di post (sono un tipo di post personalizzato), sicuramente è un carico di DB piuttosto pesante get_posts(), quindi get_post_meta()per ognuno di quelli? @MannyFleurmond, è difficile trovare informazioni utili sulla memorizzazione nella cache integrata di WP, ma AFAIK memorizzerebbe roba per richiesta. La chiamata al server per acquisire questi dati è una chiamata AJAX e non credo che qualcos'altro afferrerà cose prima di esso.
Steve Taylor,

In realtà, vado per più query e memorizzo nella cache i risultati. Si scopre che non abbiamo solo bisogno di post meta, compresi campi con più valori, ma anche di dati sugli utenti collegati ai post tramite meta field (due set di questi), oltre a meta user su di essi. Pure SQL è decisamente fuori dalla finestra!
Steve Taylor,

Risposte:


58

Le meta-informazioni post vengono automaticamente memorizzate nella memoria cache per uno standard WP_Query(e la query principale), a meno che non sia stato espressamente indicato di non farlo utilizzando il update_post_meta_cacheparametro

Pertanto, non dovresti scrivere le tue domande per questo.

Come funziona la meta cache per le normali query:

Se il update_post_meta_cacheparametro su WP_Querynon è impostato su false, dopo che i post sono stati recuperati dal DB, update_post_caches()verrà chiamata la funzione, che a sua volta chiama update_postmeta_cache().

La update_postmeta_cache()funzione è un wrapper per update_meta_cache(), ed essenzialmente chiama un semplice SELECTcon tutti gli ID dei post recuperati. Ciò consentirà di ottenere tutti i postmeta, per tutti i post nella query, e di salvare i dati nella cache degli oggetti (usando wp_cache_add()).

Quando fai qualcosa del genere get_post_custom(), controlla prima quella cache degli oggetti. Quindi non è necessario effettuare query aggiuntive per ottenere la meta meta a questo punto. Se hai ricevuto il post in a WP_Query, allora il meta è già in memoria e lo ottiene direttamente da lì.

I vantaggi qui sono molte volte maggiori rispetto alla creazione di una query complessa, ma il vantaggio maggiore deriva dall'utilizzo della cache degli oggetti. Se si utilizza una soluzione di memorizzazione nella cache persistente come XCache o memcached o APC o qualcosa del genere, e si dispone di un plug-in che può legare la cache degli oggetti (W3 Total Cache, ad esempio), l'intera cache degli oggetti viene archiviata nella memoria veloce già. In questo caso, ci sia pari a zero query necessarie per recuperare i dati; è già in memoria. Il caching persistente degli oggetti è fantastico sotto molti aspetti.

In altre parole, la query è probabilmente caricata e caricata più lentamente rispetto all'utilizzo di una query corretta e di una semplice soluzione di memoria persistente. Usa il normale WP_Query. Risparmia qualche sforzo.

Ulteriori: update_meta_cache() è intelligente, a proposito. Non recupererà le meta informazioni per i post che hanno già le loro meta informazioni memorizzate nella cache. Non ottiene lo stesso meta due volte, sostanzialmente. Super efficiente.

Ulteriore ulteriore: "Dai più lavoro possibile al database." ... No, questa è la rete. Si applicano regole diverse. In generale, si desidera sempre dare meno lavoro possibile al database, se possibile. I database sono lenti o mal configurati (se non lo hai configurato in modo specifico, puoi scommettere bene che questo è vero). Spesso sono condivisi tra molti siti e sovraccaricati in una certa misura. Di solito hai più web server che database. In generale, si desidera semplicemente estrarre i dati desiderati dal DB nel modo più rapido e semplice possibile, quindi eseguire l'ordinamento utilizzando il codice lato web server. Come principio generale, ovviamente, i diversi casi sono tutti diversi.


30

Consiglierei una query pivot. Usando il tuo esempio:

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

Questa risposta dovrebbe essere contrassegnata come corretta.
Luca

Se stai cercando una query nel database questa è la risposta corretta
Alex Popov

Questa query mi ha ridotto il tempo in cui utilizzavo WP_Query da ~ 25 secondi a ~ 3 secondi. Il mio requisito era di licenziarlo solo una volta, quindi non era necessaria la memorizzazione nella cache.
Kush,

11

Mi sono imbattuto in un caso in cui desidero anche recuperare rapidamente molti post con le loro meta informazioni associate. Devo recuperare i post di O (2000).

L'ho provato usando il suggerimento di Otto: eseguire WP_Query :: query per tutti i post, quindi scorrere e avviare get_post_custom per ogni post. Ciò ha richiesto, in media, circa 3 secondi per essere completato .

Ho quindi provato la query pivot di Ethan (anche se non mi piaceva dover chiedere manualmente ogni meta_key a cui ero interessato). Ho comunque dovuto scorrere tutti i post recuperati per annullare la serializzazione del meta_valore. Ciò ha richiesto, in media, circa 1,3 secondi per essere completato .

Ho quindi provato a utilizzare la funzione GROUP_CONCAT e ho trovato il risultato migliore. Ecco il codice:

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

Ciò ha richiesto in media 0,7 secondi . Questo è circa un quarto del tempo della soluzione get_post_custom () di WP e circa la metà della soluzione di query pivot.

Forse questo sarà di interesse per qualcuno.


Sarei interessato a quali risultati ottieni con una soluzione persistente di cache degli oggetti. La cache degli oggetti a volte sarà più lenta per il caso base, a seconda del database e della configurazione, ma i risultati del mondo reale con la maggior parte degli host forniranno risultati molto diversi. La memorizzazione nella cache basata sulla memoria è incredibilmente veloce.
Otto,

Ehi @Otto. Indipendentemente dal metodo che utilizzo per ottenere i dati, voglio sicuramente memorizzare il risultato nella cache. Ho provato a usare l'API transitoria per farlo, ma sto colpendo problemi di memoria. La stringa serializzata per i miei 2000 oggetti clock a ~ 8M e set_transient () non riesce (memoria esaurita). Inoltre, è necessario modificare l'impostazione MySQL max_allowed_packet. Esaminerò la memorizzazione nella cache per il file, ma non sono ancora sicuro delle prestazioni lì. C'è un modo per memorizzare nella cache che persiste tra le richieste?
Trevor Mills,

Sì, se si dispone di una cache di memoria persistente (XCache, memcached, APC, ecc.) E si utilizza un plug-in di cache degli oggetti (W3 Total Cache supporta molti tipi di cache di memoria), quindi memorizza tutta la cache degli oggetti in memoria, dandoti un moltiplicatore di velocità praticamente di tutto.
Otto,

Sto restituendo 6000 articoli da utilizzare in uno schema di filtro js backbone / underscore. Questo ha richiesto una query personalizzata 6s che non potevo nemmeno eseguire come WP_Query perché è scaduto e l'ha resa una query 2s. Anche se array_map lo rallenta un po '...
Jake,

Esiste un supporto per la creazione di un supporto ad alte prestazioni per la restituzione di tutti i metadati all'interno di un WP_Query?
atwellpub,

2

Mi sono trovato in una situazione dalla quale avevo bisogno di fare questo compito per creare un documento CSV, alla fine ho lavorato direttamente con mysql per farlo. Il mio codice unisce i post e le meta table per recuperare le informazioni sui prezzi di woocommerce, la soluzione precedentemente pubblicata mi imponeva di utilizzare gli alias di tabella in 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

Attenzione però, il woocommerce ha creato 300K + righe nella mia meta table, quindi era molto grande e quindi molto lento.


1

NESSUNA VERSIONE SQL:

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

Supponiamo che tu abbia un elenco di ID post memorizzati come una matrice di ID, qualcosa del genere

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

Ora ottenere tutti i post e tutti i metas in 1 query non è possibile senza usare almeno un po 'di SQL, quindi dobbiamo fare 2 query (ancora solo 2):

1. Ricevi tutti i post (usando WP_Query )

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

(Non dimenticare di chiamare wp_reset_postdata();se stai facendo un "loop" in seguito;))

2. Aggiorna 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);

Per ottenere i metadati basta usare lo standard get_post_meta()che, come sottolineato da @Otto:
cerca prima nella cache :)

Nota: se in realtà non hai bisogno di altri dati dai post (come titolo, contenuto, ...) puoi fare solo 2. :-)


0

utilizzando il modulo di soluzione trevor e modificandolo per funzionare con SQL nidificato. Questo non è 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);

-1

Mi sono imbattuto anche nel problema dei metacampi a più valori. Il problema è con WordPress stesso. Guarda in wp-Includes / meta.php. Cerca questa linea:

$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 meta valori, la variabile $ meta_type è impostata su CHAR. Non conosco i dettagli su come CASTing il valore su CHAR influenzi la stringa serializzata, ma per risolverlo, puoi rimuovere il cast in modo che SQL assomigli a questo:

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

Ora, anche se funziona, stai confondendo 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 quindi scrivere un po 'di PHP per affrontare le istruzioni AND aggiuntive per i meta_valori che sto cercando e usare $ wpdb-> get_results ($ sql ) per l'output finale. Hacky, ma funziona.


Non l'ho provato, ma sfruttando il get_meta_sqlfiltro che segue questa linea sarebbe ovviamente preferibile hackerare il codice core.
Steve Taylor,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.