Modelagem Lógica: mapeamento do modelo ER para o relacional

Quero começar este módulo te situando exatamente onde estamos na jornada do projeto de um banco de dados, porque é justamente esse “lugar no mapa” que costuma render questão de prova. Nos módulos anteriores você desenhou o modelo conceitual: aquele Diagrama Entidade-Relacionamento (DER) cheio de retângulos, losangos e linhas, que conversa com o usuário de negócio e ignora deliberadamente qualquer detalhe de implementação. Agora vamos descer um degrau de abstração. A modelagem lógica é a etapa em que pego aquele desenho conceitual e o traduzo para o modelo de dados de um paradigma específico — no nosso caso, o modelo relacional, feito de tabelas, colunas, chaves primárias e chaves estrangeiras. Repare comigo: ainda não escolhemos o SGBD (PostgreSQL, Oracle, SQL Server), ainda não pensamos em índices, tablespaces ou tipos físicos de armazenamento. Isso fica para a modelagem física. A lógica é a ponte intermediária, e dominá-la é o que separa quem “sabe desenhar DER” de quem realmente sabe construir bancos de dados.

Vou te mostrar o panorama das três camadas para fixar a posição.

flowchart LR
    A[Modelo Conceitual - DER] --> B[Modelo Logico - Relacional]
    B --> C[Modelo Fisico - SGBD especifico]
    A -. independente de tecnologia .-> A
    B -. independente de SGBD .-> B
    C -. dependente do SGBD .-> C

Definição. A modelagem lógica é o processo de mapear um modelo conceitual (DER) para as estruturas de um modelo de dados específico — aqui, o relacional — produzindo um esquema composto de tabelas, colunas, chaves e restrições, ainda sem comprometer-se com detalhes físicos de um SGBD particular.

Por que essa etapa cai em prova

Antes de entrar nas regras mecânicas, deixa eu te convencer de que essa etapa não é burocracia. A modelagem lógica refina o conceitual, transformando conceitos abstratos em estruturas concretas e, nesse caminho, expõe ambiguidades que ficavam escondidas no DER. Ela garante independência de SGBD: o mesmo modelo lógico pode ser implementado em PostgreSQL ou em Oracle com poucos ajustes. Ela é o ponto onde aplico normalização para reduzir redundância — assunto que aprofundaremos no módulo de Normalização — e onde defino as restrições de integridade que manterão os dados consistentes. E, não menos importante, ela serve como documentação e como linguagem comum entre analistas, projetistas e desenvolvedores. Quando a prova pergunta “qual o papel do modelo lógico”, é esse conjunto de funções que ela espera ver: refinamento, independência tecnológica, otimização estrutural, integridade e documentação.

Vou abrir um pouco mais cada uma dessas funções, porque elas costumam aparecer fragmentadas em alternativas de prova. O refinamento do conceitual significa que, ao traduzir, sou obrigado a tomar decisões que o DER permitia adiar: que atributo será chave primária, como um atributo composto se decompõe, se um relacionamento opcional aceita nulo. Cada uma dessas decisões fecha uma lacuna. A independência de SGBD é o que dá portabilidade ao projeto: como ainda não escolhi tipos físicos nem recursos proprietários, o mesmo esquema lógico sobrevive a uma troca de fornecedor. A otimização estrutural, via normalização, ataca a redundância na origem, evitando que o mesmo dado apareça repetido em vários lugares e gere anomalias de inserção, atualização e exclusão. A integridade, por sua vez, transforma regras de negócio em restrições verificáveis pelo próprio banco, e não em promessas que dependem da disciplina do programador. E a documentação consolida tudo isso num artefato que sobrevive à rotatividade da equipe. Quando você enxerga essas cinco funções como um conjunto, fica fácil reconhecê-las isoladas numa questão.

O processo, na prática, segue um roteiro que vou destrinchar ao longo do módulo. Olhe o fluxo geral:

flowchart TD
    A[Analisar modelo conceitual] --> B[Mapear entidades para tabelas]
    B --> C[Mapear atributos para colunas]
    C --> D[Definir chaves primarias]
    D --> E[Mapear relacionamentos]
    E --> F[Tratar entidades fracas]
    F --> G[Tratar generalizacao especializacao]
    G --> H[Definir restricoes]
    H --> I[Normalizar e refinar]
    I --> J[Validar e documentar]

Regra 1 — Entidades viram tabelas, atributos viram colunas

A regra mais elementar do mapeamento é direta: cada entidade do DER se transforma em uma tabela, cada atributo da entidade vira uma coluna dessa tabela, e o identificador da entidade vira a chave primária. Vou usar como fio condutor um sistema de biblioteca, que carregaremos pelo módulo inteiro. Suponha a entidade Livro com os atributos ISBN (identificador), título, ano de publicação e número de páginas. O mapeamento produz:

CREATE TABLE livro (
    isbn          VARCHAR(13)  PRIMARY KEY,
    titulo        VARCHAR(200) NOT NULL,
    ano_publicacao SMALLINT,
    num_paginas   INTEGER
);

Note que já apareceu uma decisão de projeto: escolhi o ISBN como chave primária porque ele identifica unicamente cada livro. Quando uma entidade não tem um identificador natural confiável, é comum introduzir uma chave substituta (uma surrogate key), tipicamente uma coluna numérica autoincrementada. Veja a entidade Usuario, que poderia até ter CPF, mas onde prefiro uma chave artificial por simplicidade e estabilidade:

CREATE TABLE usuario (
    id_usuario  SERIAL       PRIMARY KEY,
    nome        VARCHAR(120) NOT NULL,
    cpf         CHAR(11)     UNIQUE,
    email       VARCHAR(150)
);

Repare que mantive o CPF na tabela, mas como chave candidata marcada com UNIQUE, não como chave primária. Essa distinção entre chave primária e chave candidata é exatamente o tipo de detalhe que a modelagem lógica formaliza.

Atributos que não mapeiam direto

Nem todo atributo do DER vira uma coluna simples. Há três casos especiais que você precisa reconhecer de imediato. O atributo composto — como um endereço formado por rua, número, cidade e CEP — normalmente é decomposto em várias colunas atômicas, porque o modelo relacional, na Primeira Forma Normal, exige valores atômicos. O atributo multivalorado — como vários telefones de um mesmo usuário — não cabe em uma única coluna; ele exige uma tabela própria, ligada por chave estrangeira à entidade original. E o atributo derivado — como a idade, calculável a partir da data de nascimento — geralmente não é armazenado, sendo computado quando necessário. Veja o tratamento do multivalorado:

CREATE TABLE telefone_usuario (
    id_usuario  INTEGER     NOT NULL REFERENCES usuario (id_usuario),
    telefone    VARCHAR(20) NOT NULL,
    PRIMARY KEY (id_usuario, telefone)
);

Atenção. Um atributo multivalorado nunca deve virar uma coluna do tipo “telefone1, telefone2, telefone3”. Isso viola a Primeira Forma Normal e engessa o modelo. A solução correta é sempre uma tabela separada com chave estrangeira de volta à entidade dona.

Regra 2 — Relacionamentos um-para-um

Agora entramos no coração da modelagem lógica: a tradução de relacionamentos. O modelo relacional não tem “losangos”; ele expressa associações por meio de chaves estrangeiras e, quando necessário, de tabelas próprias. A estratégia muda conforme a cardinalidade.

Comecemos pelo um-para-um (1:1). Imagine que cada usuário possui no máximo um cartão de matrícula, e cada cartão pertence a um único usuário. Há três abordagens possíveis, e a escolha depende da obrigatoriedade da participação. A mais comum, quando um dos lados é opcional, é colocar a chave estrangeira do lado obrigatório dentro da tabela do lado opcional, marcando-a como UNIQUE para garantir a cardinalidade máxima de um:

CREATE TABLE cartao (
    id_cartao   SERIAL      PRIMARY KEY,
    codigo      VARCHAR(30) NOT NULL,
    id_usuario  INTEGER     NOT NULL UNIQUE REFERENCES usuario (id_usuario)
);

