Le moyen le plus efficace d'obtenir des publications avec postmeta

36

Je dois obtenir un tas de messages avec leurs métadonnées. Bien sûr, vous ne pouvez pas obtenir de métadonnées avec une requête de publication standard, vous devez donc généralement effectuer une recherche get_post_custom()pour chaque publication.

J'essaie avec une requête personnalisée, comme ceci:

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

Semble travailler. Il se déclenche si vous utilisez l'un de ces champs méta d'une manière qui autorise plusieurs méta-valeurs pour le même poste. Je ne peux pas penser à une jointure pour faire ça.

Donc, question 1: existe-t-il une jointure, une sous-requête, ou autre, pour importer des méta-champs à valeurs multiples?

Mais question 2: ça vaut le coup? Combien de postmetajointures de table dois-je ajouter avant qu'une approche à 2 requêtes devienne préférable? Je pouvais saisir toutes les données de publication dans une requête, puis toutes les publications pertinentes dans une autre et combiner la méta avec les données de publication dans un jeu de résultats en PHP. Cela finirait-il par être plus rapide qu'une requête SQL de plus en plus complexe, si cela est encore possible?

Je pense toujours: "Donnez autant de travail que possible à la base de données." Pas sûr sur celui-ci!

Steve Taylor
la source
Je ne suis pas sûr si vous voulez même faire les jointures. la combinaison de get_posts () et get_post_meta () vous renvoie les mêmes données. En fait, l'utilisation des jointures est moins efficace, car vous récupérerez peut-être des données que vous n'utiliserez pas plus tard.
rexposadas
2
Les métadonnées postales ne sont-elles pas mises en cache automatiquement de toute façon?
Manny Fleurmond
@rxn, si j'ai plusieurs centaines de messages à revenir (ce sont des types de messages personnalisés), la charge de base de données est certainement assez lourde get_posts(), alors get_post_meta()pour chacun d'entre eux? @MannyFleurmond, il est difficile de trouver des informations fiables sur la mise en cache intégrée de WP, mais autant que je sache, les choses seraient mises en cache par requête. L'appel au serveur pour récupérer ces données est un appel AJAX, et je ne pense pas que rien d'autre puisse récupérer des données avant.
Steve Taylor
En fait, je vais faire plusieurs requêtes et mettre en cache les résultats. Il s'avère que non seulement nous avons besoin de post méta, y compris des champs comportant plusieurs valeurs, mais aussi de données sur les utilisateurs connectés aux publications via des méta-champs (deux ensembles), ainsi que des méta utilisateur. Pure SQL est définitivement hors de propos!
Steve Taylor

Réponses:

58

Les métadonnées post sont automatiquement mises en cache dans la mémoire pour une WP_Queryrequête standard (et la requête principale), sauf si vous lui indiquez de ne pas le faire en utilisant le update_post_meta_cacheparamètre.

Par conséquent, vous ne devriez pas écrire vos propres requêtes pour cela.

Comment fonctionne la meta-cache pour les requêtes normales:

Si le update_post_meta_cacheparamètre à WP_Queryn'est pas défini sur false, la update_post_caches()fonction sera appelée une fois les publications extraites de la base de données, puis appelée update_postmeta_cache().

La update_postmeta_cache()fonction est un wrapper pour update_meta_cache(), et elle appelle essentiellement un simple SELECTavec tous les identifiants des posts récupérés. Cela lui permettra d'obtenir tout le postmeta, toutes les publications de la requête, et d'enregistrer ces données dans le cache d'objets (using wp_cache_add()).

Lorsque vous faites quelque chose comme get_post_custom(), c'est d'abord vérifier ce cache d'objets. Donc, il ne faut pas faire de requêtes supplémentaires pour obtenir la publication méta à ce stade. Si vous avez le message dans un WP_Query, alors la méta est déjà en mémoire et il l'obtient directement à partir de là.

