PHP

PostgreSQL com PHP Já leu

8 min de leitura

PostgreSQL com PHP
O MySQL é o banco de dados mais comum no ecossistema PHP, mas o PostgreSQL é o mais poderoso. Enquanto o MySQL foi por muito tempo otimizado para leitura rápida e simplicidade, o PostgreSQL nasceu com foco em conformidad

O MySQL é o banco de dados mais comum no ecossistema PHP, mas o PostgreSQL é o mais poderoso. Enquanto o MySQL foi por muito tempo otimizado para leitura rápida e simplicidade, o PostgreSQL nasceu com foco em conformidade com o padrão SQL, integridade de dados e recursos avançados que outros bancos levaram anos para implementar — ou nunca implementaram.

Neste artigo vamos conectar o PHP ao PostgreSQL usando PDO, explorar os tipos de dados nativos do Postgres que não existem no MySQL, trabalhar com JSONB para dados semiestruturados e entender quando faz sentido escolher o PostgreSQL em vez do MySQL.


Por que PostgreSQL?

O PostgreSQL, frequentemente chamado apenas de "Postgres", é um sistema de banco de dados relacional de código aberto com mais de 35 anos de desenvolvimento ativo. Algumas vantagens concretas sobre o MySQL:

Tipos de dados ricos. O Postgres suporta arrays nativos, tipos geométricos (ponto, linha, polígono), ranges (intervalo de datas, de inteiros), tipos de rede (inet, cidr), e o JSONB — uma versão indexável e eficiente do JSON. O MySQL tem JSON desde 2015, mas a implementação do Postgres é significativamente mais poderosa.

Conformidade com SQL. O Postgres implementa o padrão SQL de forma muito mais completa, incluindo Common Table Expressions (CTEs) recursivos, window functions avançadas, e suporte a transações DDL (você pode fazer rollback de um CREATE TABLE, por exemplo).

Extensibilidade. O Postgres permite criar seus próprios tipos de dados, operadores e funções. Extensions como PostGIS (para dados geográficos), pgvector (para embeddings de IA), e TimescaleDB (para séries temporais) transformam o Postgres em um banco especializado quando necessário.

Confiabilidade. O Postgres tem reputação sólida de nunca perder dados. Seu sistema MVCC (Multi-Version Concurrency Control) é robusto e bem testado.


Instalação e configuração

No Linux (Ubuntu/Debian):

sudo apt install postgresql postgresql-contrib php-pgsql
sudo systemctl start postgresql

Criar banco e usuário:

sudo -u postgres psql

-- Dentro do psql:
CREATE DATABASE loja;
CREATE USER app_user WITH PASSWORD 'senha123';
GRANT ALL PRIVILEGES ON DATABASE loja TO app_user;
\q

Verificar conexão:

psql -h localhost -U app_user -d loja

Conectando com PDO

A beleza do PDO é que a mudança de MySQL para PostgreSQL exige apenas trocar o DSN. O resto do código permanece idêntico:

<?php
declare(strict_types=1);

// MySQL seria: mysql:host=localhost;dbname=loja;charset=utf8mb4
$dsn = 'pgsql:host=localhost;port=5432;dbname=loja';

try {
    $pdo = new PDO(
        dsn:      $dsn,
        username: 'app_user',
        password: 'senha123',
        options:  [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ]
    );

    echo "Conectado ao PostgreSQL com sucesso!\n";

} catch (PDOException $e) {
    throw new RuntimeException(
        "Falha na conexão: " . $e->getMessage()
    );
}

Note o PDO::ATTR_EMULATE_PREPARES => false. No PostgreSQL, isso é especialmente importante: garante que o PDO use prepared statements reais do servidor, o que melhora performance e segurança. O Postgres tem um cache interno de planos de execução para prepared statements, e desativar a emulação permite que esse cache seja utilizado.


Criando o schema

<?php

