kb.erickguedes.com
PostgreSQL: Banco Relacional Avançado

Instalação e Fundamentos

Aula 1 de 7

Instalação

Docker

# Iniciar PostgreSQL 16
docker run -d \
    --name postgres \
    -e POSTGRES_PASSWORD=senha123 \
    -e POSTGRES_DB=meubanco \
    -p 5432:5432 \
    -v pgdata:/var/lib/postgresql/data \
    postgres:16

# Conectar
docker exec -it postgres psql -U postgres

apt (Debian/Ubuntu)

# Adicionar repositório oficial
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt update

# Instalar PostgreSQL 16
sudo apt install postgresql-16 postgresql-client-16 -y

# Iniciar serviço
sudo systemctl start postgresql
sudo systemctl enable postgresql

psql — Cliente Interativo

# Conectar ao banco
psql -U postgres -h localhost
psql -U postgres -d meubanco

# Comandos \ (meta-comandos)
\l                    # listar databases
\dt                   # listar tabelas
\d tabela             # descrever tabela
\du                   # listar roles
\dn                   # listar schemas
\di                   # listar indexes
\df                   # listar funções
\dv                   # listar views
\dx                   # listar extensões
\c banco              # conectar a banco
\h                    # help de comandos SQL
\?                    # help de meta-comandos
\q                    # sair

# Executar arquivo SQL
\i /caminho/script.sql

# Timing
\timing on

Databases e Tablespaces

-- Criar database
CREATE DATABASE meubanco;
CREATE DATABASE meubanco OWNER app_user ENCODING 'UTF8' LC_COLLATE 'pt_BR.UTF-8';

-- Listar databases
SELECT datname, datistemplate, datallowconn FROM pg_database;

-- Tablespace
CREATE TABLESPACE fastspace LOCATION '/ssd/postgres/data';
CREATE DATABASE analitico TABLESPACE fastspace;

-- Schema
CREATE SCHEMA vendas;
CREATE TABLE vendas.clientes (id INT, nome TEXT);
SET search_path TO vendas, public;

Data Types

-- Numéricos
SMALLINT          -- 2 bytes, -32768 a 32767
INTEGER (INT)     -- 4 bytes, -2^31 a 2^31-1
BIGINT            -- 8 bytes
NUMERIC(10,2)     -- exato, 10 dígitos, 2 decimais
REAL              -- 4 bytes, precisão simples
DOUBLE PRECISION  -- 8 bytes, precisão dupla
SMALLSERIAL       -- auto-incremento 2 bytes
SERIAL            -- auto-incremento 4 bytes
BIGSERIAL         -- auto-incremento 8 bytes

-- Texto
CHAR(N)           -- fixo, preenchido com espaço
VARCHAR(N)        -- variável com limite
TEXT              -- variável sem limite

-- Booleano
BOOLEAN           -- true / false / NULL

-- JSON
JSON              -- validado, mantém formato original
JSONB             -- binário, indexável, operadores @>, ?, ||

-- Array
INT[]             -- array de inteiros
TEXT[][]          -- array multidimensional
TEXT[] ARRAY[4]   -- array com tamanho fixo

-- ENUM
CREATE TYPE status_pedido AS ENUM ('pendente', 'pago', 'cancelado');

-- UUID
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE usuarios (id UUID DEFAULT gen_random_uuid(), nome TEXT);

-- Interval
INTERVAL          -- '1 day', '2 hours', '3 months'
INTERVAL YEAR TO MONTH
INTERVAL DAY TO SECOND

-- tsvector / tsquery (full-text search)
tsvector           -- 'gato':1 'cachorro':2
tsquery            -- 'gato' & 'cachorro'

-- Geométricos
POINT, LINE, LSEG, BOX, PATH, POLYGON, CIRCLE

-- Range
INT4RANGE, INT8RANGE, NUMRANGE, TSRANGE, TSTZRANGE, DATERANGE

CREATE TABLE — Constraints