O UNIQUE sobre id_usuario é o detalhe decisivo: sem ele, eu teria um relacionamento um-para-muitos disfarçado, pois nada impediria dois cartões apontarem para o mesmo usuário. É esse pequeno detalhe que faz a cardinalidade máxima de um ser respeitada pelo banco. Quando os dois lados são obrigatórios e fortemente acoplados — quando um nunca existe sem o outro — uma alternativa elegante é fundir as duas entidades em uma única tabela, evitando uma junção permanente e um relacionamento que nunca traria nulos. Já se a participação for opcional dos dois lados, a chave estrangeira pode ir para qualquer um dos lados; nesse caso, escolho o lado que torna as consultas mais frequentes mais simples, ou, em situações de baixo acoplamento, crio uma terceira tabela só com o par de chaves. Perceba que, ao contrário do 1:N, no 1:1 a posição da chave estrangeira é uma decisão de projeto com mais de uma resposta correta, guiada pela obrigatoriedade e pelo padrão de acesso. Vamos visualizar:

erDiagram
    USUARIO ||--o| CARTAO : possui
    USUARIO {
        int id_usuario PK
        string nome
    }
    CARTAO {
        int id_cartao PK
        int id_usuario FK
        string codigo
    }

Regra 3 — Relacionamentos um-para-muitos

O relacionamento um-para-muitos (1:N) é o mais frequente e o mais simples de mapear, então preste bastante atenção porque ele é a base de tudo. A regra é uma só: a chave primária do lado “um” é replicada como chave estrangeira na tabela do lado “muitos”. Pense em editoras e livros — uma editora publica muitos livros, e cada livro pertence a uma única editora. O “um” é a editora; o “muitos” é o livro. Logo, a chave da editora desce para a tabela livro:

CREATE TABLE editora (
    id_editora  SERIAL       PRIMARY KEY,
    nome        VARCHAR(120) NOT NULL,
    cidade      VARCHAR(80)
);

ALTER TABLE livro
    ADD COLUMN id_editora INTEGER REFERENCES editora (id_editora);

Quero que você internalize a lógica por trás disso, não apenas a mecânica. A chave estrangeira vai sempre para o lado “muitos” porque cada linha desse lado se associa a no máximo um registro do outro. Se eu tentasse o contrário — guardar uma lista de livros dentro da editora — cairia de novo no problema do multivalorado. Colocar a chave no lado “muitos” mantém cada coluna atômica e o relacionamento limpo.

Há ainda uma decisão fina que distingue o bom modelador: a obrigatoriedade da participação. Se todo livro precisa obrigatoriamente ter uma editora, então a coluna id_editora deve ser NOT NULL, e o banco passa a recusar qualquer livro órfão. Se, ao contrário, um livro pode existir temporariamente sem editora cadastrada, a coluna aceita nulo e a participação é opcional. Essa diferença entre cardinalidade mínima zero e um, que no DER aparecia como uma bolinha ou um traço na linha do relacionamento, agora vira uma cláusula concreta no CREATE TABLE. É exatamente esse tipo de tradução fiel — do símbolo conceitual para a restrição executável — que dá valor à etapa lógica.