$pdo->exec("
    CREATE TABLE IF NOT EXISTS produtos (
        id          SERIAL PRIMARY KEY,
        nome        VARCHAR(200)    NOT NULL,
        preco       NUMERIC(10, 2)  NOT NULL CHECK (preco >= 0),
        estoque     INTEGER         NOT NULL DEFAULT 0,
        ativo       BOOLEAN         NOT NULL DEFAULT TRUE,
        tags        TEXT[],
        metadata    JSONB,
        criado_em   TIMESTAMPTZ     NOT NULL DEFAULT NOW()
    )
");

Observe alguns tipos específicos do PostgreSQL: - SERIAL: auto-incremento (equivalente a AUTO_INCREMENT do MySQL). No Postgres moderno, prefere-se GENERATED ALWAYS AS IDENTITY, mas SERIAL ainda é amplamente usado. - NUMERIC(10, 2): tipo exato para dinheiro, sem erros de ponto flutuante. - BOOLEAN: tipo nativo no Postgres (no MySQL é TINYINT(1)). - TEXT[]: array de strings. - JSONB: JSON armazenado em formato binário, indexável. - TIMESTAMPTZ: timestamp com timezone (melhor prática para aplicações multi-fuso).


Arrays nativos

<?php

// Inserindo um produto com array de tags
$stmt = $pdo->prepare("
    INSERT INTO produtos (nome, preco, estoque, tags)
    VALUES (:nome, :preco, :estoque, :tags)
    RETURNING id, criado_em
");

// Arrays são passados como strings no formato do Postgres
$stmt->execute([
    ':nome'    => 'Teclado Mecânico',
    ':preco'   => '299.90',
    ':estoque' => 15,
    ':tags'    => '{periférico,teclado,mecânico}',
]);

$resultado = $stmt->fetch();
echo "Produto criado com ID: {$resultado['id']}\n";
echo "Criado em: {$resultado['criado_em']}\n";

// Note o RETURNING — permite retornar dados da linha inserida
// sem fazer um SELECT separado. O MySQL não tem isso.

// Consultando e decodificando o array
$stmt = $pdo->prepare("SELECT nome, tags FROM produtos WHERE id = :id");
$stmt->execute([':id' => $resultado['id']]);
$produto = $stmt->fetch();

// O PDO retorna o array como string: {periférico,teclado,mecânico}
// Precisamos fazer o parse manualmente
$tags = str_getcsv(trim($produto['tags'], '{}'));
print_r($tags); // ['periférico', 'teclado', 'mecânico']

// Buscar produtos que contenham uma tag específica
$stmt = $pdo->prepare("
    SELECT nome, tags
    FROM produtos
    WHERE :tag = ANY(tags)
");
$stmt->execute([':tag' => 'mecânico']);

JSONB — JSON indexável

O JSONB é um dos recursos mais poderosos do PostgreSQL. Diferente do tipo JSON simples (que armazena o texto original), o JSONB armazena em formato binário parseado, permitindo indexação e consultas eficientes:

<?php

// Inserindo com JSONB
$metadata = json_encode([
    'fabricante' => 'Keychron',
    'switch'     => 'Brown',
    'bluetooth'  => true,
    'dimensoes'  => ['largura' => 36, 'altura' => 13, 'profundidade' => 12],
    'cores'      => ['preto', 'cinza'],
]);

$stmt = $pdo->prepare("
    UPDATE produtos
    SET metadata = :metadata::jsonb
    WHERE id = :id
");
$stmt->execute([':metadata' => $metadata, ':id' => 1]);

// Consultando campos específicos do JSONB com operador ->
$stmt = $pdo->query("
    SELECT
        nome,
        metadata->>'fabricante'        AS fabricante,
        metadata->>'switch'            AS switch,
        (metadata->>'bluetooth')::bool AS bluetooth,
        metadata->'dimensoes'->>'largura' AS largura
    FROM produtos
    WHERE metadata->>'fabricante' = 'Keychron'
      AND (metadata->>'bluetooth')::bool = true
");

while ($row = $stmt->fetch()) {
    echo "{$row['nome']} — {$row['fabricante']}, switch {$row['switch']}\n";
}

// Criando índice GIN para consultas JSONB eficientes
$pdo->exec("CREATE INDEX idx_produtos_metadata ON produtos USING gin(metadata)");

// Com o índice, consultas usando @> (contém) são muito eficientes:
$stmt = $pdo->prepare("
    SELECT nome FROM produtos
    WHERE metadata @> :filtro::jsonb
");
$stmt->execute([':filtro' => json_encode(['bluetooth' => true])]);

Transações e recursos avançados

<?php

// CTEs (Common Table Expressions) — disponíveis no MySQL 8+
// mas muito mais comuns e idiomáticas no PostgreSQL
$stmt = $pdo->query("
    WITH
    produtos_ativos AS (
        SELECT id, nome, preco, estoque
        FROM produtos
        WHERE ativo = true AND estoque > 0
    ),
    estatisticas AS (
        SELECT
            COUNT(*)            AS total,
            AVG(preco)          AS preco_medio,
            SUM(estoque)        AS estoque_total
        FROM produtos_ativos
    )
    SELECT p.nome, p.preco, e.preco_medio
    FROM produtos_ativos p, estatisticas e
    ORDER BY p.preco DESC
    LIMIT 10
");

// UPSERT nativo com ON CONFLICT
$stmt = $pdo->prepare("
    INSERT INTO produtos (nome, preco, estoque)
    VALUES (:nome, :preco, :estoque)
    ON CONFLICT (nome)
    DO UPDATE SET
        preco    = EXCLUDED.preco,
        estoque  = EXCLUDED.estoque,
        ativo    = true
    RETURNING id, (xmax = 0) AS inserido
");
// xmax = 0 indica que foi um INSERT; xmax != 0 indica UPDATE

Quando usar PostgreSQL vs MySQL

Critério PostgreSQL MySQL
Consultas complexas ✅ Excelente ✅ Bom
Dados geográficos (PostGIS) ✅ Referência ❌ Limitado
JSONB/documentos híbridos ✅ Excelente ⚠️ Funcional
Performance de leitura simples ✅ Ótimo ✅ Ótimo
Ecossistema WordPress/legado ⚠️ Menor ✅ Dominante
Hospedagem compartilhada ⚠️ Menos disponível ✅ Ubíquo
Conformidade SQL ✅ Excelente ⚠️ Parcial

Exercício da semana

  1. Instale o PostgreSQL localmente e conecte via PDO. Crie um banco biblioteca com tabela livros (id, titulo, autores TEXT[], generos TEXT[], metadata JSONB, publicado_em DATE).

  2. Insira 5 livros com arrays de autores e metadados JSON (editora, ISBN, numero_paginas). Use RETURNING para capturar o id gerado.

  3. Escreva uma query que use ANY() para buscar livros de um determinado gênero. Depois, use @> para buscar pelo JSONB (ex: todos da editora "Companhia das Letras").

  4. Implemente um UPSERT: se o livro (pelo ISBN no JSONB) já existir, atualize o preço; caso contrário, insira.

  5. Desafio: crie um índice GIN no campo generos (array) e no campo metadata (JSONB). Use EXPLAIN ANALYZE para comparar o custo das queries com e sem índice.


Referências

  • Documentação oficial do PostgreSQL: https://www.postgresql.org/docs/
  • PHP Manual — PDO_PGSQL: https://www.php.net/manual/pt_BR/ref.pdo-pgsql.php
  • PostgreSQL vs MySQL — comparação técnica detalhada: https://www.postgresql.org/about/featurematrix/
  • JSONB no PostgreSQL — guia completo: https://www.postgresql.org/docs/current/datatype-json.html
Comentários

Mais em PHP

PDO Avançado, Prepared Statements e Padrão Repository
PDO Avançado, Prepared Statements e Padrão Repository

PDO (PHP Data Objects) é a camada de abstração de banco de dados nativa do PH...

O que é PHP e por que ele ainda importa
O que é PHP e por que ele ainda importa

Quando voc&ecirc; acessa um site, preenche um formul&aacute;rio, faz login em...

SOLID na Prática
SOLID na Prática

SOLID é um acrônimo para cinco princípios de design orientado a objetos formu...