получение всех значений для ключа произвольного поля (кросс-пост)
Я знаю, как получить значение произвольного поля для конкретной записи.
get_post_meta($post_id, $key, $single);
Мне нужно получить все значения, связанные с определенным ключом произвольного поля, во всех записях.
Кто-нибудь знает эффективный способ сделать это? Я бы не хотел перебирать все ID записей в базе данных.
Пример:
4 записи, все с разными значениями для произвольного поля 'Mood'. 2 записи имеют значение 'happy', 1 запись имеет 'angry' и 1 запись имеет 'sad'
Я хочу вывести: во всех записях у нас: два счастливых, один сердитый и один грустный автор(ов).
Но для МНОЖЕСТВА записей.
Я ищу либо:
- функцию WP для получения этого. или
- пользовательский запрос для максимально эффективного получения этого.

Один из возможных подходов — использовать один из вспомогательных методов класса WPDB для выполнения более точного запроса на основе метаданных.
Используя функцию $wpdb
get_col
, можно получить простой плоский массив данных.
Вот пример функции, которая запрашивает из базы данных все записи указанного типа, статуса и мета-ключа (или пользовательского поля, для менее технически подкованных).
function get_meta_values( $meta_key = '', $post_type = 'post', $post_status = 'publish' ) {
global $wpdb;
if( empty( $meta_key ) )
return;
$meta_values = $wpdb->get_col( $wpdb->prepare( "
SELECT pm.meta_value FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = %s
AND p.post_type = %s
AND p.post_status = %s
", $meta_key, $post_type, $post_status ) );
return $meta_values;
}
Например, если вы хотите узнать, какие записи имеют мета-ключ rating для типа записи movies, и сохранить эту информацию в переменной, вызов функции будет выглядеть так:
$movie_ratings = get_meta_values( 'rating', 'movies' );
Если вам нужно просто вывести эти данные на экран, функция PHP implode
может быстро преобразовать массив в строку.
// Выводим мета-значения, разделенные переносом строки
echo implode( '<br />', get_meta_values( 'YOURKEY' ));
Вы также можете использовать возвращенные данные, чтобы подсчитать, сколько записей имеют определенные мета-значения, выполнив простую итерацию (цикл) по данным и создав массив с количеством, например:
$movie_ratings = get_meta_values( 'rating', 'movies' );
if( !empty( $movie_ratings ) ) {
$num_of_ratings = [];
foreach( $movie_ratings as $meta_value ) {
$num_of_ratings[$meta_value] = isset( $num_of_ratings[$meta_value] ) ? $num_of_ratings[$meta_value] + 1 : 1;
}
}
// Выводим количество оценок
printf( '<pre>%s</pre>', print_r( $num_of_ratings ) );
/*
Вывод:
Array(
[5] => 10
[9] => 2
)
т.е. 10 записей фильмов имеют оценку 5 и 2 записи — оценку 9.
*/
Эту логику можно применить к различным типам данных и расширить для работы разными способами. Надеюсь, мои примеры были полезны и достаточно просты для понимания.
Использование транзитов для кэширования результатов
А вот обновленная версия, которая использует WordPress-транзиты для кэширования запроса, так как это основная критика использования $wpdb
в других предложенных ответах.
function get_meta_values( string $meta_key, string $post_type = 'post', bool $distinct = false, string $post_status = 'publish' ) {
global $wpdb, $wp_post_types;
if( !isset( $wp_post_types[$post_type] ) )
// Существующая строка WordPress, она должна переводиться как есть
return __( 'Invalid post type.' );
$transient_key = 'get_' . $wp_post_types[$post_type]->name . '_type_meta_values';
$get_meta_values = get_transient( $transient_key );
if( true === (bool)$get_meta_values )
return $get_meta_values;
$distinct = $distinct ? ' DISTINCT' : '';
$get_meta_values = $wpdb->get_col( $wpdb->prepare( "
SELECT{$distinct} pm.meta_value FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = %s
AND p.post_type = %s
AND p.post_status = %s
", $meta_key, $post_type, $post_status ) );
set_transient( $transient_key, $get_meta_values, DAY_IN_SECONDS );
return $get_meta_values;
}
Константа DAY_IN_SECONDS
— одна из нескольких констант времени в секундах, установленных WordPress.
Обновлены имена аргументов и переменных, чтобы сделать их более согласованными с именованием WordPress, а также добавлен необязательный параметр DISTINCT
по совету Howdy_McGee в комментариях.

Также забавный факт для будущих читателей: если вы хотите получить только уникальные значения мета-полей - вы пишете DISTINCT
сразу после SELECT
в функции выше. Может быть полезно.

Не рекомендуется и не нужно использовать глобальную переменную $wpdb:
// функция для получения всех возможных мета-значений выбранного мета-ключа.
function get_meta_values( $meta_key, $post_type = 'post' ) {
$posts = get_posts(
array(
'post_type' => $post_type,
'meta_key' => $meta_key,
'posts_per_page' => -1,
)
);
$meta_values = array();
foreach( $posts as $post ) {
$meta_values[] = get_post_meta( $post->ID, $meta_key, true );
}
return $meta_values;
}
$meta_values = get_meta_values( $meta_key, $post_type );

Это мой предпочтительный метод в большинстве случаев. Он выполняет пять запросов вместо одного, но, поскольку используются стандартные процедуры WordPress для их генерации и отправки, будет задействовано любое кеширование, специфичное для платформы (например, Object Caching от WP Engine или какой-нибудь плагин). Данные также будут храниться во внутреннем кеше WordPress на время запроса, поэтому не потребуется извлекать их из базы данных повторно, если они понадобятся снова.

К данным также будут применены все фильтры, что может быть крайне важно, например, на многоязычном сайте. Наконец, поскольку используются только стандартные функции ядра WordPress, вероятность того, что это сломается в будущем обновлении, гораздо ниже.

Производительность можно улучшить, ограничив запрос только ID записей?
Добавить: 'fields' => 'ids'
Таким образом, массив запроса будет выглядеть так:
array(
'post_type' => $post_type,
'meta_key' => $meta_key,
'posts_per_page' => -1,
'fields' => 'ids'
)

Я могу представить случаи, когда вполне допустимо иметь несколько метазначений с одинаковым значением, поэтому я не стал вносить это изменение в свой код. Если вам нужны уникальные значения, то это был бы правильный подход. Кроме того, вы также можете добавить это в качестве аргумента функции (чтобы использовать или не использовать эту возможность по мере необходимости).

Для получения всех мета-значений по ключу
Проверьте wp->db в WordPress Codex
$values = $wpdb->get_col("SELECT meta_value
FROM $wpdb->postmeta WHERE meta_key = 'yourmetakey'" );

Проблема этого подхода в отсутствии специфичности — такой запрос вернёт множество результатов, которые могут включать черновики, удалённые записи, посты, страницы и любые другие типы записей. Вы никогда не должны запрашивать то, что вам не нужно, здесь определённо требуется конкретность.

Хотя верно, что вы можете получить значения из других типов записей и их статусов, бывают случаи, когда вам нужны только значения и вы не использовали этот meta_key нигде, кроме нужного места. Если все или большинство значений уникальны, это может быть лучшим решением.

самым быстрым способом будет пользовательский SQL-запрос, и я не уверен, но вы можете попробовать
$wpdb->get_results("
SELECT posts.* , COUNT(*) 'moodcount'
FROM $wpdb->posts as posts
JOIN $wpdb->postmeta as postmeta
ON postmeta.post_id = posts.ID
AND postmeta.meta_key = 'Mood'
GROUP BY postmeta.meta_key
");
Если что, то это начало.

спасибо, но разве не следует избегать пользовательских запросов 'любой ценой'? Я бы предпочел использовать уровень абстракции WP (так это называется?)... но конечно, если это невозможно..

Пользовательские запросы, если они написаны правильно, могут быть лучше, и вам следует избегать их, только если вы не знаете, что делаете.

Нет никаких причин, почему нельзя объединить код t31os и Bainternet, чтобы получить переиспользуемое подготовленное выражение (в стиле WordPress), которое возвращает и количество, и значения в одной эффективной операции.
Это пользовательский запрос, но он всё ещё использует уровень абстракции базы данных WordPress — так что, например, неважно, как на самом деле называются таблицы или если они изменятся, и это подготовленное выражение, что делает его значительно безопаснее против SQL-инъекций и т.д.
В данном случае я больше не проверяю тип записи и исключаю пустые строки:
$r = $wpdb->get_results( $wpdb->prepare( "
SELECT pm.meta_value AS name, count(*) AS count FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = '%s'
AND pm.meta_value != ''
AND p.post_type = '%s'
GROUP BY pm.meta_value
ORDER BY pm.meta_value
", $key, $type)
);
return $r;
В этом конкретном случае
Это вернёт массив объектов вида:
array
0 =>
object(stdClass)[359]
public 'name' => string 'Hamish' (length=6)
public 'count' => string '3' (length=1)
1 =>
object(stdClass)[360]
public 'name' => string 'Ida' (length=11)
public 'count' => string '1' (length=1)
2 =>
object(stdClass)[361]
public 'name' => string 'John' (length=12)
public 'count' => string '1' (length=1)

Обратите внимание, что по умолчанию используется текущая запись, если не указан post_id.

Это просто возвращает все пользовательские поля для одной записи, которая по умолчанию имеет ID "0". Обратите внимание, что документация явно указывает, что "параметры нельзя считать необязательными".
