Parte 1 - Junções e Uniões
Esta resposta abrange:
- Parte 1
- Juntar duas ou mais tabelas usando uma junção interna (consulte a entrada da Wikipédia para informações adicionais)
- Como usar uma consulta de união
- Left and Right Outer Joins (esta resposta do stackOverflow é excelente para descrever tipos de junções)
- Consultas de interseção (e como reproduzi-las se seu banco de dados não as suportar) - esta é uma função do SQL-Server (ver informações ) e parte do razão pela qual escrevi tudo isso em primeiro lugar.
- Parte 2
- Subconsultas - o que são, onde podem ser usadas e o que observar
- Cartesian junta-se a AKA - Oh, que miséria!
Há 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 com 89, pode parecer muito menos intuitivo - mas tudo o que posso dizer é tentar) como é muito mais fácil de entender quando as consultas começam a ficar mais complexas. Por que usá-lo? Há ganho de desempenho? A resposta curta não, mas é mais fácil de ler quando você se acostumar com isso. É mais fácil ler consultas escritas por outras pessoas usando essa sintaxe.
Também vou usar o conceito de um pequeno estaleiro que tem um banco de dados para acompanhar quais carros ele tem disponível. O proprietário contratou você como seu cara de computador de TI e espera que você seja capaz de enviar a ele os dados que ele pede em um piscar de olhos.
Eu fiz 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. Vou tentar pensar em erros comuns que são cometidos no início e explicar o que há de errado com eles - além de mostrar como corrigi-los.
A primeira tabela é simplesmente uma lista de cores para que saibamos quais 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 de carros que o estaleiro 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 em vez de modelos de carros 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, para 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 dará dados suficientes (espero) para cobrir os exemplos abaixo de diferentes tipos de junções e também fornecer dados suficientes para fazê-los valer a pena.
Então, entrando no assunto, o chefe quer saber Os IDs 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 nela. Como você pode ver, os dados no
model
coluna dos cars
tabela está relacionada aos models
coluna dos cars
mesa temos. Agora, sabemos que a tabela de modelos tem um ID de 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 de que precisamos e usamos uma junção que identifica corretamente em quais colunas unir.
ERROR 1052 (23000): Column 'ID' in field list is ambiguous
Ah não! Um erro em nossa primeira consulta! Sim, e é uma ameixa. Você vê, a consulta realmente tem as colunas certas, mas algumas delas existem em ambas as tabelas, então o banco de dados fica confuso sobre qual coluna real queremos dizer e onde. Existem duas soluções para resolver isso. A primeira é legal e simples, podemos usar
tableName.columnName
para 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 mais usado e é chamado de alias de tabela. As tabelas neste exemplo têm nomes simples e curtos, mas digitando algo como
KPI_DAILY_SALES_BY_DEPARTMENT
provavelmente envelheceria rapidamente, então 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 ao pedido. Como você pode ver, temos as informações que precisamos, mas também temos informações que não foram solicitadas, então precisamos incluir uma cláusula where na declaração para obter apenas os carros esportivos conforme solicitado. Como prefiro o método de alias de tabela em vez de usar os nomes das tabelas repetidamente, vou me ater a ele 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 por acaso é menos digitação), vamos usar isso 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, sendo um chefe e nunca estando satisfeito com o que ele pediu, ele olha as informações, então diz eu quero as cores também .
Ok, então temos uma boa parte da nossa query já escrita, mas precisamos usar uma terceira tabela que é cores. Agora, nossa principal tabela de informações
cars
armazena o ID da cor do carro e isso é vinculado à coluna ID das cores. Assim, de forma semelhante ao original, podemos juntar 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 nas nossas costas por um momento. Agora, para explicar um pouco disso com um pouco mais de detalhes. Como você pode ver, o
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 tão bem com as tabelas todas alternadas, mas faz menos sentido quando voltamos a essa consulta para lê-lo em alguns meses, por isso é melhor tentar escrever uma consulta que seja agradável e fácil de entender - faça-a intuitivamente, use um recuo agradável para que tudo fique o mais claro possível. continue ensinando os outros, tente incutir essas características em suas consultas - especialmente se você estiver solucionando problemas. É perfeitamente possível continuar vinculando mais e 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 onde podemos querer juntar mais de uma coluna no
join
declaração, aqui está um exemplo. Se os models
tabela tinha modelos específicos da marca e, portanto, também tinha uma coluna chamada brand
que vinculou de volta às brands
tabela no ID
campo, poderia ser feito assim: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 aos principais
cars
tabela, mas também especifica junções entre as tabelas já unidas. Se isso não foi feito, o resultado é chamado de junção cartesiana - que é dba falar por mal. 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, então a consulta retorna todos as linhas que atendem aos critérios. Então, para dar um exemplo de uma 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)
Meu Deus, isso é feio. No entanto, no que diz respeito ao banco de dados, é exatamente o que foi pedido. Na consulta, solicitamos o
ID
de cars
e o model
de models
. No entanto, como não especificamos como para unir as tabelas, o banco de dados correspondeu a todas linha da primeira tabela com todos linha da segunda tabela. Ok, então o chefe está de volta, e ele quer mais informações novamente. Quero a mesma lista, mas também incluir 4WDs nela .
Isso, no entanto, nos dá uma ótima desculpa para olhar para duas maneiras diferentes de fazer 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 acima funcione perfeitamente bem, vamos olhar de forma diferente, esta é uma ótima desculpa para mostrar como uma
union
consulta funcionará. Sabemos que o seguinte devolverá 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
Então, adicionando um
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 seria muito mais fácil simplesmente usar a primeira consulta, mas
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 completamente tabelas não relacionadas. No entanto, existem algumas regras a seguir. - 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 o que o a diferença é entre usar
union
e union all
. Uma union
consulta removerá duplicatas, enquanto um union all
não vou. Isso significa que há um pequeno impacto no desempenho ao usar union
sobre union all
mas os resultados podem valer a pena - não vou especular sobre esse tipo de coisa aqui. Nesta nota, pode valer a pena 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, anexando umorder by a.ID
resultaria em um erro - no que diz respeito aos resultados, a coluna é chamadaID
em vez dea.ID
- mesmo que o mesmo alias tenha sido usado em ambas as 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. Também adicionei uma linha em cars
que tem a color
valor de 12
- que não tem referência na tabela de cores. Ok, o chefe está de volta novamente, latindo pedidos - *Eu quero uma contagem de cada marca que carregamos e o número de carros nela!` - Típico, acabamos de chegar a uma seção interessante de nossa discussão e o chefe quer mais trabalho .
Certo, 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 ao adorável
Holden
marca que adicionei. Isso ocorre porque uma junção procura linhas correspondentes em ambos mesas. Como não há dados em carros do tipo
Holden
não é devolvido. É aqui que podemos usar um outer
Junte-se. Isso retornará todos os resultados de uma tabela, sejam eles 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 adorável função de agregação 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 se esconde.
Agora, para explicar isso com mais detalhes, as junções externas podem ser do lado
left
ou right
tipo. A Esquerda ou Direita define qual tabela está totalmente incluído. Uma left outer join
incluirá todas as linhas da tabela à esquerda, enquanto (você adivinhou) uma right outer join
traz todos os resultados da tabela à direita para os resultados. Alguns bancos de dados permitirão uma
full outer join
que trará de volta resultados (com ou sem correspondência) de ambos tabelas, mas isso não é suportado em todos os bancos de dados. Agora, eu provavelmente 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.
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 não são esses os resultados esperados? É porque, embora tenhamos selecionado a junção externa de carros para marcas, ela não foi especificada na junção para cores - portanto, essa junção específica só trará de volta resultados que correspondam em ambas as 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 associações externas na consulta e os resultados estão chegando conforme o esperado.
Agora, e aqueles outros tipos de junções que você pergunta? E as interseções?
Bem, nem todos os bancos de dados suportam a
intersection
mas praticamente todos os bancos de dados permitirão que você crie uma interseção por meio de uma junção (ou pelo menos uma instrução where bem estruturada). Uma interseção é um tipo de junção um pouco semelhante a uma
union
como descrito acima - mas a diferença é que somente retorna linhas de dados que são idênticas (e 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 assim:
select
*
from
colors
where
ID>2
intersect
select
*
from
colors
where
id<4
Enquanto uma
union
normal consulta retornaria todas as linhas da tabela (a primeira consulta retornando qualquer coisa sobre ID>2
e a segunda qualquer coisa com ID<4
) que resultaria em um conjunto completo, uma consulta de interseção retornaria apenas a linha correspondente a id=3
pois atende a ambos os critérios. Agora, se seu banco de dados não suporta um
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 realizar uma interseção em duas tabelas diferentes usando um banco de dados que não oferece suporte inerente a uma consulta de interseção, você precisará criar uma junção em cada coluna das mesas.