CREATE TABLE clientes (
    id          INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    nome        VARCHAR(100) NOT NULL,
    email       VARCHAR(255) UNIQUE,
    cpf         CHAR(11) UNIQUE NOT NULL,
    data_nasc   DATE,
    altura      NUMERIC(3,2) CHECK (altura > 0 AND altura < 3),
    ativo       BOOLEAN DEFAULT true,
    criado_em   TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE categorias (
    id   SERIAL PRIMARY KEY,
    nome TEXT NOT NULL
);

CREATE TABLE produtos (
    id          SERIAL PRIMARY KEY,
    nome        VARCHAR(200) NOT NULL,
    preco       NUMERIC(10,2) NOT NULL CHECK (preco >= 0),
    categoria_id INTEGER NOT NULL REFERENCES categorias(id),
    estoque     INTEGER DEFAULT 0 CHECK (estoque >= 0),
    descricao   TEXT,
    metadata    JSONB DEFAULT '{}',
    tags        TEXT[] DEFAULT '{}',
    criado_em   TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_produtos_categoria ON produtos(categoria_id);
CREATE INDEX idx_produtos_metadata ON produtos USING GIN(metadata);

SERIAL vs IDENTITY

-- SERIAL (legado, cria sequência automaticamente)
CREATE TABLE t1 (id SERIAL PRIMARY KEY, nome TEXT);

-- IDENTITY (SQL Standard, PostgreSQL 10+)
CREATE TABLE t2 (id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, nome TEXT);
-- ALWAYS: não aceita inserção manual
INSERT INTO t2 (id, nome) VALUES (999, 'teste'); -- ERRO

CREATE TABLE t3 (id INTEGER GENERATED BY DEFAULT AS IDENTITY, nome TEXT);
-- BY DEFAULT: aceita inserção manual, ajusta sequência
INSERT INTO t3 (id, nome) VALUES (100, 'manual'); -- OK

Generated Columns

CREATE TABLE pedidos (
    id          SERIAL PRIMARY KEY,
    quantidade  INTEGER NOT NULL,
    preco_unit  NUMERIC(10,2) NOT NULL,
    subtotal    NUMERIC(10,2) GENERATED ALWAYS AS (quantidade * preco_unit) STORED,
    criado_em   TIMESTAMPTZ DEFAULT NOW()
);

INSERT INTO pedidos (quantidade, preco_unit) VALUES (3, 29.90);
SELECT subtotal FROM pedidos; -- 89.70

Lab: Schema de E-commerce

-- 01-schema-ecommerce.sql
CREATE DATABASE ecommerce;

\c ecommerce

-- Extensões
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- ENUMs
CREATE TYPE status_pedido AS ENUM ('pendente', 'confirmado', 'enviado', 'entregue', 'cancelado');
CREATE TYPE tipo_endereco AS ENUM ('cobranca', 'entrega');

-- Tabelas
CREATE TABLE clientes (
    id          UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    nome        VARCHAR(150) NOT NULL,
    email       VARCHAR(255) UNIQUE NOT NULL,
    cpf         CHAR(11) UNIQUE NOT NULL,
    telefone    VARCHAR(20),
    data_cadastro TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE enderecos (
    id            UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    cliente_id    UUID NOT NULL REFERENCES clientes(id) ON DELETE CASCADE,
    tipo          tipo_endereco NOT NULL DEFAULT 'entrega',
    logradouro    VARCHAR(255) NOT NULL,
    numero        VARCHAR(10),
    complemento   VARCHAR(100),
    bairro        VARCHAR(100),
    cidade        VARCHAR(100) NOT NULL,
    estado        CHAR(2) NOT NULL,
    cep           CHAR(8) NOT NULL,
    principal     BOOLEAN DEFAULT false
);

CREATE TABLE categorias (
    id          SERIAL PRIMARY KEY,
    nome        VARCHAR(100) NOT NULL,
    slug        VARCHAR(100) UNIQUE NOT NULL,
    descricao   TEXT,
    ativo       BOOLEAN DEFAULT true
);

CREATE TABLE produtos (
    id              UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    categoria_id    INTEGER REFERENCES categorias(id),
    nome            VARCHAR(200) NOT NULL,
    slug            VARCHAR(200) UNIQUE NOT NULL,
    descricao       TEXT,
    preco           NUMERIC(10,2) NOT NULL CHECK (preco >= 0),
    estoque         INTEGER NOT NULL DEFAULT 0 CHECK (estoque >= 0),
    ativo           BOOLEAN DEFAULT true,
    criado_em       TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE pedidos (
    id              UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    cliente_id      UUID NOT NULL REFERENCES clientes(id),
    status          status_pedido NOT NULL DEFAULT 'pendente',
    total           NUMERIC(10,2) NOT NULL DEFAULT 0,
    criado_em       TIMESTAMPTZ DEFAULT NOW(),
    atualizado_em   TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE itens_pedido (
    id          UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    pedido_id   UUID NOT NULL REFERENCES pedidos(id) ON DELETE CASCADE,
    produto_id  UUID NOT NULL REFERENCES produtos(id),
    quantidade  INTEGER NOT NULL CHECK (quantidade > 0),
    preco_unit  NUMERIC(10,2) NOT NULL,
    subtotal    NUMERIC(10,2) GENERATED ALWAYS AS (quantidade * preco_unit) STORED
);

-- Índices
CREATE INDEX idx_pedidos_cliente ON pedidos(cliente_id);
CREATE INDEX idx_pedidos_status ON pedidos(status);
CREATE INDEX idx_itens_pedido ON itens_pedido(pedido_id);
CREATE INDEX idx_produtos_categoria ON produtos(categoria_id);
CREATE INDEX idx_produtos_slug ON produtos(slug);

-- Ver schema
\dt
\d clientes
\d pedidos

PostgreSQL oferece tipos ricos (JSONB, UUID, array, range), constraints poderosas (CHECK, UNIQUE, FK) e colunas generated. Identity é superior a SERIAL. Use JSONB ao invés de JSON para performance.