Ordenar por valor meta incluyendo entradas que no lo tienen
He estado modificando la búsqueda integrada de WP usando el filtro pre_get_posts
, permitiendo al usuario ordenar las entradas (incluyendo varios tipos de post personalizados) por diferentes campos.
El problema que tengo es que cuando le digo a WP que ordene por un valor meta, excluirá todas las entradas que no tienen ese valor meta establecido. Esto hace que el número de resultados cambie si cambias la ordenación de, por ejemplo, "Precio" a "Fecha" porque las "Entradas" no tienen "Precio" establecido pero los "Artículos" sí.
Esto no es lo que quiero, así que me gustaría saber si hay alguna manera de incluir TODAS las entradas - incluso aquellas que carecen del valor meta por el que estoy ordenando - y poner las que no tienen el valor al final.
Sé cómo ordenar por más de un campo pero eso no ayuda.
Gracias
Parece que no soy el único con esta pregunta: ¿Forma de incluir posts tanto con como sin cierta meta_key en los argumentos para wp_query? pero no hay solución allí.
Actualización
He probado la respuesta pero no estoy seguro si la entendí correctamente, esto es lo que tengo ahora:
<?php
function my_stuff ($qry) {
$qry->set('meta_query', array(array(
'key' => 'item_price',
'value' => '',
'compare' => 'NOT EXISTS'
)));
$qry->set('orderby', 'meta_value date'); # La ordenación funciona tanto con meta_value como con meta_value_num - He probado ambos
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');
}
El valor meta es un número (se usa para almacenar un precio como sugiere el nombre)
Actualización 2
He comentado la parte de ordenación y ahora solo tengo esto:
<?php
$qry->set('meta_query', array(array(
'key' => 'item_price',
'value' => '',
'compare' => 'NOT EXISTS'
)));
Con este código la consulta parece devolver todas las entradas que no tienen la clave item_price
y ninguna de las entradas que la tienen. Es decir, el problema ahora está invertido.
Si agrego el código de ordenación también, obtengo 0 resultados.
Edición: ...tres años después... :P Tuve este problema de nuevo. Probé todas las respuestas dadas y ninguna funciona. No estoy seguro por qué algunas personas parecen pensar que funcionan pero al menos para mí no funcionan.
La solución que terminé usando es utilizar el filtro save_post
- asegurándome de que todas las entradas tengan el campo personalizado por el que deseo ordenar. Es un poco molesto tener que hacerlo, pero mientras lo hagas temprano probablemente no tendrás problemas.
En este caso estaba construyendo un "contador de vistas" en las entradas y quería que los usuarios pudieran ordenar por las entradas más leídas. Nuevamente, las entradas que nunca han sido vistas (supongo que es bastante improbable, pero aun así) desaparecían al ordenar por el contador de vistas. Agregué este fragmento de código para asegurarme de que todas las entradas tengan un contador de vistas:
add_action('save_post', function ($postId) {
add_post_meta($postId, '_sleek_view_count', 0, true);
});

