Orderby meta_value solo devuelve posts que tienen el meta_key existente

14 may 2015, 02:14:21
Vistas: 13.6K
Votos: 11

Tengo la siguiente wp_query:

$args = array(
    'post_type' => 'news',
    'orderby' => 'meta_key',
    'order' => 'ASC',
    'meta_key'=>'custom_author_name',
    'post_per_page'=>-1
);

$query = new WP_Query($args);

echo $query->found_posts;

echo muestra 10 resultados porque solo hay 10 posts de news con un meta_key = custom_author_name. Pero existen cientos de posts news que no tienen una fila post_meta con ese meta_key específico. Nota que no hay meta_query involucrado. No se asigna meta_value porque solo intento ordenar los posts por meta_key, no filtrar por meta_value.

¿No debería orderby seleccionar todos los posts? ¿Y simplemente ordenarlos?

Si es así, ¿por qué se filtra el resultado? Si no se encuentra el meta_key, ¿por qué no usar simplemente una cadena vacía o un match all?

Si no es así, ¿por qué no?

Si agrego un meta_key a cada post news (incluso si es una cadena vacía) entonces obtengo el resultado esperado. Pero eso parece generar muchas filas de tabla innecesarias.

0
Todas las respuestas a la pregunta 4
1
12

Como se menciona en la respuesta de @ambroseya, se supone que funciona así. Una vez que declaras una meta query, incluso si no estás buscando un valor específico, solo consultará publicaciones que tengan esa meta clave declarada. Si quieres incluir todas las publicaciones y ordenarlas por la meta clave, usa el siguiente código:

$args = array(
    'post_type' => 'news',
    'orderby' => 'meta_value',
    'order' => 'ASC',
    'meta_query' => array(
        'relation' => 'OR',
        array( 
            'key'=>'custom_author_name',
            'compare' => 'EXISTS'           
        ),
        array( 
            'key'=>'custom_author_name',
            'compare' => 'NOT EXISTS'           
        )
    ),
    'posts_per_page'=>-1
);

$query = new WP_Query($args);

echo $query->found_posts;

Lo que hace esto es usar una meta query avanzada que busca publicaciones que tienen y no tienen esa meta clave declarada. Como la que tiene EXISTS va primero, cuando ordenas por meta_value, usará la primera consulta.

14 may 2015 05:41:31
Comentarios

Esto no cambió el orden en absoluto para mí, cuando usé 'orderby' => 'meta_value', sí cambió el orden, pero no tenía nada que ver con el campo meta real.

Jake Jake
26 oct 2016 14:21:38
1

Intenté aplicar la respuesta de @Manny Fleurmond y, al igual que @Jake, no pude hacer que funcionara incluso después de corregir el error tipográfico de que 'orderby' => 'meta_key' debería ser 'orderby' => 'meta_value'. (Y para ser exhaustivo, debería ser 'posts_per_page' no 'post_per_page', pero eso no afecta el problema que se está analizando).

Si observas la consulta SQL generada realmente por la respuesta de @Manny Fleurmond (habiendo corregido los errores tipográficos), esto es lo que obtienes:

SELECT   wp_{prefix}_posts.* FROM wp_{prefix}_posts
LEFT JOIN wp_{prefix}_postmeta ON (wp_{prefix}_posts.ID = wp_{prefix}_postmeta.post_id AND wp_{prefix}_postmeta.meta_key = 'custom_author_name' )
LEFT JOIN wp_{prefix}_postmeta AS mt1 ON ( wp_{prefix}_posts.ID = mt1.post_id )
WHERE 1=1  AND ( 
    wp_{prefix}_postmeta.post_id IS NULL 
    OR 
    mt1.meta_key = 'custom_author_name'
) AND wp_{prefix}_posts.post_type = 'news' AND
(wp_{prefix}_posts.post_status = 'publish' OR wp_{prefix}_posts.post_author = 1 AND wp_{prefix}_posts.post_status = 'private')
GROUP BY wp_{prefix}_posts.ID ORDER BY wp_{prefix}_postmeta.meta_value ASC

Esto ilustra la forma en que WP está analizando las variables de la consulta: está creando una tabla para cada cláusula de meta_query, luego determinando cómo unirlas y por qué ordenar. El orden funcionaría bien si solo estuvieras usando una sola cláusula con 'compare' => 'EXISTS', pero unir la segunda cláusula 'compare' => 'NOT EXISTS' con OR (como debemos hacer) arruina el orden. El resultado es que se usa LEFT JOIN para unir tanto la primera cláusula/tabla como la segunda cláusula/tabla, y la forma en que WP une todo significa que la tabla creada usando 'compare' => 'EXISTS' en realidad se está llenando con meta_values de CUALQUIER campo personalizado, no solo del campo 'custom_author_name' que nos interesa. Por lo tanto, creo que ordenar por esa cláusula/tabla solo dará los resultados deseados si el tipo de publicación particular de 'news' solo tiene un único campo personalizado.

La solución que funcionó para mi situación fue ordenar por la otra cláusula/tabla, la de NOT EXISTS. Aunque parece contrario a la intuición, debido a la forma en que WP analiza las variables de la consulta, es en esta tabla donde meta_value se llena solo con el campo personalizado que nos interesa.