Les avantages ici sont bien plus nombreux que les requêtes complexes, mais le plus gros avantage provient de l'utilisation du cache d'objets. Si vous utilisez une solution de mise en cache de la mémoire persistante telle que XCache ou memcached, APC ou quelque chose du genre et que vous disposez d'un plug-in pouvant y lier votre cache d'objets (W3 Total Cache, par exemple), alors tout le cache d'objets est stocké en mémoire rapide. déjà. Dans ce cas, aucune requête n'est nécessaire pour récupérer vos données; c'est déjà en mémoire. La mise en cache d'objets persistants est géniale à bien des égards.

En d'autres termes, votre requête charge probablement et charge plus lentement que d'utiliser une requête appropriée et une solution simple de mémoire persistante. Utilisez la normale WP_Query. Épargnez-vous des efforts.

Supplémentaire: update_meta_cache() est intelligent, BTW. Les méta-informations ne seront pas récupérées pour les publications dont les méta-informations sont déjà mises en cache. Il ne fait pas deux fois la même méta, fondamentalement. Super efficace.

Additional additionnel: "Donnez le plus de travail possible à la base de données." ... Non, c'est le Web. Des règles différentes s'appliquent. En général, vous voulez toujours donner le moins de travail possible à la base de données, si cela est réalisable. Les bases de données sont lentes ou mal configurées (si vous ne les avez pas configurées spécifiquement, vous pouvez parier que cela est vrai). Souvent, ils sont partagés entre plusieurs sites et surchargés dans une certaine mesure. Vous avez généralement plus de serveurs Web que de bases de données. En général, vous souhaitez extraire les données souhaitées de la base de données aussi rapidement et simplement que possible, puis les trier à l'aide du code côté serveur Web. En règle générale, bien sûr, différents cas sont tous différents.

Otto
la source
30

Je recommanderais une requête pivot. En utilisant votre exemple:

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
la source
Cette réponse devrait être marquée correcte.
Luc
Si vous recherchez une requête sur une base de données, c'est la bonne réponse
Alex Popov
Cette requête a réduit mon temps d'utilisation de WP_Query d'environ 25 secondes à environ 3 secondes. Mon exigence était de ne lancer cette opération qu'une seule fois, de sorte qu'aucune mise en cache n'était nécessaire.
Kush
11

J'ai rencontré un cas où je veux aussi pouvoir récupérer rapidement de nombreux messages avec leurs méta-informations associées. J'ai besoin de récupérer des messages O (2000).

Je l'ai essayé en utilisant la suggestion d'Otto - exécutant WP_Query :: query pour toutes les publications, puis parcourant get_post_custom et exécutant pour chaque publication. Cela a pris en moyenne environ 3 secondes .

J'ai ensuite essayé la requête pivot d'Ethan (bien que je n'aime pas avoir à demander manuellement chaque méta_key qui m'intéressait). Il me restait encore à parcourir toutes les publications récupérées pour annuler la sérialisation de la méta_valeur. Cela a pris en moyenne environ 1,3 seconde .

J'ai ensuite essayé d'utiliser la fonction GROUP_CONCAT et j'ai trouvé le meilleur résultat. Voici le code:

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

Cela a pris en moyenne 0,7 seconde . C'est environ le quart du temps de la solution WP get_post_custom () et environ la moitié de la solution de requête pivot.

Cela intéressera peut-être quelqu'un.

