Relacionando Tabelas
Por que relacionar tabelas?
Ate agora trabalhamos com uma tabela so. Mas no mundo real, os dados se conectam: um aluno se matricula em cursos, um pedido tem produtos, um post tem comentarios. Se voce colocar tudo em uma tabela so, vai ter muita repeticao de dados. A solucao e dividir em tabelas e conectar elas com chaves estrangeiras.
Chave estrangeira (Foreign Key)
Uma chave estrangeira (FK) e uma coluna que referencia a chave primaria de outra tabela. Ela cria um vinculo entre as tabelas e garante que voce nao pode inserir um valor que nao existe na tabela referenciada.
CREATE TABLE cursos (
id SERIAL PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
carga_horaria INTEGER NOT NULL
);
CREATE TABLE matriculas (
id SERIAL PRIMARY KEY,
aluno_id INTEGER NOT NULL REFERENCES alunos(id),
curso_id INTEGER NOT NULL REFERENCES cursos(id),
data_matricula DATE DEFAULT CURRENT_DATE
);
Agora matriculas.aluno_id aponta para alunos.id e matriculas.curso_id aponta para cursos.id. O banco nao vai deixar voce inserir uma matricula com um aluno_id que nao existe na tabela alunos.
Tipos de relacionamento
1:1 (Um para Um)
Cada registro de uma tabela se relaciona com no maximo um registro da outra. Exemplo: cada aluno tem um perfil.
CREATE TABLE perfis (
id SERIAL PRIMARY KEY,
aluno_id INTEGER UNIQUE NOT NULL REFERENCES alunos(id),
bio TEXT,
foto_url VARCHAR(255)
);
1:N (Um para Muitos)
Um registro se relaciona com varios da outra tabela. Exemplo: um curso tem muitas matriculas.
-- Um curso → muitas matriculas
-- A FK fica no lado "muitos" (matriculas)
CREATE TABLE matriculas (
id SERIAL PRIMARY KEY,
aluno_id INTEGER NOT NULL REFERENCES alunos(id),
curso_id INTEGER NOT NULL REFERENCES cursos(id),
data_matricula DATE DEFAULT CURRENT_DATE
);
N:N (Muitos para Muitos)
Quando os dois lados podem ter varios. Exemplo: um aluno pode se matricular em varios cursos, e um curso pode ter varios alunos. A solucao e criar uma tabela intermediaria (tambem chamada de tabela pivot):
-- alunos N:N cursos → tabela intermediaria: matriculas
-- matriculas e a tabela que conecta as duas
INSERT INTO matriculas (aluno_id, curso_id) VALUES (1, 1);
INSERT INTO matriculas (aluno_id, curso_id) VALUES (1, 2); -- Ana em 2 cursos
INSERT INTO matriculas (aluno_id, curso_id) VALUES (2, 1); -- Bruno no curso 1
ON DELETE: o que acontece quando apaga o pai?
Quando voce apaga um registro que e referenciado por outra tabela, o banco precisa saber o que fazer:
CREATE TABLE matriculas (
id SERIAL PRIMARY KEY,
aluno_id INTEGER NOT NULL REFERENCES alunos(id) ON DELETE CASCADE,
curso_id INTEGER NOT NULL REFERENCES cursos(id) ON DELETE RESTRICT,
data_matricula DATE DEFAULT CURRENT_DATE
);
| Opcao | O que faz |
|---|---|
CASCADE | Apaga os filhos junto com o pai |
RESTRICT | Impede a exclusao se existir filhos |
SET NULL | Seta a FK como NULL nos filhos |
SET DEFAULT | Seta a FK como o valor padrao |
NO ACTION | Parecido com RESTRICT (padrao) |
Relacionamentos nos ORMs
Prisma
model Aluno {
id Int @id @default(autoincrement())
nome String
matriculas Matricula[]
perfil Perfil?
}
model Curso {
id Int @id @default(autoincrement())
nome String
matriculas Matricula[]
}
model Matricula {
id Int @id @default(autoincrement())
aluno Aluno @relation(fields: [alunoId], references: [id])
alunoId Int
curso Curso @relation(fields: [cursoId], references: [id])
cursoId Int
dataMatricula DateTime @default(now())
}
model Perfil {
id Int @id @default(autoincrement())
aluno Aluno @relation(fields: [alunoId], references: [id])
alunoId Int @unique
bio String?
fotoUrl String?
}
Drizzle
import { pgTable, serial, integer, varchar, date, text } from 'drizzle-orm/pg-core'
export const alunos = pgTable('alunos', {
id: serial('id').primaryKey(),
nome: varchar('nome', { length: 100 }).notNull(),
})
export const cursos = pgTable('cursos', {
id: serial('id').primaryKey(),
nome: varchar('nome', { length: 100 }).notNull(),
})
export const matriculas = pgTable('matriculas', {
id: serial('id').primaryKey(),
alunoId: integer('aluno_id').notNull().references(() => alunos.id),
cursoId: integer('curso_id').notNull().references(() => cursos.id),
dataMatricula: date('data_matricula').defaultNow(),
})
Lucid (AdonisJS)
// app/models/aluno.ts
import { BaseModel, column, hasMany, hasOne } from '@adonisjs/lucid/orm'
import Matricula from './matricula.js'
import Perfil from './perfil.js'
export default class Aluno extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column()
declare nome: string
@hasMany(() => Matricula)
declare matriculas: HasMany<typeof Matricula>
@hasOne(() => Perfil)
declare perfil: HasOne<typeof Perfil>
}
// app/models/matricula.ts
import { BaseModel, column, belongsTo } from '@adonisjs/lucid/orm'
import Aluno from './aluno.js'
import Curso from './curso.js'
export default class Matricula extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column()
declare alunoId: number
@column()
declare cursoId: number
@belongsTo(() => Aluno)
declare aluno: BelongsTo<typeof Aluno>
@belongsTo(() => Curso)
declare curso: BelongsTo<typeof Curso>
}
Exercicios
- Crie as tabelas
autoreselivroscom um relacionamento 1:N (um autor tem muitos livros). - Crie uma tabela
avaliacoesque conectaalunosecursoscom uma nota de avaliacao (N:N com atributo extra). - Adicione
ON DELETE CASCADEna FK dematriculasparaalunoseON DELETE RESTRICTparacursos. Explique por que cada escolha faz sentido. - Desenhe (no papel ou em texto) o diagrama de relacionamento entre:
alunos,cursos,matriculas,perfis.
Referencias
- Foreign Keys - PostgreSQL Docs -- documentacao oficial
- SQL FOREIGN KEY Constraint -- W3Schools
- Prisma Relations -- documentacao oficial Prisma
Testa o que você leu
5 perguntas sobre esta aula. Errar aqui não custa nada - a explicação vem junto da correção.