Sencillo y fácil, probado en 2018 y actualmente en uso en producción.
Actualización 2022: Modificado para que la consulta NOT_EXISTS
venga antes de la consulta EXISTS
, y se aclara el efecto que tienen múltiples claves en la cláusula orderby
.
$query->set( 'meta_query', array(
'relation' => 'OR',
array(
'key' => 'custom_meta_key',
'compare' => 'NOT EXISTS'
),
array(
'key' => 'custom_meta_key',
'compare' => 'EXISTS'
),
) );
$query->set( 'orderby', 'meta_value title' );
Esto verifica todos los elementos con y sin la meta clave, sin especificar un valor. La consulta meta proporciona la clave para el orderby de manera confiable. Ha sido probado. La cláusula orderby
con meta_value
/meta_value_num
usará la última clave en la cadena.
Ejemplo práctico
/**
* Modifica la consulta antes de recuperar los posts. Establece los
* parámetros `meta_query` y `orderby` cuando no se ha establecido
* ningún parámetro `orderby` (ordenación por defecto).
*
* @param WP_Query $query El objeto completo `WP_Query`.
* @return void
*/
function example_post_ordering( $query ) {
// Si no está en wp-admin,
// y la consulta es la consulta principal,
// y la consulta no es una consulta singular,
// y la consulta no tiene un parámetro orderby establecido...
// Nota: verificar tipos de post, etc. aquí según sea necesario.
if ( ! is_admin()
&& $query->is_main_query()
&& ! $query->is_singular()
&& empty( $query->get( 'orderby' ) ) ) {
// Establecer solo `meta_key` no es suficiente, ya que esto
// ignorará los posts que aún no tengan, o nunca tendrán un
// valor para la clave especificada. Esta meta consulta
// registrará la `meta_key` para ordenar, pero no ignorará
// aquellos posts sin un valor para esta clave.
$query->set( 'meta_query', array(
'relation' => 'OR',
array(
'key' => 'custom_meta_key',
'compare' => 'NOT EXISTS'
),
array(
'key' => 'custom_meta_key',
'compare' => 'EXISTS'
),
) );
// Ordenar por el meta valor, luego por el título si múltiples
// posts comparten el mismo valor para la meta clave proporcionada.
// Usar `meta_value_num` si los meta valores son numéricos.
$query->set( 'orderby', 'meta_value title' );
}
}
add_action( 'pre_get_posts', 'example_post_ordering', 10 );
Esto ordenará los posts por custom_meta_key
por defecto, y no ignorará los posts sin un valor para esa clave.

Solo con leer el código, parece que lo único que hace es obtener posts que tienen custom_meta_key
y posts que no tienen custom_meta_key
. Siéntete libre de incluir un ejemplo práctico funcional con ordenamiento.

Tienes razón, eso es todo lo que está haciendo, pero la línea de abajo es la responsable de ordenar por meta_value (de la meta key que se está consultando). $query->set( 'orderby', 'meta_value title' );
(Ordenar por meta value, luego por título cuando múltiples posts tienen el mismo valor para la meta key). Esto debería hacerse en el hook pre_get_posts
, usando la variable $query
que se pasa. Ten en cuenta que la pregunta era cómo ordenar por meta value, sin ignorar los posts que no tienen un valor para esa meta key.

De acuerdo, lo intentaré la próxima vez que me enfrente a este problema.

Funcionó para mí en una llamada personalizada get_posts()
para colocar las publicaciones con meta _featured
en la parte superior, y luego ordenar por fecha después de eso. ¡Gracias!

Esto solo funcionó para mí cuando invertí el orden de NOT EXISTS y EXISTS. Funcionaba, pero el orderby se ignoraba.

@mikemike gracias por esa información, haciendo estos cambios funcionó para mí

