Acertijos de PostgreSQL: Referencia rápida para desarrolladores

Referencia rápida de PostgreSQL

Índice

Una referencia rápida para el uso diario PostgreSQL en el trabajo: conexiones, sintaxis SQL, metacomandos de psql, rendimiento, JSON, funciones de ventana y mucho más.

Si deseas un repaso conciso entre bases de datos antes de profundizar en las especificidades de Postgres, esta hoja de trucos de SQL con los comandos más útiles es un compañero útil.

postgresql logo

Conexión y conceptos básicos

# Conectar
psql -h HOST -p 5432 -U USER -d DB
psql $DATABASE_URL

# Dentro de psql
\conninfo             -- mostrar conexión
\l[+]                 -- listar bases de datos
\c DB                 -- conectar a DB
\dt[+] [schema.]pat   -- listar tablas
\dv[+]                -- listar vistas
\ds[+]                -- listar secuencias
\df[+] [pat]          -- listar funciones
\d[S+] name           -- describir tabla/vista/secuencia
\dn[+]                -- listar esquemas
\du[+]                -- listar roles
\timing               -- alternar tiempo de ejecución de consultas
\x                    -- vista expandida
\e                    -- editar búfer en $EDITOR
\i file.sql           -- ejecutar archivo
\copy ...             -- COPY del lado del cliente
\! shell_cmd          -- ejecutar comando de shell

Tipos de datos (comunes)

  • Numéricos: smallint, integer, bigint, decimal(p,s), numeric, real, double precision, serial, bigserial
  • Texto: text, varchar(n), char(n)
  • Booleano: boolean
  • Temporal: timestamp [with/without time zone], date, time, interval
  • UUID: uuid
  • JSON: json, jsonb (preferido)
  • Arreglo: type[] p. ej. text[]
  • Red: inet, cidr, macaddr
  • Geométrico: point, line, polygon, etc.

Verificar la versión de PostgreSQL

SELECT version();

Versión del servidor de PostgreSQL:

pg_config --version

Versión del cliente de PostgreSQL:

psql --version

DDL (Crear / Alterar)

-- Crear esquema y tabla
CREATE SCHEMA IF NOT EXISTS app;
CREATE TABLE app.users (
  id           bigserial PRIMARY KEY,
  email        text NOT NULL UNIQUE,
  name         text,
  active       boolean NOT NULL DEFAULT true,
  created_at   timestamptz NOT NULL DEFAULT now(),
  profile      jsonb,
  tags         text[]
);

-- Alterar
ALTER TABLE app.users ADD COLUMN last_login timestamptz;
ALTER TABLE app.users ALTER COLUMN name SET NOT NULL;
ALTER TABLE app.users DROP COLUMN tags;

-- Restricciones
ALTER TABLE app.users ADD CONSTRAINT email_lower_uk UNIQUE (lower(email));

-- Índices
CREATE INDEX ON app.users (email);
CREATE UNIQUE INDEX CONCURRENTLY users_email_uidx ON app.users (lower(email));
CREATE INDEX users_profile_gin ON app.users USING gin (profile);
CREATE INDEX users_created_at_idx ON app.users (created_at DESC);

DML (Insertar / Actualizar / Upsert / Eliminar)

INSERT INTO app.users (email, name) VALUES
  ('a@x.com','A'), ('b@x.com','B')
RETURNING id;

-- Upsert (ON CONFLICT)
INSERT INTO app.users (email, name)
VALUES ('a@x.com','Alice')
ON CONFLICT (email)
DO UPDATE SET name = EXCLUDED.name, updated_at = now();

UPDATE app.users SET active = false WHERE last_login < now() - interval '1 year';
DELETE FROM app.users WHERE active = false AND last_login IS NULL;

Fundamentos de consultas

SELECT * FROM app.users ORDER BY created_at DESC LIMIT 20 OFFSET 40; -- paginación

-- Filtros
SELECT * FROM app.users WHERE email ILIKE '%@example.%' AND active;

-- Agregaciones y GROUP BY
SELECT active, count(*) AS n
FROM app.users
GROUP BY active
HAVING count(*) > 10;

-- Uniones (Joins)
SELECT o.id, u.email, o.total
FROM app.orders o
JOIN app.users u ON u.id = o.user_id
LEFT JOIN app.discounts d ON d.id = o.discount_id;

-- DISTINCT ON (específico de Postgres)
SELECT DISTINCT ON (user_id) user_id, status, created_at
FROM app.events
ORDER BY user_id, created_at DESC;

-- CTE (Comunas Tablas Temporal)
WITH recent AS (
  SELECT * FROM app.orders WHERE created_at > now() - interval '30 days'
)
SELECT count(*) FROM recent;

-- CTE Recursivo
WITH RECURSIVE t(n) AS (
  SELECT 1
  UNION ALL
  SELECT n+1 FROM t WHERE n < 10
)
SELECT sum(n) FROM t;

Funciones de ventana

SELECT
  user_id,
  created_at,
  sum(total)      OVER (PARTITION BY user_id ORDER BY created_at) AS running_total,
  row_number()    OVER (PARTITION BY user_id ORDER BY created_at) AS rn,
  lag(total, 1)   OVER (PARTITION BY user_id ORDER BY created_at) AS prev_total
FROM app.orders;

JSON / JSONB

-- Extracción
SELECT profile->>'company' AS company FROM app.users;
SELECT profile->'address'->>'city' FROM app.users;

-- Índice para consultas jsonb
CREATE INDEX users_profile_company_gin ON app.users USING gin ((profile->>'company'));

