Guia Oficial de Modelagem Visual no DrawDB

Dominando o DrawDB: Flags, Relacionamentos e Templates SQL

Aprenda o que significa cada botão da interface do DrawDB, como ligar Chaves Primárias (PK) e Estrangeiras (FK) sem erro e importe 3 projetos completos com 1 clique via File > Import > From SQL.

1. O que significam as siglas PK, AI, NN, UQ e UN no DrawDB?

PK

Primary Key (Chave Primária)

Identifica exclusivamente cada linha da tabela. Não aceita valores duplicados nem NULL.

Regra Prática: Toda tabela DEVE ter uma Primary Key (geralmente a coluna `id`).
AI

Auto Increment (Autoincremento / Serial)

Faz o banco somar +1 automaticamente a cada novo INSERT (1, 2, 3, 4...).

Regra Prática: Ative junto com PK em colunas `INT` ou `BIGINT`. Nunca marque AI em uma Foreign Key (FK)!
NN

Not Null (Obrigatório)

Impede que a coluna fique vazia (`NULL`). Se o PHP tentar salvar sem preencher, o banco bloqueia.

Regra Prática: Por padrão, marque NN em quase tudo, deixando desmarcado apenas o que for genuinamente opcional (ex: `complemento`, `deletado_em`).
UQ

Unique (Valor Único)

Garante que duas linhas nunca tenham o mesmo valor nesta coluna (cria um índice UNIQUE).

Regra Prática: Essencial para `email`, `cpf`, `cnpj`, `slug` e `uuid`.
UN

Unsigned (Sem Sinal Negativo)

Impede números negativos e dobra a capacidade positiva do inteiro (ex: de 2 bilhões para 4 bilhões no INT).

Regra Prática: Se a PK `id` da tabela pai estiver marcada como `UN` (Unsigned), a FK `tabela_id` na tabela filha TAMBÉM precisa estar marcada como `UN`!

2. Como Desenhar Relacionamentos no DrawDB (1:N, N:N e 1:1)

1 : N (Um para Muitos) 1 Categoria tem Muitos Produtos (`categorias` 1 ───< `produtos`)

O Relacionamento Mais Comum (90% dos casos)

A chave estrangeira (`categoria_id`) fica SEMPRE do lado do "Muitos" (na tabela `produtos`). No DrawDB, arraste a linha do `id` de `categorias` até `categoria_id` em `produtos`.

💡 Regra de Ouro: Quem tem MUITOS não guarda lista de IDs; quem pertence a UM é que guarda o ID do pai!
N : N (Muitos para Muitos) 1 Aluno faz Muitos Cursos, e 1 Curso tem Muitos Alunos (`alunos` >───< `cursos`)

Relacionamento com Tabela Associativa (Pivô)

Bancos relacionais não ligam N:N diretamente. Você cria uma 3ª tabela no meio (ex: `matriculas` ou `aluno_curso`) contendo duas FKs: `aluno_id` e `curso_id`.

💡 Regra de Ouro: Na tabela pivô, você pode adicionar dados extras do vínculo, como `data_matricula` e `preco_pago`!
1 : 1 (Um para Um) 1 Usuário tem 1 Perfil Fiscal (`usuarios` 1 ─── 1 `perfis_fiscais`)

Divisão de Dados Sensíveis ou Extensões Opcionais

Crie a FK `usuario_id` na segunda tabela e marque a flag `UQ` (Unique) nela. Assim, o mesmo usuário não pode ter duas linhas.

💡 Regra de Ouro: Use quando uma tabela tem dezenas de colunas pouco usadas ou dados sigilosos separados.

3. Ações ON DELETE (Integridade)

Ao clicar em uma linha de relacionamento no DrawDB, você define o que acontece se o registro Pai for apagado:

ON DELETE RESTRICT (ou NO ACTION)
Mais Seguro para Financeiro

Impede apagar o registro Pai se existirem registros Filhos vinculados. Ex: impede apagar um Cliente que já possui Pedidos emitidos.

ON DELETE CASCADE
Para Sub-itens Dependentes

Ao apagar o Pai, o banco apaga automaticamente todos os Filhos. Ex: ao apagar um Pedido em rascunho, apaga os `itens_pedido` daquele pedido.

ON DELETE SET NULL
Para Vínculos Opcionais

Ao apagar o Pai, a coluna FK no Filho vira `NULL` (exige que a FK não seja `NN`). Ex: se um Vendedor for demitido/excluído, o pedido continua existindo com `vendedor_id = NULL`.

