viernes, 8 de enero de 2010

Join con relaciones muchos a muchos

En la entrada anterior dejaba pendiente el tema de las relaciones muchos a muchos, aunque es probable que ya te hayas dado cuenta de como combinar tablas relacionadas de esta manera.

Este tipo de relaciones requiere una tabla intermedia (o join table) que nos permita asociar las parejas de registros. Las tablas izquierda y derecha se relacionan de uno a muchos con la join table. Por lo tanto tendremos que "unir" la tabla izquierda con la join table y ésta con la tabla derecha.

Etiquetar libros

Vamos a seguir con nuestro ejemplo y vamos a añadir una tabla tags a nuestro sistema para poder etiquetar cada libro con diversas palabras clave descriptoras. Por ejemplo, así:


CREATE TABLE `tags` (
  `id` int(11) NOT NULL auto_increment,
  `tag` varchar(200) default NULL,
  PRIMARY KEY  (`id`)
)

Y vamos a introducir algunos valores, para que la tabla quede así:


+----+-----------------------+
| id | tag                   |
+----+-----------------------+
|  1 | Novela                | 
|  2 | Lit. Castellana       | 
|  3 | Lit. Hispanoamericana | 
|  4 | Lit. Francesa         | 
|  5 | Poesia                | 
+----+-----------------------+
5 rows in set (0,00 sec)

Ahora creamos la join table:

create table books_tags (
books_id int(11) not null,
tags_id int(11) not null);

Y la poblamos para relacionar nuestros libros con sus tags correspondientes, nos quedaría algo así:

+----------+---------+
| books_id | tags_id |
+----------+---------+
|        1 |       1 | 
|        2 |       1 | 
|        3 |       1 | 
|        4 |       1 | 
|        1 |       2 | 
|        4 |       2 | 
|        2 |       3 | 
|        3 |       4 | 
+----------+---------+
8 rows in set (0,00 sec)

Ahora podemos empezar a trabajar combinando las tablas. En realidad es muy simple: definimos una cláusula JOIN con cada una de las tablas que queremos unir. Por ejemplo:

select * from books join books_tags join tags;

Esta query específicamente se puede abreviar usando ',' en vez de JOIN:

select * from books, books_tags, tags;

La petición anterior nos devolverá el producto cartesiano de las tres tablas (nada menos que 160 filas). Ya que las tablas están relacionadas podemos usar las sentencias ON sobre los campos de clave primaria y clave foránea:

select title, tag 
from books 
    join books_tags on books.id = books_tags.books_id 
    join tags on books_tags.tags_id = tags.id;

Es decir, unimos la tabla books con la books_tags cuando coinciden books.id y books_tags.books_id y ésta a su vez con la tabla tags, cuando coinciden books_tags.tags.id y tags.id.

El resultado (lo he restringido a los campos title y tag para que se vea más claro):

+---------------------+-----------------------+
| title               | tag                   |
+---------------------+-----------------------+
| El quijote          | Novela                | 
| El quijote          | Lit. Castellana       | 
| 100 a?os de soledad | Novela                | 
| 100 a?os de soledad | Lit. Hispanoamericana | 
| El Principito       | Novela                | 
| El Principito       | Lit. Francesa         | 
| Lazarillo de Tormes | Novela                | 
| Lazarillo de Tormes | Lit. Castellana       | 
+---------------------+-----------------------+
8 rows in set (0,00 sec)

¿Podríamos meter a los autores en esta petición? Vamos a verlo (añado también el campo author y una cláusula para ordenar los registros a fin de apreciar mejor los resultados):

select title, author, tag 
from books 
    join authors on books.author_id = authors.id 
    join books_tags on books.id = books_tags.books_id 
    join tags on books_tags.tags_id = tags.id 
order by title;

+---------------------+----------------+-----------------------+
| title               | author         | tag                   |
+---------------------+----------------+-----------------------+
| 100 a?os de soledad | Garc?a M?rquez | Lit. Hispanoamericana | 
| 100 a?os de soledad | Garc?a M?rquez | Novela                | 
| El Principito       | Saint_Exupery  | Novela                | 
| El Principito       | Saint_Exupery  | Lit. Francesa         | 
| El quijote          | Cervantes      | Novela                | 
| El quijote          | Cervantes      | Lit. Castellana       | 
+---------------------+----------------+-----------------------+
6 rows in set (0,00 sec)

Por supuesto, puedes usar los LEFT JOIN y RIGHT JOIN según tus necesidades, por ejemplo, para obtener el listado completo de libros y sus etiquetas:

select title, author, tag 
from books 
    left join authors on books.author_id = authors.id 
    join books_tags on books.id = books_tags.books_id 
    join tags on books_tags.tags_id = tags.id 
order by title;

En qué medida debes usar left o right join depende de si necesitas obtener todos los registros posibles de las tablas izquierda o derecha, o sólo aquellos que tienen datos en ambas tablas.

Ahora veamos cómo podemos buscar libros que correspondan a una etiqueta. Empezamos por la combinación de tablas y luego no tenemos más que indicar en WHERE qué etiquetas queremos seleccionar:

select title, author, tag 
from books 
    left join authors on books.author_id = authors.id 
    join books_tags on books.id = books_tags.books_id 
    join tags on books_tags.tags_id = tags.id 
where tags.tag = 'Novela'
order by title;

+---------------------+----------------+--------+
| title               | author         | tag    |
+---------------------+----------------+--------+
| 100 a?os de soledad | Garc?a M?rquez | Novela | 
| El Principito       | Saint_Exupery  | Novela | 
| El quijote          | Cervantes      | Novela | 
| Lazarillo de Tormes | NULL           | Novela | 
+---------------------+----------------+--------+
4 rows in set (0,00 sec)

También puede ser otra etiqueta, claro. Por ejemplo, qué libros tenemos de literatura castellana:

select title, author, tag 
from books 
    left join authors on books.author_id = authors.id 
    join books_tags on books.id = books_tags.books_id 
    join tags on books_tags.tags_id = tags.id 
where tags.tag = 'Lit. Castellana'
order by title;

Que nos dará este resultado:

+---------------------+-----------+-----------------+
| title               | author    | tag             |
+---------------------+-----------+-----------------+
| El quijote          | Cervantes | Lit. Castellana | 
| Lazarillo de Tormes | NULL      | Lit. Castellana | 
+---------------------+-----------+-----------------+
2 rows in set (0,00 sec)

Como puedes ver, aparte del pequeño lío que supone especificar las combinaciones de tablas a través de múltiples join el trabajo es bastante sencillo.

2 comentarios:

Obelich dijo...

Hola que tal muchas gracias por el tutorial me servido de mucha ayuda ^_^

Unknown dijo...

Muchas gracias por fin pude realizar la consulta para organizarla en cakephp.