Trevor Mills
la source
J'aimerais savoir quels résultats vous obtenez avec une solution de cache d'objets persistants. Le cache d'objets sera parfois plus lent pour le scénario de base, en fonction de votre base de données et de votre configuration, mais les résultats réels avec une majorité d'hôtes produiront des résultats très variables. La mise en cache basée sur la mémoire est ridiculement rapide.
Otto
Hé @ Otto. Quelle que soit la méthode utilisée pour obtenir les données, je souhaite absolument mettre en cache le résultat. J'ai essayé d'utiliser l'API transitoire pour le faire, mais je rencontre des problèmes de mémoire. La chaîne sérialisée pour mes 2 000 objets est cadencée à environ 8 Mo et set_transient () échoue (mémoire épuisée). De plus, vous devez modifier le paramètre MySQL max_allowed_packet. Je vais examiner la possibilité de la mettre en cache dans un fichier, mais je ne suis pas encore sûr de la performance là-bas. Existe-t-il un moyen de conserver en mémoire cache des requêtes persistantes?
Trevor Mills
Oui, si vous avez un cache de mémoire persistante (XCache, memcached, APC, etc.) et utilisez un plug-in de mise en cache d’objets (W3 Total Cache prend en charge de nombreux types de caches de mémoire), il stocke tout le cache d’objets en mémoire, ce qui vous donne une accélération multiple de pratiquement tout.
Otto
Je retourne 6000 articles à utiliser dans un schéma de filtrage dorsal / underscore js. Cela prenait une requête personnalisée 6s que je ne pouvais même pas exécuter en tant que WP_Query car le délai imparti était dépassé, et en faisait une requête 2s. Bien que le array_map le ralentisse un peu ...
Jake
Existe-t-il un support pour la création d'un support hautes performances pour le retour de toutes les métadonnées dans une requête WP_Query?
atwellpub
2

Je me suis trouvé dans une situation où je devais faire cette tâche pour créer un document CSV à partir de, j'ai fini par travailler directement avec mysql pour faire cela. Mon code rejoint les tables de méta et les tables de méta pour récupérer les informations de tarification de woocommerce. La solution précédemment publiée nécessitait que j'utilise des alias de table dans SQL pour fonctionner correctement.

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

Soyez averti cependant, woocommerce a créé plus de 300 000 lignes dans ma table de méta, elle était donc très grande et donc très lente.

Terry Kernan
la source
1

PAS DE VERSION SQL:

Obtenez tous les messages et toutes leurs méta-valeurs (métas) sans SQL:

Supposons que vous ayez une liste d’ID de publication stockés sous la forme d’un tableau d’ID, quelque chose comme:

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

Maintenant, obtenir tous les posts et toutes les métas dans 1 requête n'est pas possible sans utiliser au moins un peu de SQL, nous devons donc faire 2 requêtes (encore 2):

1. Obtenez tous les messages (en utilisant WP_Query )

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

(N'oubliez pas d'appeler wp_reset_postdata();si vous faites une "boucle" après;))

2. Mettre à jour le méta cache

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

Pour obtenir les métadonnées, utilisez simplement le standard get_post_meta()qui, comme @Otto l'a souligné:
examine d'abord le cache :)

Remarque: si vous n'avez pas réellement besoin d'autres données des articles (comme le titre, le contenu, ...), vous ne pouvez en faire que 2. :-)

jave.web
la source
0

en utilisant la solution trevor et en la modifiant pour fonctionner avec du SQL imbriqué. Ceci n'est pas testé.

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
la source
-1

J'ai aussi rencontré le problème des champs de méta de valeurs multiples. Le problème est avec WordPress lui-même. Regardez dans wp-includes / meta.php. Cherchez cette ligne:

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

Le problème est avec l'instruction CAST. Dans une requête de méta-valeurs, la variable $ meta_type est définie sur CHAR. Je ne connais pas les détails sur la façon dont CASTing la valeur de CHAR affecte la chaîne sérialisée, mais pour résoudre ce problème, vous pouvez supprimer la conversion de sorte que le code SQL ressemble à ceci:

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

Maintenant, même si cela fonctionne, vous travaillez avec les éléments internes de WordPress, ce qui pourrait entraîner des problèmes, et ce n'est pas une solution permanente, en supposant que vous ayez besoin de mettre à niveau WordPress.

La façon dont je l'ai corrigé est de copier le SQL généré par WordPress pour la méta-requête que je veux, puis d'écrire un peu de PHP pour ajouter des instructions AND supplémentaires aux méta_valeurs que je cherche et d'utiliser $ wpdb-> get_results ($ sql ) pour la sortie finale. Hacky, mais ça marche.

Harry Love
la source
Je ne l'ai pas essayé, mais get_meta_sqlil serait bien sûr préférable d'utiliser le filtre qui suit cette ligne plutôt que de pirater le code principal.
Steve Taylor