La forma más eficiente de obtener publicaciones con postmeta

32

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 que funciona. Se dispara si usas cualquiera de esos campos de metadatos de una manera que permita múltiples valores de metadatos para ellos en la misma publicación. No puedo pensar en una unión para hacer eso.

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

Pero la pregunta 2: ¿Vale la pena? ¿Cuántas combinaciones de tablas postmeta debo agregar antes de que sea preferible un enfoque de 2 consultas? Podría capturar todos los datos de publicación en una consulta, luego capturar toda la publicación relevante en otra y combinar el metadatos con la información de publicación en un conjunto de resultados en PHP. ¿Acaso eso terminaría siendo más rápido que una sola consulta SQL cada vez más compleja, si es posible?

Siempre pienso: "Dar tanto trabajo como sea posible a la base de datos". No estoy seguro de esto!

    
pregunta Steve Taylor 10.01.2012 - 23:18

7 respuestas

55

La metainformación de la publicación se almacena automáticamente en la memoria caché para un WP_Query estándar (y la consulta principal), a menos que específicamente le digas que no lo haga usando el parámetro update_post_meta_cache .

Por lo tanto, no debes escribir tus propias consultas para esto.

Cómo funciona el almacenamiento en caché meta para consultas normales:

Si el parámetro update_post_meta_cache al WP_Query no se establece en falso, luego de que las publicaciones se recuperan del DB, se llamará a la función update_post_caches() , que a su vez llama a update_postmeta_cache() .

La función update_postmeta_cache() es una envoltura para update_meta_cache() , y esencialmente llama a un simple SELECT con todas las 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 se comprueba el caché del objeto. Por lo tanto, no es necesario realizar consultas adicionales para obtener el meta de publicación en este punto. Si has recibido la publicación en un WP_Query , entonces el meta ya está en la memoria y se obtiene directamente desde allí.

Las ventajas aquí son muchas veces mayores que hacer una consulta compleja, pero la mayor ventaja proviene del uso del caché de objetos. Si utiliza una solución de almacenamiento en caché de memoria 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 la memoria rápida ya. En cuyo caso, hay consultas cero necesarias para recuperar sus datos; ya está en la memoria El almacenamiento en caché de objetos persistentes es impresionante en muchos aspectos.

En otras palabras, su consulta probablemente se carga y se carga más lentamente que utilizando una consulta adecuada y una solución de memoria persistente simple. Utilice el WP_Query normal. Ahórrese un poco de esfuerzo.

Adicional: update_meta_cache() es inteligente, por cierto. No recuperará la metainformación para las publicaciones que ya tienen su metainformación en caché. No se consigue el mismo meta dos veces, básicamente. Super eficiente.

Adicional adicional: "Dale todo el trabajo posible a la base de datos." ... No, esta es la web. Se aplican diferentes reglas. En general, siempre se debe dar a la base de datos, en la medida de lo posible, lo menos posible , si es posible. Las bases de datos son lentas o están mal configuradas (si no las configuró específicamente, puede apostar buen dinero si esto es cierto). A menudo, se comparten entre muchos sitios y se sobrecargan hasta cierto punto. Normalmente tienes 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 realizar la clasificación utilizando el código del lado del servidor web. Como principio general, por supuesto, todos los casos son diferentes.

    
respondido por el Otto 24.01.2012 - 06:56
26

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
    
respondido por el Ethan Seifert 24.01.2012 - 06:21
8

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

Lo intenté 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 tardó, en promedio, alrededor de 3 segundos en completarse .

Luego intenté la consulta dinámica de Ethan (aunque no me gustaba tener que preguntar manualmente por cada meta_key que me interesaba). Todavía tenía que recorrer todas las publicaciones recuperadas para deserializar el meta_valor. Esto tardó, 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ó en promedio 0,7 segundos . Eso es aproximadamente un cuarto 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.

    
respondido por el Trevor Mills 07.10.2012 - 17:23
2

Me encontré en una situación en la que necesitaba hacer esta tarea para crear un documento CSV, terminé trabajando directamente con mysql para hacer esto. Mi código se une a las tablas de metadatos y publicación para recuperar la información de precios de Woocommerce, la solución publicada anteriormente requería que utilizara alias de tabla en el SQL para que funcionara 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, ten en cuenta que woocommerce creó más de 300K filas en mi tabla meta, por lo que era muy grande y, por lo tanto, muy lento.

    
respondido por el Terry Kernan 09.09.2015 - 15:19
0

usando la solución de formulario 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);
    
respondido por el Jonathan Joosten 24.09.2014 - 10:01
0

NO HAY VERSIÓN SQL:

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

Digamos que tienes 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 una consulta sin usar al menos un poco de SQL, por lo que debemos hacer 2 consultas (aún solo 2):

1. Obtenga todas las publicaciones (utilizando 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 a wp_reset_postdata(); si estás haciendo un "loop " afterwards;))

2. Actualizar meta caché

//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, use el estándar get_post_meta() que, como @Otto señaló:
mira la caché primero :)

Nota: Si realmente no necesitas otros datos de las publicaciones (como el título, el contenido, ...) puedes hacerlo solo 2. :-)

    
respondido por el jave.web 31.01.2017 - 13:27
-1

También me encontré con el problema de múltiples campos de metacampo de valor. 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 sentencia CAST. En una consulta de valores meta, la variable $ meta_type se establece en CHAR. No conozco los detalles sobre cómo CAST el valor de CHAR afecta a la cadena serializada, pero para corregirlo, puede eliminar la conversión para que el SQL tenga este aspecto:

$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 jodiendo con los elementos internos de WordPress, por lo que otras cosas podrían romperse, y no es una solución permanente, asumiendo que necesitarás actualizar WordPress.

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

    
respondido por el Harry Love 23.04.2012 - 21:25

Lea otras preguntas en las etiquetas