(La única forma en que descubrí esto fue ejecutando el equivalente a esta consulta para mi caso:

SELECT   wp_{prefix}_posts.ID, wp_{prefix}_postmeta.meta_value, mt1.meta_value FROM wp_{prefix}_posts
LEFT JOIN wp_{prefix}_postmeta ON (wp_{prefix}_posts.ID = wp_{prefix}_postmeta.post_id AND wp_{prefix}_postmeta.meta_key = 'custom_author_name' )
LEFT JOIN wp_{prefix}_postmeta AS mt1 ON ( wp_{prefix}_posts.ID = mt1.post_id )
WHERE 1=1  AND ( 
    wp_{prefix}_postmeta.post_id IS NULL 
    OR 
    mt1.meta_key = 'custom_author_name'
) AND wp_{prefix}_posts.post_type = 'news' AND
(wp_{prefix}_posts.post_status = 'publish' OR wp_{prefix}_posts.post_author = 1 AND wp_{prefix}_posts.post_status = 'private')
ORDER BY wp_{prefix}_postmeta.meta_value ASC

Todo lo que hice fue cambiar las columnas que se muestran y eliminar la cláusula GROUP BY. Esto me mostró lo que estaba sucediendo: que la columna postmeta.meta_value estaba extrayendo valores de todos los meta_keys, mientras que la columna mt1.meta_value solo extraía meta_values del campo personalizado de noticias).

La Solución

Como dice @Manny Fleurmond, es la primera cláusula la que se usa para el orderby, por lo que la respuesta es simplemente intercambiar las cláusulas, dando esto:

$args = array(
    'post_type' => 'news',
    'orderby' => 'meta_value',
    'order' => 'ASC',
    'meta_query' => array(
        'relation' => 'OR',
        array( 
            'key' => 'custom_author_name',
            'compare' => 'NOT EXISTS'           
        ),
        array( 
            'key' => 'custom_author_name',
            'compare' => 'EXISTS'           
        )
    ),
    'posts_per_page' => -1
);

$query = new WP_Query($args);

Alternativamente, puedes hacer que las cláusulas sean matrices asociativas y ordenar por la clave correspondiente, así:

$args = array(
    'post_type' => 'news',
    'orderby' => 'not_exists_clause',
    'order' => 'ASC',
    'meta_query' => array(
        'relation' => 'OR',
        'exists_clause' => array( 
            'key' => 'custom_author_name',
            'compare' => 'EXISTS'           
        ),
        'not_exists_clause' => array( 
            'key' => 'custom_author_name',
            'compare' => 'NOT EXISTS'           
        )
    ),
    'posts_per_page' => -1
);

$query = new WP_Query($args);
5 nov 2017 11:09:23
Comentarios

Vale la pena señalar que si la clave meta custom_author_name se establece y luego se elimina, ese meta_key responderá a EXISTS y el efecto será que esos aparecerán junto a las publicaciones que sí tienen un custom_author_name. En mi caso tengo una casilla de verificación, así que estoy usando "value" => "1" en lugar de EXISTS, pero las cadenas de texto necesitarán un enfoque diferente.

djb djb
23 may 2019 18:20:17
0

Así es exactamente como funciona.

Si deseas hacerlo sin agregar filas a la tabla, tendrás que realizar dos consultas. Una con el meta_key que tiene los resultados limitados, y otra que obtenga la lista completa; luego usar PHP para comparar los resultados de ambas consultas (posiblemente eliminando los resultados del meta_key de la otra consulta para eliminar duplicados, o lo que tenga sentido en tu configuración).

14 may 2015 04:45:13
0

Desafortunadamente, así no es como funciona WP_Query. Tan pronto como agregas ese componente "meta", has creado una especie de filtro. Si haces un dump de $query->request verás a lo que me refiero.

En segundo lugar, WP_Query no soporta ordenar por una meta key en absoluto. Puedes ordenar por un meta value para una key particular pero no por la key en sí. Nuevamente, haz un dump de la query para ver lo que quiero decir. Notarás que los componentes de "order" desaparecen si lo intentas.

La forma más limpia de hacer que esto funcione, en mi opinión, es con un par de filtros cortos:

function join_meta_wpse_188287($join) {
  remove_filter('posts_join','join_meta_wpse_188287');
  global $wpdb;
  return ' INNER JOIN '.$wpdb->postmeta.' ON ('.$wpdb->posts.'.ID = '.$wpdb->postmeta.'.post_id)';
}
add_filter('posts_join','join_meta_wpse_188287');

function orderby_meta_wpse_188287($orderby) {
  remove_filter('posts_orderby','orderby_meta_wpse_188287');
  global $wpdb;
  return $wpdb->postmeta.'.meta_key ASC';
}
add_filter('posts_orderby','orderby_meta_wpse_188287');

$args = array(
    'post_type' => 'news',
    'post_per_page'=>-1
);
$q = new WP_Query($args);
var_dump($q->request); // debug
var_dump(wp_list_pluck($q->posts,'post_title')); // debug
14 may 2015 16:41:30