Este método devolverá todas las publicaciones, incluyendo aquellas con y sin el meta_key
solicitado, pero hará cosas extrañas al ordenar.
add_action('pre_get_posts', 'my_stuff');
function my_stuff ($qry) {
$qry->set(
'meta_query',
array(
'relation' => 'OR', # Las coincidencias con este meta_query se añadirán a las que coincidan con la consulta 'meta_key'
array(
'key' => 'item_price',
'value' => 'bug #23268',
'compare' => 'NOT EXISTS'
)
)
);
$qry->set('orderby', 'meta_value date'); # El ordenamiento funciona con meta_value y meta_value_num - probé ambos
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');
}
Descubrí esto experimentando con todas las diferentes respuestas a esta pregunta y analizando el SQL generado mediante prueba y error. Parece que configurar array('meta_query' => array('relation' => 'OR'))
genera un LEFT JOIN
apropiado en lugar de un INNER JOIN
, que es necesario para incluir publicaciones que carecen de los metadatos. Especificar NOT EXISTS
evita que la cláusula WHERE
filtre las publicaciones que no tienen el campo meta. Para esta WP_Query
en particular, el SQL generado es (se añadieron indentación/saltos de línea):
SELECT SQL_CALC_FOUND_ROWS
wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
INNER JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id
LEFT JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id AND mt1.meta_key = 'item_price')
WHERE 1=1
AND ( wp_term_relationships.term_taxonomy_id IN (2) )
AND wp_posts.post_type = 'post'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private')
AND (wp_postmeta.meta_key = 'item_price'
-- ¡Mira aquí! Le damos permiso a SQL para elegir una fila
-- aleatoria de wp_postmeta cuando esta publicación en particular
-- carece de 'item_price':
OR mt1.post_id IS NULL )
GROUP BY wp_posts.ID
ORDER BY wp_postmeta.meta_value,wp_posts.post_date DESC
LIMIT 0, 10
El resultado es un listado de todas las publicaciones con el meta_value de item_price
y aquellas que carecen de item_price
. Todas las publicaciones con item_price
se ordenarán correctamente entre sí, pero las publicaciones sin item_price
usarán algún otro valor meta aleatorio (por ejemplo, _edit_last
que parece ser 1
con frecuencia en mi base de datos u otro metadato interno de WordPress completamente arbitrario) para su wp_postmeta.meta_value
en la cláusula ORDER BY
. Así que, aunque este método está cerca y puede parecer funcionar para ciertos datos, está roto. Todo lo que puedo decir es que, si los valores de tu item_price
no entran en conflicto con los campos meta aleatorios que MySQL elige para las publicaciones sin item_price
, esto podría funcionarte. Si solo necesitas garantizar que tus publicaciones con item_price
estén ordenadas correctamente entre sí sin importar el orden del resto, puede ser aceptable. Pero creo que esto es solo una limitación en WordPress. Por favor corríjanme, espero estar equivocado y que haya una manera de solucionarlo ;-).
Parece que para el INNER JOIN wp_postmeta
, MySQL está eligiendo una fila aleatoria entre las múltiples filas de postmeta
asociadas con la publicación cuando falta el meta_key
en dicha publicación. Desde una perspectiva SQL, necesitamos averiguar cómo decirle a WordPress que genere ORDER BY mt1.meta_value
. Esta columna es propiamente NULL
cuando falta nuestro meta_key
solicitado, a diferencia de wp_postmeta.meta_value
. Si pudiéramos hacer eso, SQL ordenaría estos valores NULL
(entradas faltantes) antes que cualquier otro valor, dándonos un orden bien definido: primero todas las publicaciones sin el campo postmeta específico, luego las que sí lo tienen. Pero ese es el problema: 'orderby' => 'meta_value'
solo puede referirse a 'meta_key' => 'item_price'
y el wp_postmeta
sin alias siempre es un INNER JOIN
en lugar de un LEFT JOIN
, lo que significa que wp_postmeta.meta_value
y wp_postmeta.meta_key
nunca pueden ser NULL
.
Así que supongo que debo decir que esto no es posible con el WP_Query
integrado de WordPress tal como está documentado actualmente (en WordPress 3.9.1). Molesto. Así que si realmente necesitas que esto funcione correctamente, probablemente necesites engancharte en otra parte de WordPress y modificar el SQL generado directamente.

¡Se ve muy prometedor! Lo probaré la próxima vez que tenga este problema. Me gustaría darte la respuesta ahora mismo, pero preferiría confirmar que funciona para mí primero.

Esto evitó que se mostrara cualquier cosa en mi caso. No apareció nada después de implementar esto.

@Jake Sí, lo mismo aquí. Tuve este problema nuevamente hoy e intenté esto. Devuelve 0 resultados.

El problema que todos están teniendo aquí tiene que ver con el orden de las consultas meta. Para ordenar correctamente, necesitarás colocar la consulta "NOT EXISTS" antes de la consulta "EXISTS".
La razón de esto es porque WordPress utiliza el meta_value del último "LEFT JOIN" en la cláusula "ORDER BY".
Por ejemplo:
$pageQuery = new WP_Query([
'meta_query' => [
'relation' => 'OR',
['key' => 'item_price', 'compare' => 'NOT EXISTS'], // ¡esto va primero!
['key' => 'item_price', 'compare' => 'EXISTS'],
],
'order' => 'DESC',
'orderby' => 'meta_value_num',
'post_status' => 'publish',
'post_type' => 'page',
'posts_per_page' => 10,
]);

