La forma más eficiente de obtener publicaciones con postmeta

36

Necesito obtener un montón de publicaciones con sus metadatos. Por supuesto, no puede obtener metadatos con una consulta de publicaciones estándar, por lo que generalmente tiene que hacer un get_post_custom()para cada publicación.

Estoy intentando con una consulta personalizada, como esta:

$results = $wpdb->get_results("
    SELECT  p.ID,
        p.post_title,
        pm1.meta_value AS first_field,
        pm2.meta_value AS second_field,
        pm3.meta_value AS third_field
    FROM    $wpdb->posts p LEFT JOIN $wpdb->postmeta pm1 ON (
            pm1.post_id = p.ID  AND
            pm1.meta_key    = 'first_field_key'
        ) LEFT JOIN $wpdb->postmeta pm2 ON (
            pm2.post_id = p.ID  AND
            pm2.meta_key    = 'second_field_key'
        ) LEFT JOIN $wpdb->postmeta pm3 ON (
            pm3.post_id = p.ID  AND
            pm3.meta_key    = 'third_field_key'
        )
    WHERE   post_status = 'publish'
");

Parece funcionar. Se dispara si usa cualquiera de esos metacampos de una manera que permita múltiples metavalores para él en la misma publicación. No puedo pensar en unirme para hacer eso.

Entonces, pregunta 1: ¿hay una combinación, subconsulta o lo que sea, para incorporar metacampos de valores múltiples?

Pero pregunta 2: ¿Vale la pena? ¿Cuántas postmetacombinaciones de tablas agrego antes de que sea preferible un enfoque de 2 consultas? Podría tomar todos los datos de publicación en una consulta, luego tomar todos los postmeta relevantes en otra, y combinar el meta con los datos de publicación en un conjunto de resultados en PHP. ¿Acabaría siendo más rápido que una única consulta SQL cada vez más compleja, si eso fuera posible?

Siempre pienso: "Ofrezca la mayor cantidad de trabajo posible a la base de datos". ¡No estoy seguro de esto!

Steve Taylor
fuente
No estoy seguro de si quieres hacer las uniones. La combinación de get_posts () y get_post_meta () le devuelve los mismos datos. De hecho, es menos eficiente usar las combinaciones, ya que puede estar recuperando datos que no usará más adelante.
rexposadas el
2
¿No se almacenan automáticamente los metadatos de publicación de todos modos?
Manny Fleurmond el
@rxn, si tengo varios cientos de publicaciones que regresan (son un tipo de publicación personalizado), seguramente es una carga de DB bastante pesada get_posts(), ¿entonces get_post_meta()para cada una de ellas? @MannyFleurmond, es difícil encontrar información concreta sobre el almacenamiento en caché incorporado de WP, pero AFAIK almacenaría cosas en caché por solicitud. La llamada al servidor para capturar estos datos es una llamada AJAX, y no creo que otra cosa esté tomando cosas antes.
Steve Taylor
En realidad, voy por múltiples consultas y almacenamiento en caché de los resultados. Resulta que no solo necesitamos metadatos de publicación, incluidos los campos que tienen múltiples valores, también necesitamos datos sobre los usuarios conectados a las publicaciones a través de metacampos (dos conjuntos de estos), además de metadatos de usuario en ellos. ¡SQL puro definitivamente está fuera de la ventana!
Steve Taylor

Respuestas:

58

La metainformación posterior se almacena automáticamente en la memoria caché para un estándar WP_Query(y la consulta principal), a menos que usted le diga específicamente que no lo haga utilizando el update_post_meta_cacheparámetro.

Por lo tanto, no debe escribir sus propias consultas para esto.

Cómo funciona el metacaching para consultas normales:

Si el update_post_meta_cacheparámetro a WP_Queryno se establece en falso, luego de recuperar las publicaciones de la base de datos, se update_post_caches()llamará a la función, que a su vez llama update_postmeta_cache().

La update_postmeta_cache()función es un contenedor para update_meta_cache(), y esencialmente llama a un simple SELECTcon todos los ID de las publicaciones recuperadas. Esto hará que obtenga todos los postmeta, para todas las publicaciones en la consulta, y guarde esos datos en el caché de objetos (usando wp_cache_add()).

Cuando haces algo como get_post_custom(), primero está comprobando el caché de ese objeto. Por lo tanto, no está haciendo consultas adicionales para obtener la meta meta en este momento. Si ha recibido la publicación en un WP_Query, entonces el meta ya está en la memoria y lo obtiene directamente desde allí.

Las ventajas aquí son muchas veces mayores que hacer una consulta compleja, pero la mayor ventaja proviene del uso de la caché de objetos. Si usa una solución de memoria caché persistente como XCache o memcached o APC o algo así, y tiene un complemento que puede vincular su caché de objetos (W3 Total Cache, por ejemplo), entonces todo el caché de objetos se almacena en memoria rápida ya. En este caso, hay cero consultas necesarias para recuperar sus datos; Ya está en la memoria. El almacenamiento en caché de objetos persistente es impresionante en muchos aspectos.

En otras palabras, su consulta es probablemente más lenta que usar una consulta adecuada y una solución simple de memoria persistente. Usa lo normal WP_Query. Ahórrate un poco de esfuerzo.

Adicional: update_meta_cache() es inteligente, por cierto. No recuperará metainformación para publicaciones que ya tienen su metainformación en caché. No obtiene el mismo meta dos veces, básicamente. Súper eficiente

Adicional adicional: "Ofrezca la mayor cantidad de trabajo posible a la base de datos" ... No, esta es la web. Se aplican diferentes reglas. En general, siempre desea dar el menor trabajo posible a la base de datos, si es factible. Las bases de datos son lentas o están mal configuradas (si no lo configuró específicamente, puede apostar un buen dinero a que esto es cierto). A menudo se comparten entre muchos sitios y se sobrecargan hasta cierto punto. Por lo general, tiene más servidores web que bases de datos. En general, solo desea obtener los datos que desea de la base de datos de la forma más rápida y sencilla posible, y luego ordenarlos utilizando el código del lado del servidor web. Como principio general, por supuesto, los diferentes casos son todos diferentes.

Otón
fuente
30

Recomendaría una consulta dinámica. Usando tu ejemplo:

SELECT  p.ID,   
        p.post_title, 
        MAX(CASE WHEN wp_postmeta.meta_key = 'first_field' then wp_postmeta.meta_value ELSE NULL END) as first_field,
        MAX(CASE WHEN wp_postmeta.meta_key = 'second_field' then wp_postmeta.meta_value ELSE NULL END) as second_field,
        MAX(CASE WHEN wp_postmeta.meta_key = 'third_field' then wp_postmeta.meta_value ELSE NULL END) as third_field,

 FROM    wp_posts p LEFT JOIN wp_postmeta pm1 ON ( pm1.post_id = p.ID)                      
GROUP BY
   wp_posts.ID,wp_posts.post_title
Ethan Seifert
fuente
Esta respuesta debe marcarse como correcta.
Lucas
Si está buscando una consulta en la base de datos, esta es la respuesta correcta
Alex Popov
Esta consulta redujo mi tiempo cuando estaba usando WP_Query de ~ 25 segundos a ~ 3 segundos. Mi requisito era disparar esto solo una vez, por lo que no se necesitaba almacenamiento en caché.
Kush
11

Me he encontrado con un caso en el que también quiero recuperar rápidamente muchas publicaciones con su metainformación asociada. Necesito recuperar las publicaciones de O (2000).

Lo probé usando la sugerencia de Otto: ejecutar WP_Query :: query para todas las publicaciones, y luego recorrer y ejecutar get_post_custom para cada publicación. Esto tomó, en promedio, unos 3 segundos en completarse .

Luego probé la consulta dinámica de Ethan (aunque no me gustaba tener que pedir manualmente cada meta_key que me interesaba). Todavía tenía que recorrer todas las publicaciones recuperadas para deserializar el meta_valor. Esto tomó, en promedio, aproximadamente 1.3 segundos en completarse .

Luego intenté usar la función GROUP_CONCAT y encontré el mejor resultado. Aquí está el código:

global $wpdb;
$wpdb->query('SET SESSION group_concat_max_len = 10000'); // necessary to get more than 1024 characters in the GROUP_CONCAT columns below
$query = "
    SELECT p.*, 
    GROUP_CONCAT(pm.meta_key ORDER BY pm.meta_key DESC SEPARATOR '||') as meta_keys, 
    GROUP_CONCAT(pm.meta_value ORDER BY pm.meta_key DESC SEPARATOR '||') as meta_values 
    FROM $wpdb->posts p 
    LEFT JOIN $wpdb->postmeta pm on pm.post_id = p.ID 
    WHERE p.post_type = 'product' and p.post_status = 'publish' 
    GROUP BY p.ID
";

$products = $wpdb->get_results($query);

// massages the products to have a member ->meta with the unserialized values as expected
function massage($a){
    $a->meta = array_combine(explode('||',$a->meta_keys),array_map('maybe_unserialize',explode('||',$a->meta_values)));
    unset($a->meta_keys);
    unset($a->meta_values);
    return $a;
}

$products = array_map('massage',$products);

Esto tomó un promedio de 0.7 segundos . Eso es aproximadamente una cuarta parte del tiempo de la solución WP get_post_custom () y aproximadamente la mitad de la solución de consulta dinámica.

Tal vez esto sea de interés para alguien.

Trevor Mills
fuente
Me interesaría qué resultados obtenga con una solución de caché de objetos persistentes. El caché de objetos a veces será más lento para el caso base, dependiendo de su base de datos y configuración, pero los resultados del mundo real con la mayoría de los hosts darán resultados muy variados. El almacenamiento en caché basado en memoria es ridículamente rápido.
Otto
Hola @ Otto. Independientemente del método que use para obtener los datos, definitivamente quiero almacenar en caché el resultado. Intenté usar la API transitoria para hacerlo, pero estoy teniendo problemas de memoria. La cadena serializada para mis 2000 objetos registra a ~ 8M y set_transient () falla (memoria agotada). Además, debe cambiar la configuración max_allowed_packet MySQL. Examinaré el almacenamiento en caché para archivar, pero aún no estoy seguro del rendimiento allí. ¿Hay alguna forma de almacenar en caché en la memoria que persiste en todas las solicitudes?
Trevor Mills
Sí, si tiene un caché de memoria persistente (XCache, memcached, APC, etc.) y usa un complemento de almacenamiento en caché de objetos (W3 Total Cache admite muchos tipos de cachés de memoria), entonces almacena todo el caché de objetos en la memoria, proporcionándole un aceleración múltiple de casi todo.
Otto
Estoy devolviendo 6000 artículos para usar en un esquema de filtrado de backbone / subrayado js. Esto tomó una consulta personalizada de 6 segundos que ni siquiera pude ejecutar como WP_Query porque se agotó el tiempo de espera y la convirtió en una consulta de 2 segundos. Aunque el array_map retrasa de nuevo hacia abajo un poco ...
Jake
¿Existe algún soporte para crear soporte de alto rendimiento para devolver todos los metadatos dentro de una WP_Query?
atwellpub
2

Me encontré en una situación en la que necesitaba hacer esta tarea para finalmente crear un documento CSV, terminé trabajando directamente con mysql para hacer esto. Mi código se une a las tablas de publicación y meta para recuperar información de precios de woocommerce, la solución publicada anteriormente requería que usara alias de tabla en el sql para funcionar correctamente.

SELECT p.ID, p.post_title, 
    MAX(CASE WHEN pm1.meta_key = '_price' then pm1.meta_value ELSE NULL END) as price,
    MAX(CASE WHEN pm1.meta_key = '_regular_price' then pm1.meta_value ELSE NULL END) as regular_price,
    MAX(CASE WHEN pm1.meta_key = '_sale_price' then pm1.meta_value ELSE NULL END) as sale_price,
    MAX(CASE WHEN pm1.meta_key = '_sku' then pm1.meta_value ELSE NULL END) as sku
    FROM wp_posts p LEFT JOIN wp_postmeta pm1 ON ( pm1.post_id = p.ID)                 
    WHERE p.post_type in('product', 'product_variation') AND p.post_status = 'publish'
    GROUP BY p.ID, p.post_title

Sin embargo, tenga en cuenta que woocommerce creó más de 300K filas en mi metatabla, por lo que era muy grande y, por lo tanto, muy lento.

Terry Kernan
fuente
1

SIN VERSIÓN SQL:

Obtenga todas las publicaciones y todos sus meta valores (metas) sin SQL:

Digamos que tiene una lista de ID de publicaciones almacenadas como una matriz de ID, algo así como

$post_ids_list = [584, 21, 1, 4, ...];

Ahora no es posible obtener todas las publicaciones y todas las metas en 1 consulta sin usar al menos un poco de SQL, por lo que debemos hacer 2 consultas (todavía solo 2):

1. Obtenga todas las publicaciones (usando WP_Query )

$request = new WP Query([
  'post__in' => $post_ids_list,
  'ignore_sticky_posts' => true, //if you want to ignore the "stickiness"
]);

(No olvides llamar wp_reset_postdata();si estás haciendo un "ciclo" después;))

