Obtener todos los valores de una clave de campo personalizado (publicación cruzada)
Sé cómo obtener el valor de un campo personalizado para una entrada específica.
get_post_meta($post_id, $key, $single);
Lo que necesito es obtener todos los valores asociados con una clave de campo personalizado específica, a través de todas las entradas.
¿Alguien conoce una manera eficiente de hacer esto? No quisiera tener que recorrer todos los IDs de entradas en la base de datos.
Ejemplo:
4 entradas, todas con diferentes valores para un campo personalizado llamado 'Mood'. 2 entradas tienen el valor 'feliz', 1 entrada tiene 'enojado' y 1 entrada tiene 'triste'
Quiero mostrar: a través de todas las entradas tenemos: dos autores felices, uno enojado y uno triste.
Pero para MUCHAS entradas.
Lo que estoy buscando es:
- una función de WP para obtener esto. o
- una consulta personalizada para obtener esto de la manera más eficiente posible.

Un posible enfoque sería utilizar uno de los métodos auxiliares de la clase WPDB para realizar una consulta más refinada basada en metadatos.
Usando la función $wpdb
get_col
es posible devolver un array simple de datos planos.
Aquí hay una función de ejemplo que consulta la base de datos para obtener todas las publicaciones de un tipo de publicación, estado de publicación y clave de metadatos específicos (o campo personalizado para los menos técnicos).
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;
}
Por ejemplo, si deseas averiguar qué publicaciones tienen una clave de metadatos llamada rating, para el tipo de publicación movies y almacenar esa información en una variable, un ejemplo de dicha llamada sería:
$movie_ratings = get_meta_values( 'rating', 'movies' );
Si deseas hacer algo más que imprimir esos datos en pantalla, la función implode
de PHP puede condensar rápidamente el array en una cadena.
// Imprimir los valores meta separados por un salto de línea
echo implode( '<br />', get_meta_values( 'YOURKEY' ));
También puedes usar los datos devueltos para calcular cuántas publicaciones tienen los valores meta haciendo una iteración (bucle) simple sobre los datos devueltos y construyendo un array de conteos, por ejemplo:
$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;
}
}
// Mostrar el número de valoraciones
printf( '<pre>%s</pre>', print_r( $num_of_ratings ) );
/*
Salida:
Array(
[5] => 10
[9] => 2
)
es decir, hay 10 publicaciones de películas con una valoración de 5 y 2 publicaciones de películas con una valoración de 9.
*/
Esta lógica podría aplicarse a varios tipos de datos y extenderse para funcionar de muchas maneras diferentes. Espero que mis ejemplos hayan sido útiles y lo suficientemente simples de seguir.
Usar transitorios para almacenar en caché los resultados
Y aquí hay una versión actualizada que utiliza transitorios de WordPress para almacenar en caché la consulta, ya que esa parece ser la principal crítica por usar $wpdb
en otras respuestas proporcionadas.
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] ) )
// Cadena existente de WP, debe traducirse tal cual
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;
}
La constante DAY_IN_SECONDS
es una de las varias constantes de tiempo en segundos configuradas por WordPress.
Se actualizaron los nombres de los argumentos y variables para hacerlos más consistentes con la nomenclatura de WordPress y también se implementó DISTINCT
como un parámetro opcional siguiendo el consejo de Howdy_McGee en los comentarios.

También un dato curioso para futuros lectores, si quieres obtener solo valores meta únicos - escribes DISTINCT
justo después del SELECT
en la función anterior. Puede ser útil.

No es bueno ni necesario usar el global $wpdb:
// función para obtener todos los valores meta posibles de la clave meta seleccionada.
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 );

Este sería mi método preferido para hacerlo, en la mayoría de los casos. Realiza cinco consultas en lugar de solo una, pero, como utiliza los procedimientos estándar de WordPress para generarlas y enviarlas, cualquier caché específica de la plataforma (como el Caché de Objetos de WP Engine o algún plugin aleatorio) entrará en acción. Los datos también se almacenarán en la caché interna de WordPress durante la duración de la solicitud, por lo que no será necesario recuperarlos de la base de datos nuevamente, si se necesitan.

También se aplicarán filtros a los datos, lo que podría ser extremadamente importante en, por ejemplo, un sitio multilingüe. Por último, dado que solo utiliza funciones estándar del núcleo de WordPress, es mucho menos probable que se rompa con una actualización futura.

Esto podría hacerse más eficiente limitando la consulta al ID de la publicación.
Añadir: 'fields' => 'ids'
Entonces, el array de consulta se vería así:
array(
'post_type' => $post_type,
'meta_key' => $meta_key,
'posts_per_page' => -1,
'fields' => 'ids'
)

Puedo imaginar casos donde sería válido tener múltiples valores meta del mismo valor, y por lo tanto no hice esa adición a mi código. Si quieres valores distintos, esta sería la forma de hacerlo. Adicionalmente, también podrías agregarlo como un argumento para la función (así puedes usarlo o no, según sea apropiado).

Para obtener todos los valores meta por una clave meta
Consultar wp->db en el codex de WordPress
$values = $wpdb->get_col("SELECT meta_value
FROM $wpdb->postmeta WHERE meta_key = 'yourmetakey'" );

El problema con este enfoque es la falta de especificidad, obtendrás numerosos resultados con dicha consulta, que podrían incluir borradores, elementos eliminados, entradas, páginas y cualquier otro tipo de publicación que exista. Nunca deberías consultar lo que no necesitas, aquí definitivamente se requiere especificidad.

Si bien es cierto que podrías obtener valores de otros tipos de publicaciones y estados, hay ocasiones en las que solo necesitas los valores y no has utilizado ese meta_key en ningún otro lugar excepto donde lo necesitas. Si todos/la mayoría de los valores son únicos, esta puede ser la mejor solución.

la forma más rápida sería una consulta SQL personalizada y no estoy seguro pero puedes intentar
$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
");
Si algo, al menos es un comienzo.

gracias, pero ¿no deberían evitarse las consultas personalizadas "a toda costa"? Preferiría usar la capa de abstracción de WP (¿es así como se llama?)... pero claro, si no es posible...

Las consultas personalizadas, si están escritas correctamente, pueden ser mejores y solo deberías evitarlas si no sabes lo que estás haciendo.

No hay razón por la cual no puedas combinar el código de t31os y Bainternet para tener una sentencia preparada reutilizable (al estilo WordPress) que devuelva el conteo y los valores en una sola operación eficiente.
Es una consulta personalizada pero sigue utilizando la capa de abstracción de bases de datos de WordPress, así que, por ejemplo, no importa cuáles sean realmente los nombres de las tablas o si cambian, y al ser una sentencia preparada estamos mucho más protegidos contra ataques SQL, etc.
En este caso ya no estoy verificando el tipo de publicación y estoy excluyendo cadenas vacías:
$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;
En este caso particular...
Esto devolverá un array de objetos como este:
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)

Ten en cuenta que esto toma por defecto la entrada actual, cuando no se especifica un post_id.

Esto simplemente devuelve todos los campos personalizados para una sola entrada, que por defecto es la que tiene ID "0". Ten en cuenta que la documentación menciona explícitamente que "los parámetros no deben considerarse opcionales".