4. Modelos Práticos Completos para Estudar no DrawDB

Passo a passo: Clique em "Copiar SQL para o DrawDB" abaixo, abra o DrawDB Editor, escolha MySQL, vá no menu superior File > Import > From SQL e cole! O diagrama inteiro será desenhado automaticamente.

Nível Essencial 6 Tabelas Relacionadas

Modelo 1: E-Commerce Completo (Lojas Virtuais)

Modelagem clássica com Clientes, Endereços, Categorias, Produtos, Pedidos e Itens do Pedido. Demonstra o uso correto de VARCHAR, CHAR, DECIMAL(10,2), INT, ENUM e TIMESTAMP.

🔍 O que observar ao abrir este diagrama no DrawDB:

  • ▸Observe por que `preco_unitario` existe tanto em `produtos` quanto em `itens_pedido` (histórico imutável: se o produto subir de preço amanhã, os pedidos antigos não podem mudar de valor!).
  • ▸Repare no uso de `CHAR(11)` para CPF, `CHAR(8)` para CEP e `CHAR(2)` para UF.
  • ▸Todas as chaves primárias e estrangeiras usam exatamente o mesmo tipo (`BIGINT UNSIGNED` ou `INT UNSIGNED`).
🗄️ Script SQL DDL Completo (Import > From SQL)
CREATE TABLE categorias (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(80) NOT NULL,
    slug VARCHAR(100) NOT NULL UNIQUE,
    ativa BOOLEAN NOT NULL DEFAULT 1
);

CREATE TABLE produtos (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    categoria_id INT UNSIGNED NOT NULL,
    nome VARCHAR(150) NOT NULL,
    sku VARCHAR(40) NOT NULL UNIQUE,
    descricao TEXT NULL,
    preco_atual DECIMAL(10, 2) NOT NULL,
    estoque_atual INT NOT NULL DEFAULT 0,
    peso_kg DECIMAL(8, 3) NOT NULL DEFAULT 0.000,
    ativo BOOLEAN NOT NULL DEFAULT 1,
    criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_produtos_categoria FOREIGN KEY (categoria_id) REFERENCES categorias(id) ON DELETE RESTRICT
);