Existen dos posibles soluciones para esto:
1. Todos los posts tienen meta
La mejor solución que he encontrado aquí es asignar un precio de 0 a los demás posts/productos. Puedes hacer esto manualmente, o recorrer todos los posts y si el precio está vacío entonces actualizarlo.
Para hacer esto manejable en el futuro puedes usar el hook save_post
y asignarles un valor cuando se añadan por primera vez (solo si está vacío).
2. Múltiples consultas
Podrías ejecutar la primera consulta como lo estás haciendo y almacenar los IDs de los posts devueltos. Luego podrías ejecutar otra consulta para todos los posts ordenados por fecha, excluyendo los IDs obtenidos en la primera consulta.
Puedes entonces mostrar los dos resultados por separado en el orden deseado.

Tres años después y tuve el mismo problema de nuevo :P Tuve que usar el método save_post
(he actualizado mi pregunta con el código que usé).

Esto es posible sin necesidad de usar hooks o ejecutar múltiples consultas. Mira mi respuesta.

También me encontré con un problema similar y la siguiente solución me ayudó:
$args = array(
'post_type' => 'kosh_products',
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
'category_sort_order' => array(
'key' => '_sort_order',
'compare' => 'EXISTS'
),
'category_sort_order_not_exists' => array(
'key' => '_sort_order',
'compare' => 'NOT EXISTS'
),
),
'orderby' => array(
'category_sort_order' => 'ASC',
'date' => 'ASC'
));
$query = new WP_Query( $args );
Encontré una descripción en WordPress Codex con el título "'orderby' con múltiples 'meta_key'": https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters

Creo que tengo una solución.
Puedes usar dos meta_key
s, una que todos los posts tengan (como "_thumbnail_id")
, y la meta_key
que deseas usar como filtro.
Así que tus argumentos serían:
$qry->set(
'meta_query',
array(
'relation' => 'OR',
array(
'key' => 'item_price',
'value' => '',
'compare' => 'EXISTS'
),
array(
'key' => 'item_price',
'value' => '',
'compare' => 'EXISTS'
)
)
);
$qry->set('orderby', 'meta_value date'); # El ordenamiento funciona con meta_value así como con meta_value_num - he probado ambos
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');

Yo mismo tuve este problema con valores meta numéricos y me di cuenta de que el orden de la consulta también es importante. Para mí, la consulta NOT EXISTS
debe ser la primera.
Ejemplo:
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_query', [
'relation' => 'OR',
[ 'key' => 'your_meta_name', 'compare' => 'NOT EXISTS' ],
[
'key' => 'your_meta_name',
'compare' => 'EXISTS',
],
] );
También es importante, para obtener la dirección correcta con valores numéricos, que el ’orderby’
general esté configurado como ’meta_value_num’
. De lo contrario, obtendrás resultados extraños con valores numéricos, por ejemplo:
1, 2, 20, 21, 3, 4, 5 …
En lugar de:
1, 2, 3, 4, 5 … 20, 21

La consulta meta OR que combina NOT EXISTS
y EXISTS
funciona pero causó una consulta lenta.
Esta solución funciona mucho más rápido.
La clave principal estuvo en la cláusula SQL order by:
order by YOUR_FIELD is NULL, YOUR_FIELD
add_filter('posts_orderby', function (string $orderby, WP_Query $query) {
if ('MY_CUSTOM_SORT' === $query->get('orderby')) {
$orderby = "mta.meta_value is NULL, mta.meta_value ='', mta.meta_value ASC";
}
return $orderby;
}, 10, 2);
add_filter('posts_join', function (string $join, WP_Query $query) {
if ('MY_CUSTOM_SORT' === $query->get('orderby')) {
$join .= " LEFT JOIN wp_postmeta AS mta ON ( wp_posts.ID = mta.post_id AND mta.meta_key = 'SOME_META_KEY')";
}
return $join;
}, 10, 2);
Y en los argumentos de tu WP_Query:
$args['orderby'] = 'MY_CUSTOM_SORT';