erDiagram
    EDITORA ||--o{ LIVRO : publica
    EDITORA {
        int id_editora PK
        string nome
    }
    LIVRO {
        string isbn PK
        int id_editora FK
        string titulo
    }

Síntese da regra 1:N. A chave primária do lado “um” entra como chave estrangeira na tabela do lado “muitos”. Se a participação do lado “muitos” for obrigatória, marque a chave estrangeira como NOT NULL; se for opcional, permita nulo.

Regra 4 — Relacionamentos muitos-para-muitos e a tabela associativa

Chegamos ao caso que mais derruba estudante desatento. No relacionamento muitos-para-muitos (N:M), nenhuma das duas tabelas pode hospedar a chave estrangeira da outra, porque cada lado se associa a vários do outro. Pense em empréstimos: um usuário pega muitos livros ao longo do tempo, e um mesmo livro é emprestado a muitos usuários. Não dá para guardar a chave do livro na tabela de usuário nem o contrário. A solução é criar uma tabela associativa (também chamada de tabela de junção, de ligação ou bridge table), que decompõe o N:M em dois relacionamentos 1:N.

Essa tabela nova recebe as chaves primárias das duas entidades como chaves estrangeiras, e a combinação delas costuma formar a chave primária composta. O ponto que muita gente ignora: a tabela associativa é o lugar natural dos atributos do próprio relacionamento. A data do empréstimo e a data de devolução não pertencem ao usuário nem ao livro — pertencem ao ato de emprestar. Veja:

CREATE TABLE emprestimo (
    id_usuario     INTEGER NOT NULL REFERENCES usuario (id_usuario),
    isbn           VARCHAR(13) NOT NULL REFERENCES livro (isbn),
    data_emprestimo DATE   NOT NULL,
    data_devolucao DATE,
    PRIMARY KEY (id_usuario, isbn, data_emprestimo)
);

Observe que incluí data_emprestimo na chave primária. Sem ela, o mesmo usuário jamais poderia pegar o mesmo livro duas vezes em datas diferentes — o que seria um erro de modelagem. Esse refinamento da chave da tabela associativa, conforme as regras de negócio, é típico da etapa lógica. Quando o relacionamento tem identidade própria forte, é comum até introduzir uma chave substituta id_emprestimo SERIAL e tratar o par como UNIQUE.

erDiagram
    USUARIO ||--o{ EMPRESTIMO : realiza
    LIVRO   ||--o{ EMPRESTIMO : consta_em
    EMPRESTIMO {
        int id_usuario PK
        string isbn PK
        date data_emprestimo PK
        date data_devolucao
    }

Para você não esquecer o critério de decisão, montei uma tabela-resumo dos três tipos de mapeamento de cardinalidade.

Cardinalidade Estratégia de mapeamento Onde vai a chave estrangeira
1:1 FK com UNIQUE (ou fusão de tabelas) No lado opcional, ou em qualquer lado
1:N FK simples Sempre no lado “muitos”
N:M Tabela associativa Nas duas FKs, compondo a PK

Regra 5 — Entidades fracas

Uma entidade fraca é aquela que não tem existência própria nem identificador suficiente sozinha: ela depende de uma entidade forte para ser identificada. O exemplo clássico é a dependência de um item de pedido em relação ao pedido, ou, no nosso domínio, os exemplares físicos de um livro. Um exemplar não faz sentido sem o livro a que pertence, e seu número de tombo só é único dentro daquele livro. No mapeamento lógico, a tabela da entidade fraca recebe a chave primária da entidade forte como parte de sua própria chave primária — uma chave composta pela chave da forte mais o discriminador parcial da fraca:

CREATE TABLE exemplar (
    isbn          VARCHAR(13) NOT NULL REFERENCES livro (isbn),
    num_exemplar  INTEGER     NOT NULL,
    estado_conservacao VARCHAR(30),
    PRIMARY KEY (isbn, num_exemplar)
);

A diferença em relação a um 1:N comum é sutil mas importante para a prova: na entidade fraca, a chave estrangeira participa da chave primária, refletindo a dependência de identificação. Em um 1:N comum, a chave estrangeira é apenas um atributo a mais, fora da chave primária. Vou tornar isso visível com um contraste: se eu modelasse exemplar com uma chave artificial própria id_exemplar SERIAL PRIMARY KEY e apenas uma FK simples para o livro, eu teria tratado o exemplar como entidade forte, e o número do exemplar deixaria de depender do livro. A modelagem como fraca é uma escolha que preserva a semântica original do DER: o exemplar 3 do livro A é coisa distinta do exemplar 3 do livro B, e só a chave composta captura isso. Na prática, muitos projetistas optam pela chave artificial por conveniência de implementação, mas você precisa saber que, no plano lógico estrito, a entidade fraca pede chave composta herdada.

Atenção à diferença. Em um relacionamento 1:N normal, a FK fica fora da PK. Numa entidade fraca, a FK da entidade forte entra dentro da PK composta. Confundir os dois é erro comum em questões discursivas.

Regra 6 — Generalização e especialização

Quando o DER traz hierarquias de herança — uma entidade genérica Pessoa especializada em Aluno e Professor, por exemplo — o modelo relacional, que não conhece herança nativa, oferece três estratégias de mapeamento. Vou te apresentar as três, porque a escolha entre elas é matéria recorrente de prova e cada uma tem um trade-off claro.

A primeira estratégia é a tabela única (single table): uma só tabela reúne a superclasse e todas as subclasses, com uma coluna discriminadora indicando o tipo e colunas específicas que ficam nulas quando não se aplicam.

CREATE TABLE pessoa (
    id_pessoa   SERIAL      PRIMARY KEY,
    nome        VARCHAR(120) NOT NULL,
    tipo        CHAR(1)     NOT NULL CHECK (tipo IN ('A','P')),
    matricula   VARCHAR(20),
    titulacao   VARCHAR(40)
);

A segunda é a tabela por classe concreta: cada subclasse vira sua própria tabela, repetindo os atributos da superclasse, e a superclasse não tem tabela.

CREATE TABLE aluno (
    id_aluno   SERIAL       PRIMARY KEY,
    nome       VARCHAR(120) NOT NULL,
    matricula  VARCHAR(20)  NOT NULL
);

CREATE TABLE professor (
    id_professor SERIAL     PRIMARY KEY,
    nome         VARCHAR(120) NOT NULL,
    titulacao    VARCHAR(40)
);

A terceira, e mais normalizada, é a tabela por tipo (class table inheritance): a superclasse vira uma tabela, cada subclasse vira outra tabela cuja chave primária é também chave estrangeira para a superclasse, num relacionamento 1:1.

CREATE TABLE pessoa (
    id_pessoa  SERIAL       PRIMARY KEY,
    nome       VARCHAR(120) NOT NULL
);

CREATE TABLE aluno (
    id_pessoa  INTEGER PRIMARY KEY REFERENCES pessoa (id_pessoa),
    matricula  VARCHAR(20) NOT NULL
);

CREATE TABLE professor (
    id_pessoa  INTEGER PRIMARY KEY REFERENCES pessoa (id_pessoa),
    titulacao  VARCHAR(40)
);

A escolha entre as três não é arbitrária; ela depende de quanto as subclasses divergem e de como você vai consultar os dados. Se as subclasses têm poucos atributos próprios e você consulta frequentemente todas as pessoas juntas, a tabela única vence pela simplicidade, ainda que ao custo de colunas nulas e de uma integridade mais frouxa — afinal, nada na estrutura impede que um aluno tenha titulação preenchida por engano, restando apenas a cláusula CHECK para mitigar. Se as subclasses são muito distintas e raramente consultadas em conjunto, a tabela por classe concreta isola bem cada tipo, mas duplica os atributos comuns e dificulta perguntas globais do tipo “todas as pessoas da instituição”. Se a prioridade é fidelidade ao modelo e ausência de redundância, a tabela por tipo é a mais correta teoricamente, pagando o preço de junções para reconstituir uma entidade completa. Não existe vencedora absoluta; existe a adequada ao caso. Compare comigo os trade-offs antes de decidir:

Estratégia Vantagem Desvantagem
Tabela única Consultas simples, sem junções Muitas colunas nulas, integridade fraca
Tabela por classe concreta Sem nulos, cada tipo isolado Atributos comuns duplicados, consultas globais difíceis
Tabela por tipo Normalizada, sem redundância Exige junções para reconstituir o objeto

flowchart TD
    P[Pessoa - superclasse] --> A[Aluno]
    P --> Pr[Professor]
    A -. herda atributos .-> P
    Pr -. herda atributos .-> P

Regra 7 — Definição de restrições

Mapear estruturas é metade do trabalho; a outra metade é blindar o esquema com restrições que garantam a integridade. Na modelagem lógica eu defino formalmente a integridade de entidade (toda tabela tem chave primária, e nenhuma parte dela pode ser nula), a integridade referencial (toda chave estrangeira aponta para uma chave primária existente ou é nula) e as restrições de domínio (cada coluna só aceita valores válidos). É aqui que decido também o comportamento das chaves estrangeiras em cascata. Veja um exemplo consolidando várias restrições:

CREATE TABLE reserva (
    id_reserva   SERIAL      PRIMARY KEY,
    id_usuario   INTEGER     NOT NULL,
    isbn         VARCHAR(13) NOT NULL,
    data_reserva DATE        NOT NULL DEFAULT CURRENT_DATE,
    status       VARCHAR(15) NOT NULL DEFAULT 'ativa'
                 CHECK (status IN ('ativa','atendida','cancelada')),
    CONSTRAINT fk_reserva_usuario
        FOREIGN KEY (id_usuario) REFERENCES usuario (id_usuario)
        ON DELETE CASCADE,
    CONSTRAINT fk_reserva_livro
        FOREIGN KEY (isbn) REFERENCES livro (isbn)
        ON DELETE RESTRICT,
    CONSTRAINT uq_reserva UNIQUE (id_usuario, isbn, data_reserva)
);

Repare nas decisões: o ON DELETE CASCADE na FK do usuário significa que apagar um usuário apaga suas reservas; já o ON DELETE RESTRICT na FK do livro impede apagar um livro que ainda tenha reservas. Essas escolhas de propagação são parte legítima do modelo lógico, pois expressam regras de negócio sobre a vida dos dados. A integridade referencial pode ser visualizada como um grafo de dependências entre tabelas:

flowchart LR
    RESERVA --> USUARIO
    RESERVA --> LIVRO
    EMPRESTIMO --> USUARIO
    EMPRESTIMO --> LIVRO
    EXEMPLAR --> LIVRO
    LIVRO --> EDITORA
    TELEFONE_USUARIO --> USUARIO

Definição. A integridade referencial é a propriedade que garante que todo valor de chave estrangeira corresponda a um valor existente da chave primária referenciada, ou seja nulo. Formalmente, para uma FK F sobre a relação R referenciando a chave K de S, exige-se que \pi_F(R) \subseteq \pi_K(S) \cup \{\text{nulo}\}.

Refinamento, normalização e validação

Com o esquema rascunhado, entra a fase iterativa. A normalização, que trataremos em profundidade no módulo próprio, decompõe tabelas para eliminar redundância e anomalias de atualização, levando o modelo às formas normais. No refinamento, reviso se cada tabela atende aos requisitos, ajusto tipos, reconsidero chaves e às vezes faço uma desnormalização controlada por desempenho — sempre uma decisão consciente, não um descuido. A validação confronta o modelo com os stakeholders e com os casos de uso reais: consigo, com este esquema, responder a todas as consultas que o negócio precisa? Toda regra de negócio está garantida por alguma restrição? Por fim, a documentação registra o dicionário de dados — tabelas, colunas, tipos, restrições — e as justificativas de cada decisão de projeto, transformando o modelo em conhecimento transmissível.

Vale insistir num ponto que a fonte enfatiza: o processo é iterativo. Você raramente acerta o modelo lógico de primeira. Cada validação revela um requisito esquecido, cada normalização sugere uma nova decomposição, e o ciclo recomeça até a estabilidade.

Síntese para a prova. Guarde antes de tudo o roteiro de mapeamento: entidade vira tabela, atributo vira coluna, identificador vira chave primária. Para relacionamentos, fixe o critério por cardinalidade — 1:1 resolve-se com chave estrangeira marcada UNIQUE ou fusão de tabelas; 1:N coloca a chave do lado “um” como chave estrangeira no lado “muitos”; N:M sempre exige tabela associativa que recebe as duas chaves e hospeda os atributos do relacionamento. Lembre que entidade fraca herda a chave da entidade forte dentro de sua chave primária composta, o que a distingue de um 1:N comum. Para generalização e especialização, saiba descrever as três estratégias — tabela única, tabela por classe concreta e tabela por tipo — com seus trade-offs entre junções e nulos. Não esqueça as três integridades — de entidade, referencial e de domínio — nem o papel das ações ON DELETE. E tenha sempre na ponta da língua o lugar da modelagem lógica: a ponte entre o conceitual independente de tecnologia e o físico dependente do SGBD, com refinamento, normalização, validação e documentação fechando o ciclo iterativo.