Parte 1 - Uniones y Uniones
Esta respuesta cubre:
- Parte 1
- Parte 2
- Subconsultas: qué son, dónde se pueden usar y a qué vigilar
- Cartesian se une a AKA - ¡Oh, la miseria!
Hay varias formas de recuperar datos de varias tablas en una base de datos. En esta respuesta, usaré la sintaxis de unión ANSI-92. Esto puede ser diferente a una serie de otros tutoriales que usan la sintaxis ANSI-89 anterior (y si estás acostumbrado a 89, puede parecer mucho menos intuitivo, pero todo lo que puedo decir es que lo pruebes) ya que es mucho más fácil entender cuándo las consultas comienzan a ser más complejas. ¿Por qué usarlo? ¿Hay un aumento de rendimiento? La respuesta corta es no, pero es más fácil de leer una vez que te acostumbras. Es más fácil leer consultas escritas por otras personas que usan esta sintaxis.
También voy a utilizar el concepto de un pequeño caryard que tiene una base de datos para realizar un seguimiento de los automóviles que tiene disponibles. El propietario lo ha contratado como su chico de informática y espera que pueda soltarle los datos que solicita en un abrir y cerrar de ojos.
He hecho una serie de tablas de búsqueda que serán utilizadas por la tabla final. Esto nos dará un modelo razonable para trabajar. Para comenzar, ejecutaré mis consultas en una base de datos de ejemplo que tiene la siguiente estructura. Trataré de pensar en los errores comunes que se cometen al comenzar y explicaré qué sucede con ellos, así como, por supuesto, mostraré cómo corregirlos.
La primera tabla es simplemente una lista de colores para que sepamos qué colores tenemos en el patio del automóvil.
mysql> create table colors(id int(3) not null auto_increment primary key,
-> color varchar(15), paint varchar(10));
Query OK, 0 rows affected (0.01 sec)
mysql> show columns from colors;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(3) | NO | PRI | NULL | auto_increment |
| color | varchar(15) | YES | | NULL | |
| paint | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
mysql> insert into colors (color, paint) values ('Red', 'Metallic'),
-> ('Green', 'Gloss'), ('Blue', 'Metallic'),
-> ('White' 'Gloss'), ('Black' 'Gloss');
Query OK, 5 rows affected (0.00 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> select * from colors;
+----+-------+----------+
| id | color | paint |
+----+-------+----------+
| 1 | Red | Metallic |
| 2 | Green | Gloss |
| 3 | Blue | Metallic |
| 4 | White | Gloss |
| 5 | Black | Gloss |
+----+-------+----------+
5 rows in set (0.00 sec)
La tabla de marcas identifica las diferentes marcas de los automóviles que Caryard podría vender.
mysql> create table brands (id int(3) not null auto_increment primary key,
-> brand varchar(15));
Query OK, 0 rows affected (0.01 sec)
mysql> show columns from brands;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(3) | NO | PRI | NULL | auto_increment |
| brand | varchar(15) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)
mysql> insert into brands (brand) values ('Ford'), ('Toyota'),
-> ('Nissan'), ('Smart'), ('BMW');
Query OK, 5 rows affected (0.00 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> select * from brands;
+----+--------+
| id | brand |
+----+--------+
| 1 | Ford |
| 2 | Toyota |
| 3 | Nissan |
| 4 | Smart |
| 5 | BMW |
+----+--------+
5 rows in set (0.00 sec)
La tabla de modelos cubrirá diferentes tipos de automóviles, será más simple para esto usar diferentes tipos de automóviles en lugar de modelos de automóviles reales.
mysql> create table models (id int(3) not null auto_increment primary key,
-> model varchar(15));
Query OK, 0 rows affected (0.01 sec)
mysql> show columns from models;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(3) | NO | PRI | NULL | auto_increment |
| model | varchar(15) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
mysql> insert into models (model) values ('Sports'), ('Sedan'), ('4WD'), ('Luxury');
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> select * from models;
+----+--------+
| id | model |
+----+--------+
| 1 | Sports |
| 2 | Sedan |
| 3 | 4WD |
| 4 | Luxury |
+----+--------+
4 rows in set (0.00 sec)
Y finalmente, para unir todas estas otras tablas, la tabla que une todo. El campo ID es en realidad el número de lote único utilizado para identificar automóviles.
mysql> create table cars (id int(3) not null auto_increment primary key,
-> color int(3), brand int(3), model int(3));
Query OK, 0 rows affected (0.01 sec)
mysql> show columns from cars;
+-------+--------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------+------+-----+---------+----------------+
| id | int(3) | NO | PRI | NULL | auto_increment |
| color | int(3) | YES | | NULL | |
| brand | int(3) | YES | | NULL | |
| model | int(3) | YES | | NULL | |
+-------+--------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql> insert into cars (color, brand, model) values (1,2,1), (3,1,2), (5,3,1),
-> (4,4,2), (2,2,3), (3,5,4), (4,1,3), (2,2,1), (5,2,3), (4,5,1);
Query OK, 10 rows affected (0.00 sec)
Records: 10 Duplicates: 0 Warnings: 0
mysql> select * from cars;
+----+-------+-------+-------+
| id | color | brand | model |
+----+-------+-------+-------+
| 1 | 1 | 2 | 1 |
| 2 | 3 | 1 | 2 |
| 3 | 5 | 3 | 1 |
| 4 | 4 | 4 | 2 |
| 5 | 2 | 2 | 3 |
| 6 | 3 | 5 | 4 |
| 7 | 4 | 1 | 3 |
| 8 | 2 | 2 | 1 |
| 9 | 5 | 2 | 3 |
| 10 | 4 | 5 | 1 |
+----+-------+-------+-------+
10 rows in set (0.00 sec)
Esto nos dará suficientes datos (espero) para cubrir los siguientes ejemplos de diferentes tipos de combinaciones y también proporcionará suficientes datos para que valgan la pena.
Entonces, entrando en la arena, el jefe quiere saber las identificaciones de todos los autos deportivos que tiene .
Esta es una combinación simple de dos tablas. Tenemos una tabla que identifica el modelo y la tabla con el stock disponible. Como puede ver, los datos en la model
columna de la cars
tabla se relacionan con la models
columna de la cars
tabla que tenemos. Ahora, sabemos que la tabla de modelos tiene un ID de 1
for, Sports
así que vamos a escribir la unión.
select
ID,
model
from
cars
join models
on model=ID
Entonces esta consulta se ve bien, ¿verdad? Hemos identificado las dos tablas y contiene la información que necesitamos y utilizamos una combinación que identifica correctamente en qué columnas unirnos.
ERROR 1052 (23000): Column 'ID' in field list is ambiguous
Oh no! Un error en nuestra primera consulta! Sí, y es una ciruela. Verá, la consulta tiene las columnas correctas, pero algunas de ellas existen en ambas tablas, por lo que la base de datos se confunde acerca de qué columna real queremos decir y dónde. Hay dos soluciones para resolver esto. El primero es agradable y simple, podemos usar tableName.columnName
para decirle a la base de datos exactamente lo que queremos decir, así:
select
cars.ID,
models.model
from
cars
join models
on cars.model=models.ID
+----+--------+
| ID | model |
+----+--------+
| 1 | Sports |
| 3 | Sports |
| 8 | Sports |
| 10 | Sports |
| 2 | Sedan |
| 4 | Sedan |
| 5 | 4WD |
| 7 | 4WD |
| 9 | 4WD |
| 6 | Luxury |
+----+--------+
10 rows in set (0.00 sec)
El otro probablemente se usa con más frecuencia y se llama alias de tabla. Las tablas en este ejemplo tienen nombres simples y agradables, pero escribir algo así KPI_DAILY_SALES_BY_DEPARTMENT
probablemente envejecería rápidamente, por lo que una forma simple es ponerle un apodo a la tabla de esta manera:
select
a.ID,
b.model
from
cars a
join models b
on a.model=b.ID
Ahora, de vuelta a la solicitud. Como puede ver, tenemos la información que necesitamos, pero también tenemos información que no se solicitó, por lo que debemos incluir una cláusula where en la declaración para obtener solo los autos deportivos como se solicitó. Como prefiero el método de alias de tabla en lugar de usar los nombres de tabla una y otra vez, me atendré a él a partir de este momento.
Claramente, necesitamos agregar una cláusula where a nuestra consulta. Podemos identificar autos deportivos por ID=1
o model='Sports'
. A medida que la ID se indexa y la clave principal (y resulta que está escribiendo menos), usemos eso en nuestra consulta.
select
a.ID,
b.model
from
cars a
join models b
on a.model=b.ID
where
b.ID=1
+----+--------+
| ID | model |
+----+--------+
| 1 | Sports |
| 3 | Sports |
| 8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)
¡Bingo! El jefe esta feliz. Por supuesto, como jefe y nunca contento con lo que pidió, mira la información y luego dice que también quiero los colores .
Bien, tenemos una buena parte de nuestra consulta ya escrita, pero necesitamos usar una tercera tabla que es colores. Ahora, nuestra tabla de información principal cars
almacena la identificación del color del automóvil y esta vuelve a la columna de identificación del color. Entonces, de manera similar al original, podemos unirnos a una tercera tabla:
select
a.ID,
b.model
from
cars a
join models b
on a.model=b.ID
join colors c
on a.color=c.ID
where
b.ID=1
+----+--------+
| ID | model |
+----+--------+
| 1 | Sports |
| 3 | Sports |
| 8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)
Maldición, aunque la tabla se unió correctamente y las columnas relacionadas se vincularon, olvidamos extraer la información real de la nueva tabla que acabamos de vincular.
select
a.ID,
b.model,
c.color
from
cars a
join models b
on a.model=b.ID
join colors c
on a.color=c.ID
where
b.ID=1
+----+--------+-------+
| ID | model | color |
+----+--------+-------+
| 1 | Sports | Red |
| 8 | Sports | Green |
| 10 | Sports | White |
| 3 | Sports | Black |
+----+--------+-------+
4 rows in set (0.00 sec)
Correcto, ese es el jefe de nuestras espaldas por un momento. Ahora, para explicar algo de esto con un poco más de detalle. Como puede ver, la from
cláusula en nuestra declaración vincula nuestra tabla principal (a menudo uso una tabla que contiene información en lugar de una tabla de búsqueda o dimensión. La consulta funcionaría igual de bien con todas las tablas cambiadas, pero tiene menos sentido cuando volvemos a esta consulta para leerla dentro de unos meses, por lo que a menudo es mejor intentar escribir una consulta que sea agradable y fácil de entender: exponga intuitivamente, use una sangría agradable para que todo sea tan claro como sea posible. puede ser. Si continúa enseñando a otros, intente inculcar estas características en sus consultas, especialmente si va a solucionarlos.
Es completamente posible seguir vinculando más y más tablas de esta manera.
select
a.ID,
b.model,
c.color
from
cars a
join models b
on a.model=b.ID
join colors c
on a.color=c.ID
join brands d
on a.brand=d.ID
where
b.ID=1
Si bien olvidé incluir una tabla donde podríamos unir más de una columna en la join
declaración, aquí hay un ejemplo. Si la models
tabla tenía modelos específicos de la marca y, por lo tanto, también tenía una columna llamada brand
que se vinculaba de nuevo a la brands
tabla en el ID
campo, podría hacerse así:
select
a.ID,
b.model,
c.color
from
cars a
join models b
on a.model=b.ID
join colors c
on a.color=c.ID
join brands d
on a.brand=d.ID
and b.brand=d.ID
where
b.ID=1
Puede ver que la consulta anterior no solo vincula las tablas unidas a la cars
tabla principal , sino que también especifica las uniones entre las tablas ya unidas. Si esto no se hizo, el resultado se llama una unión cartesiana, que es hablar mal. Una combinación cartesiana es aquella en la que se devuelven filas porque la información no le dice a la base de datos cómo limitar los resultados, por lo que la consulta devuelve todas las filas que se ajustan a los criterios.
Entonces, para dar un ejemplo de una unión cartesiana, ejecutemos la siguiente consulta:
select
a.ID,
b.model
from
cars a
join models b
+----+--------+
| ID | model |
+----+--------+
| 1 | Sports |
| 1 | Sedan |
| 1 | 4WD |
| 1 | Luxury |
| 2 | Sports |
| 2 | Sedan |
| 2 | 4WD |
| 2 | Luxury |
| 3 | Sports |
| 3 | Sedan |
| 3 | 4WD |
| 3 | Luxury |
| 4 | Sports |
| 4 | Sedan |
| 4 | 4WD |
| 4 | Luxury |
| 5 | Sports |
| 5 | Sedan |
| 5 | 4WD |
| 5 | Luxury |
| 6 | Sports |
| 6 | Sedan |
| 6 | 4WD |
| 6 | Luxury |
| 7 | Sports |
| 7 | Sedan |
| 7 | 4WD |
| 7 | Luxury |
| 8 | Sports |
| 8 | Sedan |
| 8 | 4WD |
| 8 | Luxury |
| 9 | Sports |
| 9 | Sedan |
| 9 | 4WD |
| 9 | Luxury |
| 10 | Sports |
| 10 | Sedan |
| 10 | 4WD |
| 10 | Luxury |
+----+--------+
40 rows in set (0.00 sec)
Dios mío, eso es feo. Sin embargo, en lo que respecta a la base de datos, es exactamente lo que se solicitó. En la consulta, solicitamos el ID
de cars
y el model
de models
. Sin embargo, debido a que no especificamos cómo unir las tablas, la base de datos ha coincidido con cada fila de la primera tabla con cada fila de la segunda tabla.
Bien, entonces el jefe está de regreso y quiere más información nuevamente. Quiero la misma lista, pero también incluyo 4WD en ella .
Sin embargo, esto nos da una gran excusa para ver dos formas diferentes de lograr esto. Podríamos agregar otra condición a la cláusula where como esta:
select
a.ID,
b.model,
c.color
from
cars a
join models b
on a.model=b.ID
join colors c
on a.color=c.ID
join brands d
on a.brand=d.ID
where
b.ID=1
or b.ID=3
Si bien lo anterior funcionará perfectamente bien, veámoslo de manera diferente, esta es una gran excusa para mostrar cómo funcionará una union
consulta.
Sabemos que lo siguiente devolverá todos los autos deportivos:
select
a.ID,
b.model,
c.color
from
cars a
join models b
on a.model=b.ID
join colors c
on a.color=c.ID
join brands d
on a.brand=d.ID
where
b.ID=1
Y lo siguiente devolvería todos los 4WD:
select
a.ID,
b.model,
c.color
from
cars a
join models b
on a.model=b.ID
join colors c
on a.color=c.ID
join brands d
on a.brand=d.ID
where
b.ID=3
Entonces, al agregar una union all
cláusula entre ellos, los resultados de la segunda consulta se agregarán a los resultados de la primera consulta.
select
a.ID,
b.model,
c.color
from
cars a
join models b
on a.model=b.ID
join colors c
on a.color=c.ID
join brands d
on a.brand=d.ID
where
b.ID=1
union all
select
a.ID,
b.model,
c.color
from
cars a
join models b
on a.model=b.ID
join colors c
on a.color=c.ID
join brands d
on a.brand=d.ID
where
b.ID=3
+----+--------+-------+
| ID | model | color |
+----+--------+-------+
| 1 | Sports | Red |
| 8 | Sports | Green |
| 10 | Sports | White |
| 3 | Sports | Black |
| 5 | 4WD | Green |
| 7 | 4WD | White |
| 9 | 4WD | Black |
+----+--------+-------+
7 rows in set (0.00 sec)
Como puede ver, los resultados de la primera consulta se devuelven primero, seguidos de los resultados de la segunda consulta.
En este ejemplo, por supuesto, habría sido mucho más fácil simplemente usar la primera consulta, pero las union
consultas pueden ser excelentes para casos específicos. Ellos son una gran manera para obtener resultados específicos de las tablas de las tablas que no están unidos entre sí con facilidad - o para el caso por completo tablas no relacionadas. Sin embargo, hay algunas reglas a seguir.
- Los tipos de columna de la primera consulta deben coincidir con los tipos de columna de cualquier otra consulta a continuación.
- Los nombres de las columnas de la primera consulta se utilizarán para identificar el conjunto completo de resultados.
- El número de columnas en cada consulta debe ser el mismo.
Ahora, puede que se pregunte cuál es la diferencia entre usar union
y union all
. Una union
consulta eliminará duplicados, mientras que una union all
no lo hará. Esto significa que hay un pequeño impacto en el rendimiento cuando se usa union
más, union all
pero los resultados pueden valer la pena; sin embargo, no especularé sobre ese tipo de cosas en esto.
En esta nota, podría valer la pena señalar algunas notas adicionales aquí.
- Si quisiéramos ordenar los resultados, podemos usar un
order by
pero ya no puede usar el alias. En la consulta anterior, agregar un order by a.ID
daría como resultado un error, en lo que respecta a los resultados, la columna se llama en ID
lugar de hacerlo a.ID
, a pesar de que se ha utilizado el mismo alias en ambas consultas.
- Solo podemos tener una
order by
declaración, y debe ser como la última declaración.
Para los siguientes ejemplos, agregaré algunas filas adicionales a nuestras tablas.
He agregado Holden
a la tabla de marcas. También he agregado una fila cars
que tiene el color
valor de 12
, que no tiene referencia en la tabla de colores.
De acuerdo, el jefe está de regreso otra vez, ladrando solicitudes - * ¡Quiero un recuento de cada marca que tenemos y la cantidad de autos que contiene! `- Típico, solo llegamos a una sección interesante de nuestra discusión y el jefe quiere más trabajo .
Rightyo, así que lo primero que debemos hacer es obtener una lista completa de posibles marcas.
select
a.brand
from
brands a
+--------+
| brand |
+--------+
| Ford |
| Toyota |
| Nissan |
| Smart |
| BMW |
| Holden |
+--------+
6 rows in set (0.00 sec)
Ahora, cuando unimos esto a nuestra mesa de autos, obtenemos el siguiente resultado:
select
a.brand
from
brands a
join cars b
on a.ID=b.brand
group by
a.brand
+--------+
| brand |
+--------+
| BMW |
| Ford |
| Nissan |
| Smart |
| Toyota |
+--------+
5 rows in set (0.00 sec)
Lo cual, por supuesto, es un problema: no estamos viendo ninguna mención de la hermosa Holden
marca que agregué.
Esto se debe a que una unión busca filas coincidentes en ambas tablas. Como no hay datos en los automóviles que sean de tipo Holden
, no se devuelven. Aquí es donde podemos usar una outer
unión. Esto devolverá todos los resultados de una tabla, ya sea que coincidan en la otra tabla o no:
select
a.brand
from
brands a
left outer join cars b
on a.ID=b.brand
group by
a.brand
+--------+
| brand |
+--------+
| BMW |
| Ford |
| Holden |
| Nissan |
| Smart |
| Toyota |
+--------+
6 rows in set (0.00 sec)
Ahora que tenemos eso, podemos agregar una función agregada encantadora para obtener un recuento y sacar al jefe de nuestras espaldas por un momento.
select
a.brand,
count(b.id) as countOfBrand
from
brands a
left outer join cars b
on a.ID=b.brand
group by
a.brand
+--------+--------------+
| brand | countOfBrand |
+--------+--------------+
| BMW | 2 |
| Ford | 2 |
| Holden | 0 |
| Nissan | 1 |
| Smart | 1 |
| Toyota | 5 |
+--------+--------------+
6 rows in set (0.00 sec)
Y con eso, el jefe se esconde.
Ahora, para explicar esto con más detalle, las uniones externas pueden ser del tipo left
o right
. La izquierda o la derecha define qué tabla está completamente incluida. A left outer join
incluirá todas las filas de la tabla de la izquierda, mientras que (lo adivinó) a right outer join
trae todos los resultados de la tabla de la derecha a los resultados.
Algunas bases de datos permitirán una full outer join
que traerá resultados (ya sea coincidentes o no) de ambas tablas, pero esto no es compatible con todas las bases de datos.
Ahora, probablemente supongo que en este momento, se está preguntando si puede fusionar o no los tipos de combinación en una consulta, y la respuesta es sí, absolutamente puede.
select
b.brand,
c.color,
count(a.id) as countOfBrand
from
cars a
right outer join brands b
on b.ID=a.brand
join colors c
on a.color=c.ID
group by
a.brand,
c.color
+--------+-------+--------------+
| brand | color | countOfBrand |
+--------+-------+--------------+
| Ford | Blue | 1 |
| Ford | White | 1 |
| Toyota | Black | 1 |
| Toyota | Green | 2 |
| Toyota | Red | 1 |
| Nissan | Black | 1 |
| Smart | White | 1 |
| BMW | Blue | 1 |
| BMW | White | 1 |
+--------+-------+--------------+
9 rows in set (0.00 sec)
Entonces, ¿por qué no son los resultados que se esperaban? Esto se debe a que, aunque hemos seleccionado la combinación externa de automóviles a marcas, no se especificó en la combinación de colores, por lo que esa combinación en particular solo devolverá resultados que coincidan en ambas tablas.
Aquí está la consulta que funcionaría para obtener los resultados que esperábamos:
select
a.brand,
c.color,
count(b.id) as countOfBrand
from
brands a
left outer join cars b
on a.ID=b.brand
left outer join colors c
on b.color=c.ID
group by
a.brand,
c.color
+--------+-------+--------------+
| brand | color | countOfBrand |
+--------+-------+--------------+
| BMW | Blue | 1 |
| BMW | White | 1 |
| Ford | Blue | 1 |
| Ford | White | 1 |
| Holden | NULL | 0 |
| Nissan | Black | 1 |
| Smart | White | 1 |
| Toyota | NULL | 1 |
| Toyota | Black | 1 |
| Toyota | Green | 2 |
| Toyota | Red | 1 |
+--------+-------+--------------+
11 rows in set (0.00 sec)
Como podemos ver, tenemos dos combinaciones externas en la consulta y los resultados se obtienen como se esperaba.
Ahora, ¿qué tal esos otros tipos de combinaciones que preguntas? ¿Qué pasa con las intersecciones?
Bueno, no todas las bases de datos son compatibles, intersection
pero prácticamente todas las bases de datos le permitirán crear una intersección a través de una unión (o una declaración where bien estructurada como mínimo).
Una intersección es un tipo de unión algo similar a la union
descrita anteriormente, pero la diferencia es que solo devuelve filas de datos que son idénticos (y quiero decir idénticos) entre las diversas consultas individuales unidas por la unión. Solo se devolverán las filas que sean idénticas en todos los aspectos.
Un ejemplo simple sería como tal:
select
*
from
colors
where
ID>2
intersect
select
*
from
colors
where
id<4
Mientras que una union
consulta normal devolvería todas las filas de la tabla (la primera consulta devolvería cualquier cosa ID>2
y la segunda cualquier cosa que tuviera ID<4
) lo que daría como resultado un conjunto completo, una consulta de intersección solo devolvería la coincidencia de fila id=3
ya que cumple con ambos criterios.
Ahora, si su base de datos no admite una intersect
consulta, lo anterior se puede cumplir fácilmente con la siguiente consulta:
select
a.ID,
a.color,
a.paint
from
colors a
join colors b
on a.ID=b.ID
where
a.ID>2
and b.ID<4
+----+-------+----------+
| ID | color | paint |
+----+-------+----------+
| 3 | Blue | Metallic |
+----+-------+----------+
1 row in set (0.00 sec)
Si desea realizar una intersección en dos tablas diferentes utilizando una base de datos que no admite inherentemente una consulta de intersección, deberá crear una unión en cada columna de las tablas.