Может ли wp_query вернуть мета-данные записей в одном запросе?
Я хотел бы создать wp_query, который возвращал бы мета-данные записей внутри массива posts
.
$args = array (
'post_type' => 'page',
'meta_key' => 'someMetaKeyName',
);
// Запрос
$query = new WP_Query( $args );
Это возвращает что-то вроде:
Как видите, записи не содержат никаких мета-данных. Возможно ли включить мета-данные в возвращаемый массив?
PS: Я не хочу использовать дополнительные wp_queries из соображений производительности.

По умолчанию WP_Query
возвращает стандартные объекты WP_Post
для запрашиваемых записей. Я считаю, что с помощью хитрых перезаписей и использования фильтров, доступных в WP_Query
, можно добавить объекты в возвращаемый массив объектов WP_Post
.
Будет ли это производительно? На мой взгляд, это скорее снизит производительность, так как вам придется объединять результаты в запросе, поскольку пользовательские поля хранятся не в таблице wp_posts
, а в таблице wp_postmeta
.
Получение метаданных записи происходит очень быстро и не требует дополнительных экземпляров WP_Query
. Вы можете просто получить пользовательское поле с помощью функции get_post_meta()
. Разработчики WordPress продумали этот момент при внедрении пользовательских полей. Они добавили кеш для них, поэтому независимо от того, запрашиваете ли вы 1 или 100 пользовательских полей, вы обращаетесь к базе данных только один раз, что очень быстро. Для полного теста и объяснения смотрите эту статью, которую я недавно написал на эту тему.
На мой взгляд, дополнительный запрос к базе данных и потраченное время того стоят и работают быстрее, чем переписывание WP_Query
таким образом, чтобы включить пользовательские поля в стандартный объект записи, возвращаемый $posts
.

OK, спасибо, я приму этот ответ, но честно говоря, вызывать get_post_meta()
для каждого поста — это слишком муторно.. Лучше бы был способ хранить дополнительные данные либо прямо в таблице wp_posts
, либо в связанной таблице, которая не так запутывает, как wp_postsmeta
.

Честно говоря, неважно, вызываете ли вы get_post_meta()
или получаете эти данные как часть объекта поста — вам всё равно придётся вызывать это для каждого поста. То же самое с тегами шаблонов, такими как the_content()
— их нужно вызывать для каждого поста.

Значит ли это, что если нужно показать 120 постов, на странице будет выполнено 120 дополнительных запросов?

Все данные поста сохраняются в кеше, поэтому при вызове метаданных поста не будет дополнительных запросов

У меня была похожая проблема недавно — мне нужно было получить 7 метаданных из пользовательского типа записи, а также выбрать запись на основе одного из метаданных.
Поэтому я создал следующий SQL-запрос, которым часто пользуюсь. Надеюсь, он поможет кому-то ещё. Постараюсь объяснить его как можно лучше.
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 );
Сначала я получаю доступ к функциям базы данных WordPress через global $wpdb
.
Затем устанавливаю тип записи в переменной $pt
.
Чтобы получить нужную запись, соответствующую определённому значению в метаданных, я указываю мета-ключ в $mk
.
Затем задаю значение мета-ключа в переменной $mv
(в данном случае значение соответствует ID записи).
Переменные $mk1
–$mk7
содержат дополнительные мета-ключи, которые мне нужны (их значения я получу в SELECT-запросе).
Также я выношу параметр сортировки в переменную $ord
.
SELECT-запрос работает следующим образом:
Я выбираю ID записи и её заголовок из таблицы {$wpdb->posts}
(псевдоним p
).
Затем выбираю все необходимые метаданные, получая их через pm1
–pm7
и переименовывая (AS) для удобства при получении данных из объекта.
Создаю LEFT JOIN для метаданных, по которым нужно искать совпадение (pm
).
Создаю 7 LEFT JOIN'ов для каждого из метаданных, которые нужно получить (pm1
–pm7
).
Условие WHERE основано на первом LEFT JOIN (pm
), чтобы получить только записи, где метаданные совпадают.
Добавляю условие AND для типа записи и для статусов, которые не являются черновиками (только опубликованные записи).
В конце добавляю сортировку ORDER BY.
Этот запрос работает быстро и использует встроенные индексы WordPress, поэтому выглядит эффективным.
Не знаю, есть ли что-то лучше этого, но если есть — с радостью воспользуюсь.
Надеюсь, это поможет.
Маркус

Этот вопрос был задан более года назад, но у меня такая же проблема, и вот функция, которая добавит каждое meta_value и meta_key в объект $wp_query.
Вместо запроса метаданных каждого поста в цикле while, эта функция выполнит один дополнительный запрос, например:
"SELECT meta_key, meta_value, post_id FROM $wpdb->postmeta WHERE post_id IN (1,2,3,4,5...)"
где (1,2,3,4,5...) — это текущие ID запрашиваемых постов из $wp_query
if(!function_exists('add_query_meta')) {
function add_query_meta($wp_query = "") {
// Возвращаем, если wp_query пуст или postmeta уже существует
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;
}
}
Дополнительные "postmeta" будут записаны для каждого $wp_query->posts[$i]
$wp_query->posts[0]->postmeta
Пример с 'someMetaKeyName' — не забудьте добавить add_query_meta()
в function.php вашей темы.
$args = array (
'post_type' => 'page',
'meta_key' => 'someMetaKeyName',
);
// Запрос
$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();
// Получаем $someMetaKeyName в текущем посте
foreach($wp_query->posts[$i]->postmeta as $k => $v) {
switch($v->meta_key) {
case('someMetaKeyName') : {
$someMetaKeyName = $v->meta_value;
break;
}
}
}
// Ваш код здесь
// Пример
echo isset($someMetaKeyName) ? '<h3>'.$someMetaKeyName.'</h3>' : '';
$i++;
}
}

Мне просто нужно было получить одно дополнительное значение post_meta (в моем случае шаблон), и я смог добиться чего-то похожего на то, что вам нужно, используя get_pages
с параметром meta_key
.
$args = [
'parent' => $post_id, // в моем примере ищем дочерние записи определенного поста
'meta_key' => '_wp_page_template', // ищем шаблон, используемый для поста
];
$posts = get_pages($args);
Это возвращает массив объектов постов в таком виде:
Array
(
[0] => WP_Post Object
(
[ID] => 22765
[post_author] => 173
[post_date] => 2022-03-14 21:11:18
...
[meta_key] => _wp_page_template
[meta_value] => template-module-content.php // <-- ВОТ ОНО!
...
)
Затем вы можете получить доступ к meta_value
следующим образом:
echo $posts[0]->meta_value;
// возвращает 'template-module-content.php' в моем случае
Признаю, что это может быть узкоспециализированный случай, но это именно то, что мне было нужно

Какова причина, по которой вы использовали meta_key
и meta_query[]['key']
одновременно?

Нет, это не работает и возвращает массив записей без связанных мета-данных.