-- Existencia / contención
SELECT * FROM app.users WHERE profile ? 'company';              -- la clave existe
SELECT * FROM app.users WHERE profile @> '{"role":"admin"}'; -- contiene

-- Actualizar jsonb
UPDATE app.users
SET profile = jsonb_set(COALESCE(profile,'{}'::jsonb), '{prefs,theme}', '"dark"', true);

Arreglos

-- membresía y contención
SELECT * FROM app.users WHERE 'vip' = ANY(tags);
SELECT * FROM app.users WHERE tags @> ARRAY['beta'];

-- agregar
UPDATE app.users SET tags = array_distinct(tags || ARRAY['vip']);

Tiempo y fechas

SELECT now() AT TIME ZONE 'Australia/Melbourne';
SELECT date_trunc('day', created_at) AS d, count(*)
FROM app.users GROUP BY d ORDER BY d;

-- Intervalos
SELECT now() - interval '7 days';

Transacciones y bloqueos

BEGIN;
UPDATE app.accounts SET balance = balance - 100 WHERE id = 1;
UPDATE app.accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- o ROLLBACK

-- Nivel de aislamiento
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- Verificar bloqueos
SELECT * FROM pg_locks l JOIN pg_stat_activity a USING (pid);

Roles y permisos

-- Crear rol/usuario
CREATE ROLE app_user LOGIN PASSWORD 'secret';
GRANT USAGE ON SCHEMA app TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;

Importar / Exportar

-- Del lado del servidor (requiere superusuario o permisos adecuados)
COPY app.users TO '/tmp/users.csv' CSV HEADER;
COPY app.users(email,name) FROM '/tmp/users.csv' CSV HEADER;

-- Del lado del cliente (psql)
\copy app.users TO 'users.csv' CSV HEADER
\copy app.users(email,name) FROM 'users.csv' CSV HEADER

Rendimiento y observabilidad

EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT ...;  -- tiempo de ejecución real

-- Vistas de estadísticas
SELECT * FROM pg_stat_user_tables;
SELECT * FROM pg_stat_statements ORDER BY total_time DESC LIMIT 20; -- requiere extensión

-- Mantenimiento
VACUUM [FULL] [VERBOSE] table_name;
ANALYZE table_name;
REINDEX TABLE table_name;

Para una visión general más amplia de las herramientas de desarrollo esenciales, incluyendo Docker, Git y PostgreSQL, consulte Developer Tools: La guía completa para flujos de trabajo de desarrollo modernos.

Habilita las extensiones según sea necesario:

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE EXTENSION IF NOT EXISTS btree_gin;
CREATE EXTENSION IF NOT EXISTS pg_trgm; -- búsqueda de trigramas

Búsqueda de texto completo (rápida)

ALTER TABLE app.docs ADD COLUMN tsv tsvector;
UPDATE app.docs SET tsv = to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,''));
CREATE INDEX docs_tsv_idx ON app.docs USING gin(tsv);
SELECT id FROM app.docs WHERE tsv @@ plainto_tsquery('english', 'quick brown fox');

Si estás decidiendo cuándo la búsqueda nativa de Postgres es suficiente y cuándo ejecutar una pila de búsqueda separada, esta comparación de PostgreSQL full text search vs Elasticsearch cubre las compensaciones en profundidad.


Configuraciones útiles de psql

\pset pager off       -- desactivar paginador
\pset null '∅'
\pset format aligned  -- otras: unaligned, csv
\set ON_ERROR_STOP on
\timing on

Consultas de catálogo útiles

-- Tamaño de tabla
SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) AS size
FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC;

-- Inflación de índices (aprox)
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes ORDER BY idx_scan ASC NULLS FIRST LIMIT 20;

Copia de seguridad / Restauración

# Volcado lógico
pg_dump -h HOST -U USER -d DB -F c -f db.dump        # formato personalizado
pg_restore -h HOST -U USER -d NEW_DB -j 4 db.dump

# SQL plano
pg_dump -h HOST -U USER -d DB > dump.sql
psql -h HOST -U USER -d DB -f dump.sql

Para herramientas de gestión de bases de datos con interfaz gráfica, consulte DBeaver vs Beekeeper - Herramientas de Gestión de Bases de Datos SQL y Instalar DBeaver en linux - howto.

Para un ejemplo práctico de pg_dump/psql usado junto con copias de seguridad de repositorios y archivos, consulte Copia de seguridad y restauración del servidor Gitea.


Replicación (alto nivel)

  • Los archivos WAL fluyen desde el primariostandby
  • Configuraciones clave: wal_level, max_wal_senders, hot_standby, primary_conninfo
  • Herramientas: pg_basebackup, standby.signal (PG ≥12)

Errores comunes y consejos

  • Usa siempre jsonb, no json, para índices y operadores.
  • Prevalénciate en timestamptz (con información de zona horaria).
  • DISTINCT ON es oro de Postgres para “los N superiores por grupo”.
  • Usa GENERATED ALWAYS AS IDENTITY en lugar de serial.
  • Evita SELECT * en consultas de producción.
  • Crea índices para filtros selectivos y claves de unión.
  • Mide con EXPLAIN (ANALYZE) antes de optimizar.

Mejoras específicas por versión (≥v12+)

-- Identidad generada
CREATE TABLE t (id bigINT GENERATED ALWAYS AS IDENTITY, ...);

-- UPSERT con índice parcial
CREATE UNIQUE INDEX ON t (key) WHERE is_active;

Enlaces útiles

Suscribirse

Recibe nuevas publicaciones sobre sistemas, infraestructura e ingeniería de IA.