Wordpress - Can wp_query return posts meta in a single request?
By default, WP_Query
returns the standard WP_Post
objects for the posts being queried. I believe with some clever rewrite and use of the filters given in WP_Query
you can add objects to the returned WP_Post
objects array.
Will this be performant? In my opinion, it will hurt performance more as you will need to join results in your query as custom fields are not saved in the wp_posts
table, but in the wp_postmeta
table
Retrieving post meta is really fast and it does not require any extra instance of WP_Query
. You can simply call the custom field with get_post_meta()
. WordPress was very thoughtful when the custom fields was introduced. They added a cache to cache them, so whether you are querying 1 or 100 custom fields, you are hitting the database once, superfast. For a complete test and explanation, see this post I have recently done on this subject.
In my opinion, the extra database call and the actual time spent is worth it and faster than rewriting WP_Query
in such a way to include custom fields in the standard post object returned by $posts
This question is more than 1 year old, but I have the same problomlem, and here is function that will add each meta_value and meta_key to $wp_query object,
instead of query each post meta in while loop, this function will do One extra query example:
"SELECT meta_key, meta_value, post_id FROM $wpdb->postmeta WHERE post_id IN (1,2,3,4,5...)"
where (1,2,3,4,5...) is curently quered post IDs from $wp_query
if(!function_exists('add_query_meta')) {
function add_query_meta($wp_query = "") {
//return In case if wp_query is empty or postmeta already exist
if( (empty($wp_query)) || (!empty($wp_query) && !empty($wp_query->posts) && isset($wp_query->posts[0]->postmeta)) ) { return $wp_query; }
$sql = $postmeta = '';
$post_ids = array();
$post_ids = wp_list_pluck( $wp_query->posts, 'ID' );
if(!empty($post_ids)) {
global $wpdb;
$post_ids = implode(',', $post_ids);
$sql = "SELECT meta_key, meta_value, post_id FROM $wpdb->postmeta WHERE post_id IN ($post_ids)";
$postmeta = $wpdb->get_results($sql, OBJECT);
if(!empty($postmeta)) {
foreach($wp_query->posts as $pKey => $pVal) {
$wp_query->posts[$pKey]->postmeta = new StdClass();
foreach($postmeta as $mKey => $mVal) {
if($postmeta[$mKey]->post_id == $wp_query->posts[$pKey]->ID) {
$newmeta[$mKey] = new stdClass();
$newmeta[$mKey]->meta_key = $postmeta[$mKey]->meta_key;
$newmeta[$mKey]->meta_value = maybe_unserialize($postmeta[$mKey]->meta_value);
$wp_query->posts[$pKey]->postmeta = (object) array_merge((array) $wp_query->posts[$pKey]->postmeta, (array) $newmeta);
unset($newmeta);
}
}
}
}
unset($post_ids); unset($sql); unset($postmeta);
}
return $wp_query;
}
}
Additioanal "postmeta" will be written to each $wp_query->posts[$i]
$wp_query->posts[0]->postmeta
Example with 'someMetaKeyName' don`t forget to put
add_query_meta()
to your theme functin.php
$args = array (
'post_type' => 'page',
'meta_key' => 'someMetaKeyName',
);
// The Query
$query = new WP_Query( $args );
if($wp_query->have_posts()) {
$wp_query = add_query_meta($wp_query);
$i = 0;
while($wp_query->have_posts()) {
$wp_query->the_post();
$post_id = get_the_id();
//Get $someMetaKeyName in current post
foreach($wp_query->posts[$i]->postmeta as $k => $v) {
switch($v->meta_key) {
case('someMetaKeyName') : {
$someMetaKeyName = $v->meta_value;
break;
}
}
}
//Your Code here
//Example
echo isset($someMetaKeyName) ? '<h3>'.$someMetaKeyName.'</h3>' : '';
$i++;
}
}
I had a similar problem recently, I needed to get 7 pieces of metadata from a custom post type, but also needed to get the post based on a piece of metadata.
So I created the following SQL statement, I use it often. Hopefully it will help someone else. I'll try and explain it as best I can.
global $wpdb;
$pt = 'clients';
$mk = 'trainerid';
$mv = $pid;
$mk1 = 'email';
$mk2 = 'phone';
$mk3 = 'gender';
$mk4 = 'dob';
$mk5 = 'photo';
$mk6 = 'registrationts';
$mk7 = 'activationts';
$ord = 'p.post_name ASC';
$sql = "
SELECT p.ID, p.post_title AS fullname, pm1.meta_value AS email, pm2.meta_value AS phone, pm3.meta_value AS gender, pm4.meta_value AS dob, pm5.meta_value AS photo, pm6.meta_value AS regts, pm7.meta_value AS actemailts
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
AND pm.meta_key = '{$mk}'
LEFT JOIN {$wpdb->postmeta} pm1 ON pm1.post_id = p.ID
AND pm1.meta_key = '{$mk1}'
LEFT JOIN {$wpdb->postmeta} pm2 ON pm2.post_id = p.ID
AND pm2.meta_key = '{$mk2}'
LEFT JOIN {$wpdb->postmeta} pm3 ON pm3.post_id = p.ID
AND pm3.meta_key = '{$mk3}'
LEFT JOIN {$wpdb->postmeta} pm4 ON pm4.post_id = p.ID
AND pm4.meta_key = '{$mk4}'
LEFT JOIN {$wpdb->postmeta} pm5 ON pm5.post_id = p.ID
AND pm5.meta_key = '{$mk5}'
LEFT JOIN {$wpdb->postmeta} pm6 ON pm6.post_id = p.ID
AND pm6.meta_key = '{$mk6}'
LEFT JOIN {$wpdb->postmeta} pm7 ON pm7.post_id = p.ID
AND pm7.meta_key = '{$mk7}'
WHERE pm.meta_value = '{$mv}'
AND p.post_type = '{$pt}'
AND p.post_status NOT IN ('draft','auto-draft')
ORDER BY {$ord}
";
$clients = $wpdb->get_results( $wpdb->prepare( $sql ), OBJECT );
First I get the wordpress database functions with global $wpdb. Then I set the posttype with $pt. To get the correct post that matches a specific value in post_meta, I set the $mk (meta_key)
Then I set the $mv (meta_value) var. (in this case the meta value matches a postid)
$mk1-$mk7 are the meta_keys I want from each post. (I'll grab the values in the select statement)
I also make the 'order by' a var, by setting $ord
The select statement goes as follows: I select the post ID and the post_title from the POST or 'p.'
Then I select all the metadata I need selecting them with pm1. -> pm.7 and grabbing the meta_value and renaming them ( AS ) so it's more readable when retrieving the data from my object.
I create a LEFT JOIN for the meta data I need to match to the post. (pm)
I create 7 left join's for each of the meta data I need to retrieve. (pm1-pm7)
The WHERE statement is based on the first LEFT JOIN (pm) so that it will know I need only the posts where the metadata matches.
I also add an 'AND' for the post type, and for the post_statuses that are not drafts. (so only published posts)
Finally I add the 'order by' clause.
This works fast and with the built-in indexes in Wordpress, so it seems efficient.
Don't know if something is better than this, but if it is, I'd love to use it.
Hope this helps.
Marcus