PostgreSQL: Restrições e Integridade de Dados no PostgreSQL
Última atualização: 2026-08-26
1. O Que Você Vai Aprender
- PRIMARY KEY (coluna única / composta)
- FOREIGN KEY (CASCADE / SET NULL / SET DEFAULT / NO ACTION / RESTRICT)
- Restrição UNIQUE
- Restrição NOT NULL
- Restrição CHECK (recurso do PostgreSQL: pode referenciar outras colunas na mesma linha)
- Restrição EXCLUSION (recurso do PostgreSQL: ex., intervalos de tempo não sobrepostos)
- Restrição adiada DEFERRABLE (recurso do PostgreSQL)
- Nomenclatura e gestão de restrições
2. A História
Charlie é um arquiteto de banco de dados em uma plataforma SaaS. Ele precisa impor as seguintes regras de negócio no nível do banco de dados:
- O valor de cada pedido deve ser > 0 (CHECK)
- Os intervalos de tempo das reservas de sala de reunião não devem se sobrepor (EXCLUSION)
- Excluir um cliente deve limpar automaticamente seus pedidos, mas pedidos críticos devem ser protegidos (estratégia de cascata FOREIGN KEY)
- Ao importar dados em lote, referências entre linhas podem violar temporariamente restrições e só devem ser verificadas após a conclusão da importação (DEFERRABLE)
Charlie usa restrições em vez de validação na camada de aplicação, tornando o banco de dados a última linha de defesa para integridade de dados.
3. Conceito: PRIMARY KEY
(1) O Papel da Chave Primária
A chave primária identifica unicamente cada linha em uma tabela, combinando UNIQUE + NOT NULL. Uma tabela pode ter apenas uma chave primária.
| Propriedade | Descrição |
|---|---|
| Unicidade | Valores duplicados não permitidos |
| Não nulo | NULL não permitido |
| Índice automático | O PostgreSQL cria automaticamente um índice único B-Tree para a chave primária |
| Um por tabela | Apenas uma PRIMARY KEY pode ser definida |
▶ Exemplo: Chave Primária de Coluna Única
CREATE TABLE customers (
customer_id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE
);
Output:
CREATE TABLE
▶ Exemplo: Chave Primária Composta
CREATE TABLE order_items (
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
PRIMARY KEY (order_id, product_id)
);
Output:
CREATE TABLE
(2) Comparação de Tipos de Coluna de Chave Primária
| Tipo | Armazenamento | Intervalo | Caso de uso |
|---|---|---|---|
| SERIAL / BIGSERIAL | 4/8 bytes | 2B / 9,2×10¹⁸ | Maioria das tabelas de negócio |
| UUID | 16 bytes | Globalmente único | Sistemas distribuídos |
| Chave natural (ex., email) | Variável | — | Raramente usado; risco de mudança de negócio |
| Chave primária composta | Múltiplas colunas | — | Tabelas de junção, tabelas ponte muitos-para-muitos |
▶ Exemplo: Chave Primária UUID
CREATE TABLE global_events (
event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_name VARCHAR(200) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
Output:
CREATE TABLE
4. Conceito: FOREIGN KEY
(1) Chave Estrangeira e as Cinco Estratégias de Cascata
Uma chave estrangeira garante integridade referencial: o valor da coluna de referência na tabela filha deve existir na chave primária/única da tabela pai.
| Estratégia | Comportamento ON DELETE | Comportamento ON UPDATE | Cenário típico |
|---|---|---|---|
| CASCADE | Excluir em cascata linhas filhas | Atualizar em cascata linhas filhas | Itens de pedido excluídos com o pedido |
| SET NULL | Definir linha filha como NULL | Definir linha filha como NULL | Associação opcional; exclusão sem impacto |
| SET DEFAULT | Definir linha filha como padrão | Definir linha filha como padrão | Raramente usado |
| RESTRICT | Rejeitar exclusão (imediato) | Rejeitar atualização (imediato) | Proteger dados críticos |
| NO ACTION | Rejeitar exclusão (adiável) | Rejeitar atualização (adiável) | Comportamento padrão |
▶ Exemplo: CASCADE Exclusão em Cascata
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL
REFERENCES customers(customer_id) ON DELETE CASCADE,
total_amount DECIMAL(12,2) NOT NULL
);
Output:
CREATE TABLE
Quando um cliente é excluído, todos os seus pedidos são excluídos automaticamente.
▶ Exemplo: RESTRICT Protege Dados Críticos
CREATE TABLE invoices (
invoice_id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL
REFERENCES orders(order_id) ON DELETE RESTRICT,
invoice_date DATE NOT NULL
);
Output:
CREATE TABLE
Se um pedido já tem uma fatura, a exclusão do pedido é rejeitada.
(2) Comparação CASCADE vs RESTRICT
| Dimensão | CASCADE | RESTRICT |
|---|---|---|
| Excluir linha pai | Linhas filhas também excluídas | Exclusão rejeitada |
| Segurança dos dados | Conveniente mas arriscado | Seguro mas precisa de limpeza manual |
| Caso de uso | Dados auxiliares | Dados críticos / financeiros |
▶ Exemplo: Cascata de Múltiplos Níveis
CREATE TABLE customers (
customer_id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
customer_id BIGINT REFERENCES customers(customer_id) ON DELETE CASCADE
);
CREATE TABLE order_items (
item_id BIGSERIAL PRIMARY KEY,
order_id BIGINT REFERENCES orders(order_id) ON DELETE CASCADE,
product_id BIGINT NOT NULL
);
Output:
CREATE TABLE
Excluir cliente → excluir automaticamente pedidos → excluir automaticamente order_items, uma cascata de três níveis.
▶ Exemplo: SET NULL
CREATE TABLE reviews (
review_id BIGSERIAL PRIMARY KEY,
product_id BIGINT REFERENCES products(product_id) ON DELETE SET NULL,
content TEXT NOT NULL
);
Output:
CREATE TABLE
Após um produto ser excluído, o product_id da avaliação é definido como NULL, mas a avaliação é mantida.
5. Conceito: UNIQUE e NOT NULL
(1) Restrição UNIQUE
UNIQUE garante que valores de coluna (ou combinações de colunas) não sejam duplicados e permite NULL (múltiplos NULLs não contam como duplicatas).
| Dimensão | PRIMARY KEY | UNIQUE |
|---|---|---|
| Quantidade por tabela | Apenas uma | Múltiplas permitidas |
| NULL permitido | Não permitido | Permitido (múltiplos NULLs não conflitam) |
| Índice automático | Sim | Sim |
| Semântica | Identifica a linha | Garante unicidade |
▶ Exemplo: Restrição UNIQUE de Múltiplas Colunas
CREATE TABLE user_accounts (
user_id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE,
phone VARCHAR(20),
UNIQUE (email, phone)
);
Output:
CREATE TABLE
email único por si só + (email, phone) único como combinação.
(2) Restrição NOT NULL
NOT NULL proíbe uma coluna de armazenar valores NULL; é a restrição de integridade mais básica.
| Escrita | Descrição |
|---|---|
col TYPE NOT NULL |
Restrição em nível de coluna |
CONSTRAINT nn_col CHECK (col IS NOT NULL) |
Forma equivalente |
▶ Exemplo: Combinação NOT NULL
CREATE TABLE products (
product_id BIGSERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
description TEXT
);
Output:
CREATE TABLE
description permite NULL; as outras colunas-chave não.
6. Conceito: Restrição CHECK
(1) Básico da Restrição CHECK
Uma restrição CHECK exige que um valor de coluna satisfaça uma expressão booleana dada. O CHECK do PostgreSQL pode referenciar outras colunas na mesma linha (o padrão SQL também suporta, mas muitos bancos de dados não).
| Propriedade | Descrição |
|---|---|
| Verificação em nível de linha | Só pode referenciar colunas da linha atual |
| Recurso do PostgreSQL | Pode referenciar outras linhas (via subconsulta, com limites) |
| CHECK em nível de tabela | Pode restringir múltiplas colunas de uma vez |
| NO INHERIT | Não propagado para tabelas filhas (recurso do PG) |
▶ Exemplo: Valor Deve Ser Positivo
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
total_amount DECIMAL(12,2) NOT NULL CHECK (total_amount > 0),
discount DECIMAL(12,2) DEFAULT 0 CHECK (discount >= 0)
);
Output:
CREATE TABLE
▶ Exemplo: Restrição CHECK de Múltiplas Colunas
CREATE TABLE campaigns (
campaign_id BIGSERIAL PRIMARY KEY,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
budget DECIMAL(12,2) NOT NULL CHECK (budget > 0),
CONSTRAINT chk_date_range CHECK (end_date >= start_date),
CONSTRAINT chk_budget_limit CHECK (budget <= 1000000)
);
Output:
CREATE TABLE
(2) Padrões Comuns de CHECK
| Regra de negócio | Expressão CHECK |
|---|---|
| Valor positivo | CHECK (amount > 0) |
| Intervalo de desconto | CHECK (discount BETWEEN 0 AND 1) |
| Ordem de datas | CHECK (end_date >= start_date) |
| Valores de enum | CHECK (status IN ('active', 'inactive', 'pending')) |
| Comprimento da string | CHECK (LENGTH(phone) >= 10) |
| Porcentagem | CHECK (rate >= 0 AND rate <= 100) |
▶ Exemplo: Restrições Nomeadas para Facilitar a Gestão
CREATE TABLE subscriptions (
sub_id BIGSERIAL PRIMARY KEY,
plan VARCHAR(50) NOT NULL,
price DECIMAL(10,2) NOT NULL,
CONSTRAINT chk_price_positive CHECK (price > 0),
CONSTRAINT chk_plan_valid CHECK (plan IN ('free', 'basic', 'pro', 'enterprise'))
);
Output:
CREATE TABLE
▶ Exemplo: Adicionar um CHECK a uma Tabela Existente
ALTER TABLE orders
ADD CONSTRAINT chk_amount_positive CHECK (total_amount > 0);
Output:
-- instrução SQL executada com sucesso
7. Conceito: Restrição EXCLUSION
(1) Como EXCLUSION Funciona
Uma restrição EXCLUSION garante: se duas linhas são "iguais" em colunas especificadas (comparadas com o operador =), elas não se "sobrepõem" na dimensão especificada (comparada com um operador de sobreposição). Este é um recurso específico do PostgreSQL.
| Cenário | Restrição | Operador |
|---|---|---|
| Intervalos de tempo não se sobrepõem | Os horários do mesmo recurso não podem cruzar | =, && |
| Sem assentos duplicados | Os números de assento da mesma sessão não se repetem | =, = (equivalente a UNIQUE) |
▶ Exemplo: Reservas de Sala de Reunião Não se Sobrepoem no Tempo
CREATE TABLE room_bookings (
booking_id BIGSERIAL PRIMARY KEY,
room_id INT NOT NULL,
time_range TSTZRANGE NOT NULL,
booked_by VARCHAR(100) NOT NULL,
CONSTRAINT excl_room_no_overlap
EXCLUDE USING GiST (room_id WITH =, time_range WITH &&)
);
Output:
CREATE TABLE
▶ Exemplo: Inserção Conflitante
INSERT INTO room_bookings (room_id, time_range, booked_by)
VALUES (1, '[2025-07-13 09:00, 2025-07-13 11:00)', 'Alice');
INSERT INTO room_bookings (room_id, time_range, booked_by)
VALUES (1, '[2025-07-13 10:00, 2025-07-13 12:00)', 'Bob');
ERROR: conflicting key value violates exclusion constraint "excl_room_no_overlap"
DETAIL: Key (room_id, time_range)=(1, [2025-07-13 10:00,2025-07-13 12:00))
conflicts with existing key (room_id, time_range)=(1, [2025-07-13 09:00,2025-07-13 11:00)).
(2) Comparação EXCLUSION vs UNIQUE
| Dimensão | UNIQUE | EXCLUSION |
|---|---|---|
| Operador de comparação | Apenas = |
Personalizado (=, &&, <->, etc.) |
| Sobreposição de intervalo | Não suportado | Suportado |
| Tipo de índice | B-Tree | GiST / SP-GiST |
| Flexibilidade | Baixa | Alta |
| Caso de uso | Unicidade de valor discreto | Intervalos não sobrepostos, restrições de distância |
▶ Exemplo: Intervalos de Preço Não se Sobrepoem
CREATE TABLE discount_tiers (
tier_id BIGSERIAL PRIMARY KEY,
product_id INT NOT NULL,
price_range NUMRANGE NOT NULL,
discount DECIMAL(5,4) NOT NULL,
CONSTRAINT excl_price_no_overlap
EXCLUDE USING GiST (product_id WITH =, price_range WITH &&)
);
Output:
CREATE TABLE
8. Conceito: Restrição Adiada DEFERRABLE
(1) Mecanismo de Restrição Adiada
Por padrão, as restrições são verificadas imediatamente após cada instrução. Uma restrição DEFERRABLE permite que a verificação seja adiada até o commit da transação, resolvendo o problema de referência circular em operações em lote.
| Modo | Momento da verificação | Sintaxe |
|---|---|---|
| IMMEDIATE | Após cada instrução | Comportamento padrão |
| DEFERRABLE INITIALLY IMMEDIATE | Após cada instrução (comutável) | DEFERRABLE INITIALLY IMMEDIATE |
| DEFERRABLE INITIALLY DEFERRED | No commit da transação | DEFERRABLE INITIALLY DEFERRED |
▶ Exemplo: Solução para Referências Circulares
CREATE TABLE departments (
dept_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
manager_id INT
);
ALTER TABLE departments
ADD CONSTRAINT fk_dept_manager
FOREIGN KEY (manager_id) REFERENCES departments(dept_id)
DEFERRABLE INITIALLY DEFERRED;
Output:
CREATE TABLE
▶ Exemplo: Operações Dentro de uma Transação
BEGIN;
INSERT INTO departments (dept_id, name, manager_id)
VALUES (1, 'Engineering', 2);
INSERT INTO departments (dept_id, name, manager_id)
VALUES (2, 'QA', 1);
COMMIT;
Output:
INSERT 0 1
Executar o primeiro INSERT sozinho falharia (manager_id=2 ainda não existe), mas DEFERRABLE adia a verificação até COMMIT, então uma vez que ambos INSERTs são concluídos, a validação passa.
(2) Alternar Dinamicamente o Momento da Verificação
▶ Exemplo: SET CONSTRAINTS
BEGIN;
SET CONSTRAINTS fk_dept_manager DEFERRED;
INSERT INTO departments (dept_id, name, manager_id) VALUES (3, 'Sales', 4);
INSERT INTO departments (dept_id, name, manager_id) VALUES (4, 'Marketing', 3);
SET CONSTRAINTS fk_dept_manager IMMEDIATE;
COMMIT;
Output:
INSERT 0 1
(3) Comparação IMMEDIATE vs DEFERRED
| Dimensão | IMMEDIATE | DEFERRED |
|---|---|---|
| Momento da verificação | Após cada instrução | No commit da transação |
| Desempenho | Mais rápido (feedback imediato) | Ligeiramente mais lento (verificação em lote) |
| Referência circular | Não resolve | Solucionável |
| Risco | Baixo | Violação descoberta apenas no fim da transação |
| Caso de uso | Maioria das restrições | Referências circulares, importação em lote |
9. Conceito: Nomenclatura e Gestão de Restrições
(1) Convenção de Nomenclatura de Restrições
Uma boa nomenclatura facilita localizar problemas e executar operações.
| Tipo de restrição | Prefixo recomendado | Exemplo |
|---|---|---|
| PRIMARY KEY | pk_ | pk_orders |
| FOREIGN KEY | fk_ | fk_orders_customer |
| UNIQUE | uq_ | uq_users_email |
| CHECK | chk_ | chk_amount_positive |
| EXCLUSION | excl_ | excl_room_no_overlap |
▶ Exemplo: Nomear Explicitamente Todas as Restrições
CREATE TABLE orders (
order_id BIGSERIAL CONSTRAINT pk_orders PRIMARY KEY,
customer_id BIGINT NOT NULL CONSTRAINT fk_orders_customer
REFERENCES customers(customer_id) ON DELETE CASCADE,
total_amount DECIMAL(12,2) CONSTRAINT chk_amount_positive CHECK (total_amount > 0),
status VARCHAR(20) CONSTRAINT chk_status_valid
CHECK (status IN ('pending', 'shipped', 'delivered', 'cancelled')),
order_date TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Output:
CREATE TABLE
(2) Operações de Gestão de Restrições
| Operação | Sintaxe |
|---|---|
| Adicionar restrição | ALTER TABLE t ADD CONSTRAINT name CHECK (...) |
| Remover restrição | ALTER TABLE t DROP CONSTRAINT name |
| Visualizar restrições | SELECT * FROM pg_constraint WHERE conrelid = 't'::regclass |
| Desabilitar triggers (desabilitar restrições indiretamente) | ALTER TABLE t DISABLE TRIGGER ALL |
▶ Exemplo: Visualizar Todas as Restrições de uma Tabela
SELECT
conname AS constraint_name,
contype AS type,
pg_get_constraintdef(oid) AS definition
FROM pg_constraint
WHERE conrelid = 'orders'::regclass;
constraint_name | type | definition
-----------------------+------+--------------------------------------------
pk_orders | p | PRIMARY KEY (order_id)
fk_orders_customer | f | FOREIGN KEY (customer_id) REFERENCES ...
chk_amount_positive | c | CHECK ((total_amount > 0))
chk_status_valid | c | CHECK ((status = ANY (ARRAY['pending'::...
10. Exemplo Abrangente
Esquema de restrições da plataforma SaaS do Charlie — cobrindo chave primária, cascata de chave estrangeira, CHECK, EXCLUSION, DEFERRABLE:
CREATE TABLE customers (
customer_id BIGSERIAL CONSTRAINT pk_customers PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) CONSTRAINT uq_customers_email UNIQUE,
credit_limit DECIMAL(12,2) DEFAULT 0
CONSTRAINT chk_credit_non_negative CHECK (credit_limit >= 0)
);
CREATE TABLE orders (
order_id BIGSERIAL CONSTRAINT pk_orders PRIMARY KEY,
customer_id BIGINT NOT NULL CONSTRAINT fk_orders_customer
REFERENCES customers(customer_id) ON DELETE RESTRICT,
total_amount DECIMAL(12,2) NOT NULL
CONSTRAINT chk_amount_positive CHECK (total_amount > 0),
status VARCHAR(20) DEFAULT 'pending'
CONSTRAINT chk_status CHECK (status IN ('pending','shipped','delivered','cancelled')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE room_bookings (
booking_id BIGSERIAL CONSTRAINT pk_bookings PRIMARY KEY,
room_id INT NOT NULL,
time_range TSTZRANGE NOT NULL,
booked_by VARCHAR(100) NOT NULL,
CONSTRAINT excl_room_no_overlap
EXCLUDE USING GiST (room_id WITH =, time_range WITH &&)
);
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer_deferred
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE RESTRICT
DEFERRABLE INITIALLY IMMEDIATE;
11. Fluxo de Execução
Onde a verificação de restrições se encaixa na execução de uma instrução DML:
flowchart TD
A[INSERT/UPDATE/DELETE] --> B{Verificação NOT NULL}
B -->|passa| C{Verificação CHECK}
B -->|falha| Z[Erro, reverter]
C -->|passa| D{Verificação UNIQUE}
C -->|falha| Z
D -->|passa| E{Verificação PRIMARY KEY}
D -->|falha| Z
E -->|passa| F{Verificação EXCLUSION}
E -->|falha| Z
F -->|passa| G{Momento da restrição?}
F -->|falha| Z
G -->|IMMEDIATE| H[Verificar FK imediatamente]
G -->|DEFERRED| I[Adiar verificação FK para COMMIT]
H -->|passa| J[Instrução concluída]
I --> K[COMMIT]
K --> L[Verificar todas FK DEFERRED]
L -->|passa| M[Transação confirmada]
L -->|falha| Z
style Z fill:#ffccbc
style J fill:#c8e6c9
style M fill:#c8e6c9
❓ Perguntas Frequentes
P: Qual a diferença entre RESTRICT e NO ACTION? R: Funcionalmente quase idênticos — ambos rejeitam exclusões/atualizações. A diferença é que NO ACTION pode ser combinado com DEFERRABLE para adiar a verificação, enquanto RESTRICT sempre verifica imediatamente.
P: Uma restrição UNIQUE permite múltiplos NULLs? R: No PostgreSQL, uma coluna UNIQUE permite múltiplos NULLs, porque NULL != NULL. Se você precisa que NULL também seja único, adicione uma restrição NOT NULL.
P: Uma restrição CHECK pode referenciar linhas em outras tabelas? R: Você pode escrever uma subconsulta, mas o CHECK do PostgreSQL só garante a restrição para a linha atual — ele não garante consistência entre linhas (outra sessão pode modificar os dados referenciados). Para consistência entre tabelas, use uma chave estrangeira.
P: Qual índice uma restrição EXCLUSION precisa? R: Precisa de um índice GiST ou SP-GiST para suporte. O PostgreSQL cria automaticamente o índice correspondente para uma restrição EXCLUSION.
P: DEFERRABLE pode ser usado para restrições CHECK? R: Não. DEFERRABLE se aplica apenas a restrições FOREIGN KEY e UNIQUE. CHECK e NOT NULL são sempre verificados imediatamente.
P: Uma exclusão CASCADE grande afeta o desempenho? R: Sim. Exclusões em cascata executam linha por linha e podem gerar bloqueios pesados e I/O. Para grandes exclusões em lote, é recomendado excluir manualmente os dados da tabela filha primeiro, depois excluir a linha pai, para evitar transações longas.
📖 Resumo
- PRIMARY KEY = UNIQUE + NOT NULL, uma por tabela, cria automaticamente um índice
- FOREIGN KEY tem cinco estratégias de cascata: CASCADE / SET NULL / SET DEFAULT / RESTRICT / NO ACTION
- CASCADE é conveniente mas arriscado; RESTRICT é seguro mas precisa de limpeza manual
- UNIQUE permite múltiplos NULLs; PRIMARY KEY não permite NULL algum
- A restrição CHECK pode referenciar outras colunas na mesma linha, ideal para validação de lógica multicoluna
- A restrição EXCLUSION (recurso do PG) garante intervalos não sobrepostos, ex., reservas de sala de reunião
- A restrição adiada DEFERRABLE (recurso do PG) resolve referências circulares e problemas de importação em lote
- Nomear explicitamente as restrições auxilia na gestão e operações; os prefixos pk_/fk_/uq_/chk_/excl_ são recomendados
📝 Exercícios
-
⭐ Crie as tabelas
customerseorders, defina as chaves primária e estrangeira (ON DELETE CASCADE) e adicione uma restrição CHECK garantindo que o valor do pedido > 0. -
⭐ Crie uma tabela
productscom uma coluna SKU UNIQUE e uma restrição CHECK garantindo preço > 0 e desconto <= preço. -
⭐⭐ Use uma restrição EXCLUSION para criar uma tabela
room_bookings, garantindo que os intervalos de tempo da mesma sala de reunião não se sobreponham e teste uma inserção conflitante. -
⭐⭐ Crie duas tabelas com referência mútua (employees referencia departments e o manager_id de departments referencia employees) e use DEFERRABLE para resolver a referência circular.
-
⭐⭐⭐ Projete um modelo de dados completo de tenant SaaS com: uma tabela tenants, uma tabela users (cascata de chave estrangeira), uma tabela subscriptions (CHECK validando datas e valores), uma tabela resource-booking (EXCLUSION prevenindo sobreposição de tempo) e nomeie todas as restrições.