CREATE TABLE clientes (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nome_completo VARCHAR(120) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    senha_hash VARCHAR(255) NOT NULL,
    cpf CHAR(11) NOT NULL UNIQUE,
    telefone VARCHAR(20) NULL,
    data_nascimento DATE NULL,
    criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE enderecos_cliente (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    cliente_id BIGINT UNSIGNED NOT NULL,
    rotulo VARCHAR(40) NOT NULL COMMENT 'Ex: Casa, Trabalho',
    cep CHAR(8) NOT NULL,
    logradouro VARCHAR(150) NOT NULL,
    numero VARCHAR(20) NOT NULL,
    complemento VARCHAR(80) NULL,
    bairro VARCHAR(80) NOT NULL,
    cidade VARCHAR(80) NOT NULL,
    uf CHAR(2) NOT NULL,
    CONSTRAINT fk_enderecos_cliente FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE CASCADE
);

CREATE TABLE pedidos (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    codigo_publico CHAR(36) NOT NULL UNIQUE,
    cliente_id BIGINT UNSIGNED NOT NULL,
    endereco_entrega_id BIGINT UNSIGNED NOT NULL,
    status ENUM('aguardando_pagamento', 'pago', 'em_separacao', 'enviado', 'entregue', 'cancelado') NOT NULL DEFAULT 'aguardando_pagamento',
    valor_produtos DECIMAL(10, 2) NOT NULL,
    valor_frete DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
    valor_desconto DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
    valor_total DECIMAL(10, 2) NOT NULL,
    criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_pedidos_cliente FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE RESTRICT,
    CONSTRAINT fk_pedidos_endereco FOREIGN KEY (endereco_entrega_id) REFERENCES enderecos_cliente(id) ON DELETE RESTRICT
);

CREATE TABLE itens_pedido (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    pedido_id BIGINT UNSIGNED NOT NULL,
    produto_id INT UNSIGNED NOT NULL,
    quantidade SMALLINT UNSIGNED NOT NULL DEFAULT 1,
    preco_unitario_congelado DECIMAL(10, 2) NOT NULL,
    subtotal DECIMAL(10, 2) NOT NULL,
    CONSTRAINT fk_itens_pedido FOREIGN KEY (pedido_id) REFERENCES pedidos(id) ON DELETE CASCADE,
    CONSTRAINT fk_itens_produto FOREIGN KEY (produto_id) REFERENCES produtos(id) ON DELETE RESTRICT
);
🐘 Como Operar este Modelo no PHP 8 + PDO
<?php
declare(strict_types=1);

// Exemplo Real: Criando um Pedido + Itens usando Transação Atômica (ACID) no PHP PDO
$pdo->beginTransaction();

try {
    $stmtPedido = $pdo->prepare('
        INSERT INTO pedidos (codigo_publico, cliente_id, endereco_entrega_id, valor_produtos, valor_frete, valor_total)
        VALUES (:uuid, :cliente, :endereco, :v_prod, :v_frete, :v_total)
    ');
    $stmtPedido->execute([
        ':uuid'     => '550e8400-e29b-41d4-a716-446655440000',
        ':cliente'  => 1,
        ':endereco' => 1,
        ':v_prod'   => '299.80',
        ':v_frete'  => '20.00',
        ':v_total'  => '319.80',
    ]);

    $pedidoId = (int) $pdo->lastInsertId();

    $stmtItem = $pdo->prepare('
        INSERT INTO itens_pedido (pedido_id, produto_id, quantidade, preco_unitario_congelado, subtotal)
        VALUES (:pedido_id, :produto_id, :qtd, :preco, :subtotal)
    ');
    $stmtItem->execute([
        ':pedido_id'  => $pedidoId,
        ':produto_id' => 10,
        ':qtd'        => 2,
        ':preco'      => '149.90',
        ':subtotal'   => '299.80',
    ]);

    $pdo->commit();
} catch (Throwable $e) {
    $pdo->rollBack();
    throw $e;
}
Nível Intermediário 5 Tabelas Relacionadas

Modelo 2: Plataforma EAD (Cursos, Matrículas N:N e Aulas)

Demonstra na prática o relacionamento Muitos-para-Muitos (N:N) entre Alunos e Cursos através da tabela associativa `matriculas`, além de controle de progresso individual por aula.

🔍 O que observar ao abrir este diagrama no DrawDB:

  • ▸Veja a constraint `UNIQUE (aluno_id, curso_id)` na tabela `matriculas`: ela impede que o mesmo aluno seja matriculado duas vezes no mesmo curso!
  • ▸Uso de `SMALLINT UNSIGNED` para ordem dos módulos e `INT UNSIGNED` para duração do vídeo em segundos.
🗄️ Script SQL DDL Completo (Import > From SQL)
CREATE TABLE instrutores (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(120) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    biografia TEXT NULL
);

CREATE TABLE cursos (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    instrutor_id INT UNSIGNED NOT NULL,
    titulo VARCHAR(160) NOT NULL,
    slug VARCHAR(180) NOT NULL UNIQUE,
    carga_horaria_horas SMALLINT UNSIGNED NOT NULL,
    preco DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
    publicado BOOLEAN NOT NULL DEFAULT 0,
    criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_cursos_instrutor FOREIGN KEY (instrutor_id) REFERENCES instrutores(id) ON DELETE RESTRICT
);

CREATE TABLE aulas (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    curso_id INT UNSIGNED NOT NULL,
    titulo VARCHAR(160) NOT NULL,
    ordem SMALLINT UNSIGNED NOT NULL DEFAULT 1,
    video_url VARCHAR(255) NOT NULL,
    duracao_segundos INT UNSIGNED NOT NULL DEFAULT 0,
    material_apoio_markdown MEDIUMTEXT NULL,
    CONSTRAINT fk_aulas_curso FOREIGN KEY (curso_id) REFERENCES cursos(id) ON DELETE CASCADE
);

CREATE TABLE alunos (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(120) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    cpf CHAR(11) NOT NULL UNIQUE,
    criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE matriculas (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    aluno_id BIGINT UNSIGNED NOT NULL,
    curso_id INT UNSIGNED NOT NULL,
    status ENUM('ativa', 'concluida', 'cancelada') NOT NULL DEFAULT 'ativa',
    nota_avaliacao TINYINT UNSIGNED NULL COMMENT 'Avaliação de 1 a 5 estrelas pelo aluno',
    matriculado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    concluido_em TIMESTAMP NULL DEFAULT NULL,
    UNIQUE KEY uq_aluno_curso (aluno_id, curso_id),
    CONSTRAINT fk_matriculas_aluno FOREIGN KEY (aluno_id) REFERENCES alunos(id) ON DELETE CASCADE,
    CONSTRAINT fk_matriculas_curso FOREIGN KEY (curso_id) REFERENCES cursos(id) ON DELETE RESTRICT
);
🐘 Como Operar este Modelo no PHP 8 + PDO
<?php
declare(strict_types=1);

// Consultando todos os cursos de um aluno com JOIN na tabela pivô `matriculas`
$alunoId = 42;

$stmt = $pdo->prepare('
    SELECT 
        c.id AS curso_id,
        c.titulo,
        i.nome AS instrutor_nome,
        m.status,
        m.matriculado_em
    FROM matriculas m
    INNER JOIN cursos c ON c.id = m.curso_id
    INNER JOIN instrutores i ON i.id = c.instrutor_id
    WHERE m.aluno_id = :aluno_id
    ORDER BY m.matriculado_em DESC
');
$stmt->bindValue(':aluno_id', $alunoId, PDO::PARAM_INT);
$stmt->execute();

$meusCursos = $stmt->fetchAll(PDO::FETCH_ASSOC);
Nível Avançado 4 Tabelas Relacionadas

Modelo 3: Clínica & Agendamento (Datas, Horários e JSON)

Focado em manipulação de datas (`DATE`), horários de expediente (`TIME`), agendamentos (`DATETIME`) e anamnese médica flexível (`JSON`).

🔍 O que observar ao abrir este diagrama no DrawDB:

  • ▸Diferença clara entre `data_nascimento DATE` (só dia), `horario_inicio TIME` (só hora) e `data_hora_inicio DATETIME` (consulta agendada).
  • ▸Uso do campo `JSON` para guardar sinais vitais e respostas dinâmicas da ficha de anamnese.
🗄️ Script SQL DDL Completo (Import > From SQL)
CREATE TABLE medicos (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(120) NOT NULL,
    crm VARCHAR(20) NOT NULL UNIQUE,
    uf_crm CHAR(2) NOT NULL,
    especialidade VARCHAR(80) NOT NULL,
    valor_consulta DECIMAL(10, 2) NOT NULL
);

CREATE TABLE agendas_medico (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    medico_id INT UNSIGNED NOT NULL,
    dia_semana TINYINT UNSIGNED NOT NULL COMMENT '1=Dom, 2=Seg ... 7=Sab',
    horario_inicio TIME NOT NULL,
    horario_fim TIME NOT NULL,
    duracao_slot_minutos TINYINT UNSIGNED NOT NULL DEFAULT 30,
    CONSTRAINT fk_agendas_medico FOREIGN KEY (medico_id) REFERENCES medicos(id) ON DELETE CASCADE
);

CREATE TABLE pacientes (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(120) NOT NULL,
    cpf CHAR(11) NOT NULL UNIQUE,
    data_nascimento DATE NOT NULL,
    tipo_sanguineo ENUM('A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-') NULL,
    telefone VARCHAR(20) NOT NULL
);

CREATE TABLE consultas (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    medico_id INT UNSIGNED NOT NULL,
    paciente_id BIGINT UNSIGNED NOT NULL,
    data_hora_inicio DATETIME NOT NULL,
    data_hora_fim DATETIME NOT NULL,
    status ENUM('agendada', 'confirmada', 'realizada', 'faltou', 'cancelada') NOT NULL DEFAULT 'agendada',
    prontuario_texto TEXT NULL,
    sinais_vitais_json JSON NULL COMMENT 'Ex: {"pressao": "120/80", "peso_kg": 72.5, "alergias": ["Dipirona"]}',
    criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uq_medico_horario (medico_id, data_hora_inicio),
    CONSTRAINT fk_consultas_medico FOREIGN KEY (medico_id) REFERENCES medicos(id) ON DELETE RESTRICT,
    CONSTRAINT fk_consultas_paciente FOREIGN KEY (paciente_id) REFERENCES pacientes(id) ON DELETE RESTRICT
);
🐘 Como Operar este Modelo no PHP 8 + PDO
<?php
declare(strict_types=1);

// Registrando prontuário e sinais vitais em JSON após a consulta
$sinaisVitais = json_encode([
    'pressao_arterial' => '120/80',
    'frequencia_cardiaca_bpm' => 72,
    'temperatura_c' => 36.6,
    'alergias_relatadas' => ['Penicilina', 'Dipirona']
], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);

$stmt = $pdo->prepare('
    UPDATE consultas
    SET status = :status,
        prontuario_texto = :prontuario,
        sinais_vitais_json = :sinais
    WHERE id = :consulta_id
');
$stmt->execute([
    ':status'      => 'realizada',
    ':prontuario'  => 'Paciente apresenta quadro estável.',
    ':sinais'      => $sinaisVitais,
    ':consulta_id' => 501
]);