2. Actualizar meta cache

//don't be confused here: "post" means content type (post X user X ...), NOT post type ;)
update_meta_cache('post', $post_ids_list);

Para obtener los metadatos, simplemente use el estándar get_post_meta()que, como señaló @Otto:
primero busca en el caché :)

Nota: Si realmente no necesita otros datos de las publicaciones (como título, contenido, ...) , puede hacer solo 2. :-)

jave.web
fuente
0

utilizando la solución trevor y modificándola para que funcione con SQL anidado. Esto no está probado.

global $wpdb;
$query = "
    SELECT p.*, (select pm.* From $wpdb->postmeta AS pm WHERE pm.post_id = p.ID)
    FROM $wpdb->posts p 
    WHERE p.post_type = 'product' and p.post_status = 'publish' 
";
$products = $wpdb->get_results($query);
Jonathan Joosten
fuente
-1

También me encontré con el problema de los metacampos de valores múltiples. El problema es con WordPress en sí. Busque en wp-includes / meta.php. Busque esta línea:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value );

El problema es con la declaración CAST. En una consulta de meta valores, la variable $ meta_type se establece en CHAR. No conozco los detalles sobre cómo CASTing el valor de CHAR afecta la cadena serializada, pero para solucionarlo, puede eliminar la conversión para que el SQL se vea así:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "$alias.meta_value {$meta_compare} {$meta_compare_string})", $meta_value );

Ahora, a pesar de que eso funciona, estás jugando con las partes internas de WordPress, por lo que otras cosas podrían romperse, y no es una solución permanente, suponiendo que necesites actualizar WordPress.

La forma en que lo arreglé es copiar el SQL generado por WordPress para la meta consulta que quiero y luego escribir PHP para agregar declaraciones AND adicionales para los meta_valores que estoy buscando y usar $ wpdb-> get_results ($ sql ) para el resultado final. Hacky, pero funciona.

Harry Love
fuente
No lo he probado, pero aprovechar el get_meta_sqlfiltro que sigue esta línea, por supuesto, sería preferible a hackear el código central.
Steve Taylor el