Esta es una pregunta muy antigua pero ninguna de las respuestas era lo suficientemente robusta para que yo la usara y estuviera satisfecho con los resultados. La solución a continuación todavía utiliza WP_Query
, pero manipula las declaraciones join y order by.
Hace uso de una función de MySQL llamada ORDER BY IF
, que permite ejecutar lógica durante tu declaración ORDER BY. Esto es importante porque las otras soluciones aquí simplemente agrupan los resultados sin valor, ya sea al principio o al final de la lista, lo cual es inútil cuando intentas ordenar por 2 claves.
En el siguiente ejemplo tengo un CPT de 'businesses' (negocios). Quiero que todos se ordenen por título, pero aquellos con is_premium
deben aparecer primero. He comentado el código para que puedas ver lo que está pasando en cada parte.
<?php
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$args = [
'post_type' => 'business',
'posts_per_page' => 12,
'meta_query' => [
'relation' => 'OR',
[
'key' => 'is_premium',
'compare' => 'NOT EXISTS' // NOT EXISTS primero es importante
],
[
'key' => 'is_premium',
'compare' => 'EXISTS'
],
]
,
'orderby' => [
'meta_value' => 'DESC', // Este es el campo is_premium
'title' => 'ASC',
],
'paged' => $paged
];
// Añadimos nuestro propio join a la consulta
function custom_join($join) {
global $wpdb;
if( ! is_admin() ) {
$join .= $wpdb->prepare(
' LEFT JOIN ' . $wpdb->postmeta . ' cpm ON cpm.post_id = ' . $wpdb->posts . '.ID AND cpm.meta_key = %s'
, 'is_premium' );
}
return $join;
}
add_filter('posts_join','custom_join');
// Añadimos nuestro propio order by a la consulta
function custom_orderby($orderby_statement){
global $wpdb;
if ( ! is_admin() ) {
// Aquí ordenamos por meta_value SOLO si es 1, de lo contrario lo ignoramos para esa declaración de orden, lo que
// significa que se ordenará por la siguiente declaración (título), igual que los otros resultados
$orderby_statement = "IF(cpm.meta_value = 1, 1, 0) DESC, wp_posts.post_title ASC ";
}
return $orderby_statement;
}
add_filter('posts_orderby','custom_orderby', 10, 2 );
// Consulta
$business = new WP_Query( $args );
// Eliminamos los filtros
remove_filter('posts_orderby','custom_orderby');
remove_filter('posts_join','custom_join');

Si es apropiado, puedes agregar un valor meta predeterminado cada vez que se guarda o actualiza una publicación, si el valor meta no existe.
function addDefaultMetaValue($post_id) {
add_post_meta($post_id, 'item_price', 0, true);
}
add_action('save_post', 'addDefaultMetaValue');
Si estás utilizando un tipo de publicación personalizado, reemplaza add_action('save_post', 'addDefaultMetaValue');
por add_action('save_post_{post_type}', 'addDefaultMetaValue');
por ejemplo: add_action('save_post_product', 'addDefaultMetaValue');

Existe un posible valor orderby
de meta_value
para eso.
$query = new WP_Query( array (
'meta_key' => 'your_keys_name',
'orderby' => 'meta_value',
'order' => 'DESC',
'meta_query' => array( array(
'key' => 'your_meta_key',
'value' => '',
'compare' => 'NOT EXISTS',
// 'type' => 'CHAR',
) )
) );
Si tienes valores numéricos, simplemente usa meta_value_num
en su lugar.
Aviso: Esto no ha sido probado, pero debería funcionar. El punto es que necesitas especificar tus valores meta_key
y key
. De lo contrario, no puedes comparar contra valores no existentes, lo que debería permitir consultar ambos tipos de posts. Es un poco hackeado, pero mientras funcione...

Gracias por tu respuesta, por favor revisa mi pregunta actualizada, no estoy seguro de haber entendido correctamente.

