SQL Join en el Loop de WordPress

El Loop de WordPress

El loop es un mecanismo que WordPress ofrece a los programadores para acceder, con relativa sencillez, secuencialmente, a los contenidos creados en el portal Web (posts, pages, etc.), almacenados en la tabla wp_posts de la base de datos.

Un diseñador de themes puede ver el loop como una colección de funciones php que puede utilizar para incrustar en sus plantillas html contenido procedente de la base de datos, abstrayéndose de cualquier complejidad relacionada con SQL, con el filtrado de los datos, con la seguridad,  etc.

Aquí se pueden encontrar detalles sobre su uso en este sentido: El loop en acción.

Un desarrollador de software puede ver el loop como un iterador, una sofisticada herramienta que facilita el recorrido de una secuencia de elementos para su tratamiento. Podemos comprobar que el loop está basado en la instanciación de uno o más objetos de la clase WP_ Query (puede haber loops secundarios). Esta clase ofrece a su vez una capa de abstracción al desarrollador sobre la elaboración de la consulta SQL, sobre las librerías PHP utilizadas para enviar dicha consulta al sistema gestor de base de datos, así como para el recorrido de los resultados.

El problema

WordPress es por defecto, en su configuración más básica, un sistema de blogging, con dos tipos o formas de publicación de contenidos, las entradas y las páginas. Estos son internamente elementos muy parecidos, no en vano almacenan su información en la misma tabla de la base de datos, denominada por defecto wp_posts. .

El loop está basado internamente en el recorrido de la tabla wp_posts

Supongamos que deseamos publicar contenidos más complejos o sofisticados que un simple artículo, contenidos como por ejemplo una convocatoria de empleo, un curso de formación o un producto de una tienda online. Estos contenidos tienen campos de información que nos gustaría mostrar, pero que además nos gustaría utilizar para realizar filtrados u ordenaciones basados en ellos.

Además de poder almacenar esta información, nos gustaría seguir utilizando el loop para obtenerla, recorrerla y plasmarla en las plantillas html de nuestro theme.

WordPress es en realidad una herramienta muy potente, con la cual podemos construir casi cualquier tipo de portal Web.

La afirmación anterior es cierta pero, cómo conseguirlo, como convertir un sistema que por defecto es un blog en una tienda online, en un catálogo de libros, en un portal de empleo …

Voy a plantear dos soluciones que explican entre otras cosas como debería estar almacenada la información adicional para ser utilizada en el loop.

En principio no voy a hacer distinciones, pero lo comentado sería válido para cualquier tipo de publicación (entradas, páginas o custom post types).

Solución Inicial: CAMPOS PERSONALIZADOS

Complejidad cuadrática O (n2).

Para enriquecer un post con datos adicionales (adicionales al propio contenido del post), la solución inicial es utilizar campos personalizados, una solución ya implementada en WordPress.

Computacionalmente la complejidad de esta solución es cuadrática, es decir, para obtener los datos asocidos a una lista de posts utilizamos un bucle para recorrerlos y, además, un bucle adicional por cada post para recorrer sus campos personalizados.

Es de destacar además que se está generando una sentencia SQL para extraer de la base de datos la lista de posts, y adicionalmente por cada post una nueva consulta SQL para extraer los campos personalizados.

Si tenemos N artículos, estamos realizando (N + 1) consultas a la base de datos.

Por otro lado, esta solución no es buena si queremos ordenar o filtrar el resultado basándonos en alguno de los campos personalizados. Sería necesario obtener todos los datos y posteriormente realizar el filtrado u ordenación por programación, cuando lo ideal sería utilizar la potencia de la base de datos y el lenguaje SQL para realizar estas tareas.

Si esa ordenación o filtrado además se basa en campos del tipo fecha, númericos con precisión, etc., el uso de los campos personalizados (almacenados siempre como valores de tipo string) se vuelven menos recomendables si cabe.

Mejor solución: JOIN CON TABLA ADICIONAL

Complejidad  Lineal O(n).

Con esta solución  realizaremos una única consulta a la base de datos.

Mi propuesta es utilizar una tabla auxiliar para el almacenamiento de la información adicional, en lugar de campos personalizados. Cada registro en la tabla auxiliar estaría asociado a un registro en la tabla wp_posts.

Esto nos pemitirá:

  1. Por un lado, tener campos asociados al post del tipo que nos interese: Fechas, decimales, booleanos, etc.
  2. Aumento de la eficiencia al extraer toda la información en una única sentencia SQL.
  3. Realizar filtrados y ordenaciones de la información directamente a través de SQL en la base de datos.

Lo más interesante de este artículo está en los puntos 2 y 3 anteriores, el mecanismo que proporciona WordPress para que el loop incorpore los datos de otra tabla, relacionada con la tabla wp_posts.

No voy a explicar como se han almacenado esos datos en la tabla auxliar, sino este artículo sería interminable. Vamos a suponer que existen y que cada registro en esa tabla tiene su correspondencia en la tabla wp_posts.

