Parte 1 - Associações e Sindicatos
Esta resposta abrange:
- Parte 1
- Parte 2
- Subconsultas - o que são, onde podem ser usadas e o que observar
- Cartesian junta-se à AKA - Oh, a miséria!
Existem várias maneiras de recuperar dados de várias tabelas em um banco de dados. Nesta resposta, usarei a sintaxe de junção ANSI-92. Isso pode ser diferente de vários outros tutoriais por aí que usam a sintaxe ANSI-89 mais antiga (e se você está acostumado a 89, pode parecer muito menos intuitivo - mas tudo o que posso dizer é experimentá-lo), pois é muito mais fácil para entender quando as consultas começam a ficar mais complexas. Por que usar isso? Existe um ganho de desempenho? A resposta curta é não, mas é mais fácil ler quando você se acostumar. É mais fácil ler consultas escritas por outras pessoas usando esta sintaxe.
Também vou usar o conceito de um pequeno estacionamento que possui um banco de dados para acompanhar quais carros ele tem disponível. O proprietário contratou você como o responsável pelo computador de TI e espera que você possa enviar a ele os dados que ele solicita imediatamente.
Eu criei várias tabelas de pesquisa que serão usadas pela mesa final. Isso nos dará um modelo razoável para trabalhar. Para começar, executarei minhas consultas em um banco de dados de exemplo que possui a seguinte estrutura. Tentarei pensar nos erros comuns cometidos ao iniciar e explicarei o que há de errado com eles - bem como, é claro, mostrar como corrigi-los.
A primeira tabela é simplesmente uma lista de cores para que saibamos que cores temos no pátio de carros.
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)
A tabela de marcas identifica as diferentes marcas dos carros que o quintal poderia 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)
A tabela de modelos cobrirá diferentes tipos de carros; será mais simples usar diferentes tipos de carros do que modelos reais.
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)
E, finalmente, amarrar todas essas outras mesas, a mesa que une tudo. O campo ID é, na verdade, o número de lote exclusivo usado para identificar carros.
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)
Isso nos fornecerá dados suficientes (espero) para encobrir os exemplos abaixo de diferentes tipos de junções e também dados suficientes para fazê-los valer a pena.
Então, entrando no clima, o chefe quer saber as identificações de todos os carros esportivos que ele tem .
Esta é uma junção simples de duas tabelas. Temos uma tabela que identifica o modelo e a tabela com o estoque disponível. Como você pode ver, os dados na model
coluna da cars
tabela estão relacionados à models
coluna da cars
tabela que temos. Agora, sabemos que a tabela de modelos tem um ID 1
para, Sports
então vamos escrever a junção.
select
ID,
model
from
cars
join models
on model=ID
Então, essa consulta parece boa, certo? Identificamos as duas tabelas e contêm as informações necessárias e usamos uma junção que identifica corretamente em quais colunas participar.
ERROR 1052 (23000): Column 'ID' in field list is ambiguous
Oh não! Um erro na nossa primeira consulta! Sim, e é uma ameixa. Veja bem, a consulta realmente possui as colunas corretas, mas algumas delas existem nas duas tabelas; portanto, o banco de dados fica confuso sobre qual coluna real queremos dizer e onde. Existem duas soluções para resolver isso. O primeiro é agradável e simples, podemos tableName.columnName
dizer ao banco de dados exatamente o que queremos dizer, assim:
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)
O outro é provavelmente usado com mais frequência e é chamado de alias de tabela. As tabelas neste exemplo têm nomes simples curtos e agradáveis, mas digitar algo como KPI_DAILY_SALES_BY_DEPARTMENT
provavelmente envelheceria rapidamente, portanto, uma maneira simples é apelidar a tabela assim:
select
a.ID,
b.model
from
cars a
join models b
on a.model=b.ID
Agora, de volta à solicitação. Como você pode ver, temos as informações de que precisamos, mas também temos as que não foram solicitadas, por isso precisamos incluir uma cláusula where na declaração para obter apenas os carros esportivos conforme solicitado. Como eu prefiro o método de alias da tabela em vez de usar os nomes das tabelas repetidamente, continuarei com isso a partir deste ponto.
Claramente, precisamos adicionar uma cláusula where à nossa consulta. Podemos identificar carros esportivos por ID=1
ou model='Sports'
. Como o ID é indexado e a chave primária (e acontece que é menos digitada), vamos usá-lo em nossa 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! O chefe está feliz. É claro que, sendo um chefe e nunca sendo feliz com o que pediu, ele olha as informações e depois diz que também quero as cores .
Ok, então já temos uma boa parte de nossa consulta escrita, mas precisamos usar uma terceira tabela, que é cores. Agora, nossa tabela de informações principal cars
armazena a identificação da cor do carro e isso é vinculado à coluna de identificação da cor. Assim, de maneira semelhante ao original, podemos entrar em uma terceira tabela:
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)
Porra, embora a tabela tenha sido unida corretamente e as colunas relacionadas tenham sido vinculadas, esquecemos de extrair as informações reais da nova tabela 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)
Certo, esse é o chefe de nossas costas por um momento. Agora, para explicar um pouco mais detalhadamente isso. Como você pode ver, a from
cláusula em nossa declaração vincula nossa tabela principal (eu geralmente uso uma tabela que contém informações em vez de uma tabela de pesquisa ou dimensão. A consulta funcionaria da mesma forma que as tabelas alternadas, mas faz menos sentido quando voltamos a esta consulta para lê-la dentro de alguns meses, por isso é melhor tentar escrever uma consulta que seja agradável e fácil de entender - faça um layout intuitivo, use um recuo agradável para que tudo fique tão claro quanto possível. Se você continuar ensinando outras pessoas, tente instilar essas características em suas consultas - especialmente se estiver solucionando problemas.
É inteiramente possível continuar vinculando cada vez mais tabelas dessa maneira.
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
Embora eu tenha esquecido de incluir uma tabela na qual possamos juntar mais de uma coluna na join
declaração, aqui está um exemplo. Se a models
tabela tivesse modelos específicos de marca e, portanto, também tivesse uma coluna chamada brand
que vinculava de volta à brands
tabela no ID
campo, isso poderia ser feito da seguinte maneira:
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
Você pode ver, a consulta acima não apenas vincula as tabelas unidas à cars
tabela principal , mas também especifica as junções entre as tabelas já unidas. Se isso não foi feito, o resultado é chamado de junção cartesiana - que é dba speak for bad. Uma junção cartesiana é aquela em que as linhas são retornadas porque as informações não informam ao banco de dados como limitar os resultados; portanto, a consulta retorna todas as linhas que atendem aos critérios.
Portanto, para dar um exemplo de junção cartesiana, vamos executar a seguinte 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)
Bom Deus, isso é feio. No entanto, no que diz respeito ao banco de dados, é exatamente o que foi solicitado. Na consulta, solicitamos o ID
from cars
e o model
from models
. No entanto, como não especificamos como ingressar nas tabelas, o banco de dados corresponde a todas as linhas da primeira tabela com todas as linhas da segunda tabela.
Ok, então o chefe está de volta e ele quer mais informações novamente. Eu quero a mesma lista, mas também inclui 4WDs nela .
Isso, no entanto, nos dá uma ótima desculpa para examinar duas maneiras diferentes de conseguir isso. Poderíamos adicionar outra condição à 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
Embora o descrito acima funcione perfeitamente bem, vamos ver de forma diferente, essa é uma ótima desculpa para mostrar como uma union
consulta funcionará.
Sabemos que o seguinte retornará todos os carros esportivos:
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
E o seguinte retornaria todos os 4WDs:
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
Portanto, adicionando uma union all
cláusula entre eles, os resultados da segunda consulta serão anexados aos resultados da primeira 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 você pode ver, os resultados da primeira consulta são retornados primeiro, seguidos pelos resultados da segunda consulta.
Neste exemplo, é claro que teria sido muito mais fácil simplesmente usar a primeira consulta, mas as union
consultas podem ser ótimas para casos específicos. Eles são uma ótima maneira de retornar resultados específicos de tabelas de tabelas que não são facilmente unidas - ou, por esse motivo, tabelas completamente não relacionadas. Existem algumas regras a seguir, no entanto.
- Os tipos de coluna da primeira consulta devem corresponder aos tipos de coluna de todas as outras consultas abaixo.
- Os nomes das colunas da primeira consulta serão usados para identificar todo o conjunto de resultados.
- O número de colunas em cada consulta deve ser o mesmo.
Agora, você pode estar se perguntando qual é a diferença entre usar union
e union all
. UMAunion
consulta removerá duplicatas, enquanto a union all
não. Isso significa que há um pequeno impacto no desempenho quando usado em union
excesso, union all
mas os resultados podem valer a pena - não vou especular sobre esse tipo de coisa.
Nesta nota, pode ser interessante notar algumas notas adicionais aqui.
- Se quisermos ordenar os resultados, podemos usar um,
order by
mas você não pode mais usar o alias. Na consulta acima, acrescentar um order by a.ID
resultaria em erro - no que diz respeito aos resultados, a coluna é chamadaID
vez de a.ID
- mesmo que o mesmo alias tenha sido usado nas duas consultas.
- Só podemos ter um
order by
declaração, e deve ser a última declaração.
Para os próximos exemplos, estou adicionando algumas linhas extras às nossas tabelas.
Eu adicionei Holden
à tabela de marcas. Eu também adicionei uma linha cars
que tem o color
valor de12
- que não tem referência na tabela de cores.
Ok, o chefe está de volta, gritando pedidos - * Quero uma contagem de cada marca que carregamos e o número de carros nela! `- Típico, chegamos a uma seção interessante de nossa discussão e o chefe quer mais trabalho .
Rightyo, então a primeira coisa que precisamos fazer é obter uma lista completa de possíveis marcas.
select
a.brand
from
brands a
+--------+
| brand |
+--------+
| Ford |
| Toyota |
| Nissan |
| Smart |
| BMW |
| Holden |
+--------+
6 rows in set (0.00 sec)
Agora, quando juntamos isso à nossa tabela de carros, obtemos o seguinte 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)
O que é obviamente um problema - não estamos vendo nenhuma menção à adorável Holden
marca que adicionei.
Isso ocorre porque uma junção procura por linhas correspondentes nas duas tabelas. Como não há dados nos carros desse tipo, Holden
eles não são retornados. É aqui que podemos usar uma outer
associação. Isso retornará todos os resultados de uma tabela, independentemente de serem correspondidos na outra tabela ou não:
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)
Agora que temos isso, podemos adicionar uma função agregada adorável para obter uma contagem e tirar o chefe de nossas costas por um 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)
E com isso, o chefe foge.
Agora, para explicar isso com mais detalhes, as junções externas podem ser do tipo left
ou right
. A esquerda ou direita define qual tabela está totalmente incluída. A left outer join
incluirá todas as linhas da tabela à esquerda, enquanto (você adivinhou) umaright outer join
traz todos os resultados da tabela à direita para os resultados.
Alguns bancos de dados permitem um full outer join
que traga de volta resultados (correspondidos ou não) de ambos tabelas, mas isso não é suportado em todos os bancos de dados.
Agora, provavelmente eu acho que, neste momento, você está se perguntando se pode ou não mesclar tipos de junção em uma consulta - e a resposta é sim, você pode absolutamente.
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)
Então, por que esse não é o resultado esperado? Isso porque, embora tenhamos selecionado a junção externa de carros para marcas, ela não foi especificada na junção de cores - portanto, essa junção específica trará apenas resultados correspondentes às duas tabelas.
Aqui está a consulta que funcionaria para obter os resultados que esperávamos:
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, temos duas junções externas na consulta e os resultados estão chegando conforme o esperado.
Agora, e os outros tipos de junções que você pergunta? E as interseções?
Bem, nem todos os bancos de dados suportam o intersection
mas praticamente todos os bancos de dados permitem criar uma interseção por meio de uma junção (ou uma instrução where bem estruturada, no mínimo).
Uma interseção é um tipo de junção um pouco semelhante ao union
descrito acima - mas a diferença é que apenas retorna linhas de dados que são idênticas (e eu quero dizer idênticas) entre as várias consultas individuais unidas pela união. Somente linhas idênticas em todos os aspectos serão retornadas.
Um exemplo simples seria o seguinte:
select
*
from
colors
where
ID>2
intersect
select
*
from
colors
where
id<4
Enquanto uma union
consulta normal retornaria todas as linhas da tabela (a primeira consulta retornando qualquer coisa ID>2
e a segunda qualquer coisa que tivesse ID<4
) que resultaria em um conjunto completo, uma consulta de interseção retornaria apenas a correspondência de linha id=3
, pois atendia aos dois critérios.
Agora, se o seu banco de dados não suportar uma intersect
consulta, o acima pode ser facilmente realizado com a seguinte 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)
Se você deseja executar uma interseção entre duas tabelas diferentes usando um banco de dados que não suporta inerentemente uma consulta de interseção, será necessário criar uma junção em todas as colunas das tabelas.