Aún no he logrado que esto funcione, así que si tienes una solución me encantaría saber qué estoy haciendo mal. Además, he establecido una recompensa en SO si quieres reclamarla: http://stackoverflow.com/questions/17016770/wordpress-order-by-meta-value-if-it-exists-else-date/17183618

Dos cosas. 'your_keys_name'
y 'your_meta_key'
deberían ser la misma cadena en lugar de distintas, de lo contrario parece que has malinterpretado la pregunta. En segundo lugar, probé esto en mi configuración local y excluye cualquier publicación donde existe la clave (a través del meta_query
) y excluye cualquier publicación donde falta la clave (a través de meta_key
), lo que resulta en que no se muestren publicaciones. Sin embargo, esta respuesta es un paso hacia algo que funciona al menos ;-).

Oh, curiosamente, esta respuesta sí funciona si solo agregas 'relation' => 'OR'
al meta_query
. Cosas locas o_o.

@binki Simplemente envía una [edición] a mi pregunta y cambia las partes que creas que deberían modificarse. Este es un sitio impulsado por la comunidad :)

@kaiser, seguí investigando, incluso haciendo pruebas, y terminé con un pequeño monólogo. Supongo que mi nueva respuesta es el lugar adecuado para guardar mis divagaciones ;-).

Esta solución funcionó para mí:
add_action( 'pre_get_posts', 'orden_portfolio' );
function orden_portfolio( $query ) {
if( ! is_admin() ) {
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'order', 'ASC' );
$query->set( 'meta_query', [
'relation' => 'OR',
[
'key' => 'ce_orden',
'compare' => 'NOT EXISTS' ],
[
'key' => 'ce_orden',
'compare' => 'EXISTS',
],
] );
return $query;
}
}
Sin embargo, esta solución primero muestra los registros con meta_value nulo. Esta otra solución muestra el orden ASC y los nulos al final:
function custom_join($join) {
global $wpdb;
if( ! is_admin() ) {
$join .= $wpdb->prepare(
' LEFT JOIN ' . $wpdb->postmeta . ' cpm ON cpm.post_id = ' . $wpdb->posts . '.ID AND cpm.meta_key = %s'
, 'ce_orden' );
}
return $join;
}
add_filter('posts_join','custom_join');
function custom_orderby($orderby_statement){
global $wpdb;
if ( ! is_admin() ) {
$orderby_statement = "CAST( COALESCE(cpm.meta_value,99999) as SIGNED INTEGER) ASC";
}
return $orderby_statement;
}
add_filter('posts_orderby','custom_orderby', 10, 2 );

Pude resolver esto usando una sola consulta con los argumentos adecuados. WP 4.1+
$args = array(
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'custom_sort',
'compare' => 'EXISTS'
),
array(
'key' => 'custom_sort',
'compare' => 'NOT EXISTS'
)
),
'orderby' => 'meta_value_num title',
'order' => 'ASC',
));
$query = new WP_Query($args);
Ver mi respuesta completa aquí: https://wordpress.stackexchange.com/a/370841/15209