Para poner un ejemplo vamos a suponer que nuestro portal contiene un sistema de gestión de cursos. Voy a llamar por lo tanto «wp_cursos» a la tabla auxiliar (en la realidad, como se ve en el código de ejemplo, sustituiría «wp_» por el prefijo que hayamos especificado al instalar WordPress), la cual contendrá al menos, los siguientes campos:

  • idcurso: Clave externa que utilizaré para relacionar cada curso con una entrada en wp_posts.
  • fecha: fecha de comienzo del curso.
  • activo: Si el curso está activo de cara al público.

Para que el loop incorpore la tabla utilizaré el filtro «posts_clauses» de WordPress, de la siguiente forma:

add_filter( 'posts_clauses', 'Add_Filtro_Next_Cursos_to_WPQuery', 20, 1 );

La implmentación de la función que añade el filtro es la siguiente, teniendo en cuenta que el parámetro $pieces es un array asociativo que contiene los distintos fragmentos de la sentencia SQL:

function Add_Filtro_Next_Cursos_to_WPQuery ($pieces) {
   global $wpdb;
   $mTabla = $wpdb->prefix."cursos";
   $pieces['join'] .= "LEFT JOIN ".$mTabla." ON ".$wpdb->prefix."posts.ID = ".$mTabla.".idcurso";
   $pieces['where'] .= ' AND ('.$mTabla.'.fecha >= CURDATE()) AND ('.$mTabla.'.activo = 1)';
   $pieces['fields'] .= ', '.$mTabla.'.fecha';   
   $pieces['orderby'] = $mTabla.'.fecha ASC';
   return $pieces;
}

Con el filtro estamos indicando que la query obtenga todos los cursos activos que hay programados en un futuro.

Un ejemplo de implementación de loop secundario, podría ser la siguiente:

add_filter( 'posts_clauses', 'Add_Filtro_Next_Cursos_to_WPQuery', 20, 1 ); 
$the_query = new WP_Query( array('post_type' => 'ptcursos', 'posts_per_page' => '-1'));
if ($the_query->have_posts()) {
   global $post;
   $lista = array();
   $formatoFecha = get_option( 'date_format' ); 
   while ( $the_query->have_posts() ) : $the_query->the_post(); 
     $lista[] = '<li><a href="'.get_permalink($post->ID).'" title="'.$post->post_title.'">'.$post->post_title.'</a><br />Fecha: '.date_i18n($formatoFecha, strtotime($post->fecha)).'.</li>';
   endwhile;
   echo('<ul>'.implode('',$lista).'</ul>');
}
wp_reset_postdata();

Se puede observar como el ejemplo está pensado para ser utilizado con un «custom post type» al que internamente he denominado «ptcursos». Además, al crear el objeto $the_query estoy indicando que no quiero hacer paginación.

Sobre el autor

Pablo Blanco

Llevo 22 años dedicado al desarrollo de software de forma profesional. En la actualidad también centrado en la docencia, como profesor técnico de F.P., en ciclos formativos de informática.
Titulado en Ingeniería Tec. Informática y Diplomado en CC. Empresariales.

2 Comentarios

Deja un comentario
  • Hola Pablo, ¿es posible ejecutar esta query con filtros?

    SELECT a.ID,a.name,tp.name
    FROM
    (select p1.ID,wt1.name FROM wp_posts p1
    INNER JOIN wp_term_relationships AS wtr1 ON p1.ID = wtr1.object_id
    INNER JOIN wp_terms AS wt1 ON wtr1.term_taxonomy_id=wt1.term_id
    INNER JOIN wp_term_taxonomy AS wtt1 ON wtr1.term_taxonomy_id=wtt1.term_taxonomy_id AND wtt1.taxonomy=»category»
    WHERE p1.post_mime_type!=»image/jpeg» AND p1.post_type=»attachment»
    ORDER BY wt1.name ASC) a
    INNER JOIN
    (select p2.ID,wt2.name FROM wp_posts p2
    INNER JOIN wp_term_relationships AS wtr2 ON p2.ID = wtr2.object_id
    INNER JOIN wp_terms AS wt2 ON wtr2.term_taxonomy_id=wt2.term_id
    INNER JOIN wp_term_taxonomy AS wtt2 ON wtr2.term_taxonomy_id=wtt2.term_taxonomy_id AND wtt2.taxonomy=»post_tag»
    WHERE p2.post_mime_type!=»image/jpeg» AND p2.post_type=»attachment»
    ORDER BY wt2.name ASC
    )tp
    ON a.ID=tp.ID;

    Gracias de antemano

  • Hola Luis:

    Pon un ejemplo de lo que quieres hacer, en el que utilices esa consulta. Así, a simple vista y fuera de contexto, no acabo de verle la utilidad.

    ¡Saludo!
    Pablo

Escribe un comentario

 

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Puede usar estas etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Copyright © 2024. Pablo Blanco.