Terminé solucionando esto con un pequeño truco (en mi opinión), pero cumplió su propósito en mi caso particular.
Puedes engancharte a los filtros posts_join_paged y posts_orderby para modificar las cadenas de unión y ordenamiento. Esto te permitirá ordenar por lo que quieras siempre que primero lo unas, en lugar de que WP_Query asuma que el campo debe existir para esa publicación en particular. Luego puedes eliminar los argumentos meta_key
, orderby
y order
de tu WP_Query.
A continuación hay un ejemplo. Al inicio de cada función tuve que agregar condiciones de escape para ciertos casos, ya que esto se aplicará a todo lo que use WP_Query. Es posible que necesites modificarlo para adaptarlo a tus necesidades específicas.
La documentación sobre estos dos filtros es lamentablemente escasa, así que... ¡buena suerte! :)
add_filter('posts_join_paged', 'edit_join', 999, 2);
add_filter('posts_orderby', 'edit_orderby', 999, 2);
/**
* Editar JOIN
*
* @param string $join_paged_statement
* @param WP_Query $wp_query
* @return string
*/
function edit_join($join_paged_statement, $wp_query)
{
global $wpdb;
if (
!isset($wp_query->query)
|| $wp_query->is_page
|| $wp_query->is_admin
|| (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
) {
return $join_paged_statement;
}
$join_to_add = "
LEFT JOIN {$wpdb->prefix}postmeta AS my_custom_meta_key
ON ({$wpdb->prefix}posts.ID = my_custom_meta_key.post_id
AND my_custom_meta_key.meta_key = 'my_custom_meta_key')
";
// Solo agregar si no está ya incluido
if (strpos($join_paged_statement, $join_to_add) === false) {
$join_paged_statement = $join_paged_statement . $join_to_add;
}
return $join_paged_statement;
}
/**
* Editar ORDER BY
*
* @param string $orderby_statement
* @param WP_Query $wp_query
* @return string
*/
function edit_orderby($orderby_statement, $wp_query)
{
if (
!isset($wp_query->query)
|| $wp_query->is_page
|| $wp_query->is_admin
|| (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
) {
return $orderby_statement;
}
$orderby_statement = "my_custom_meta_key.meta_value DESC";
return $orderby_statement;
}

El código funciona. Pero el meta_value se maneja como una cadena. Así que 6 tiene una calificación más alta que 50. ¿Es posible hacer modificaciones para tratarlos como números?

@Drivingralle cast(my_custom_meta_key.meta_value as unsigned) DESC
debería funcionar...

Terminé utilizando el filtro 'get_meta_sql' que proporciona WordPress
add_filter( 'get_meta_sql', 'adjust_the_meta_sql' ), 10, 2 );
La función se ve así
function adjust_the_meta_sql( $sql, $queries ) {
if ( 'my_post_type' === filter_input( INPUT_GET, 'post_type' )
&& '_my_key' === $queries[0]['key'] ) {
$sql['join'] = preg_replace( '/INNER JOIN wp_postmeta ON \(([^)]+)\)/', 'LEFT JOIN wp_postmeta ON ($1 ' . $sql['where'] . ')', $sql['join'] );
$sql['where'] = '';
}
return $sql;
}
Resuelve dos problemas con el SQL generado por WordPress.
- Convierte el INNER JOIN en un LEFT JOIN
- Mueve la cláusula WHERE a la cláusula ON.
Tu consulta particular puede ser diferente, así que asegúrate de probar y ajustar según sea necesario.

Creo que lo que @kaiser estaba intentando hacer era decirle a la consulta que devuelva todos los posts que tengan esa meta clave aplicando una especie de condición where ficticia para no filtrar ninguno de esos posts. Entonces, si sabes que todos los valores que pueden tomar tus campos personalizados son x,y,z, podrías decir "WHERE meta_key IN(x,y,z)" pero la idea es que puedes evitar ese problema por completo diciendo != (''):
$query = new WP_Query( array (
'orderby' => 'meta_value_num',
'order' => 'DESC',
'meta_query' => array( array(
'key' => 'item_price',
'value' => '',
'compare' => '!=',
) )
) );
Tampoco lo he probado pero parece que vale la pena intentarlo :-).

Amigos, gracias por todas las respuestas válidas pero, actualmente (2022), el anidamiento es la clave:
$query->set( 'orderby', array(
'meta_value_num' => 'DESC',
'title' => 'ASC' )
); // prioridades de orden
$query->set( 'meta_query', array(
'featured' => array(
'relation' => 'OR',
array(
'key' => '_featured',
'value' => '1', // mostrar primero los posts con valor = 1
),
array( // estas reglas anidadas tienen menos prioridad para el orden, pero nos aseguran mostrar todos los demás posts
'relation' => 'OR',
array(
'key' => '_featured',
'compare' => 'NOT EXISTS', // se explica por sí mismo
),
array(
'key' => '_featured',
'value' => '0', // el caso donde el valor existe pero necesita ser considerado como no destacado
)
)
)
));
Caso extra: No he probado esto, pero si el meta no es un valor 0/1, la tercera regla debería ser:
array(
'key' => '_featured',
'value' => '[valor de prioridad]',
'compare' => '!='
)
