Hoja de referencia rápida de PostgreSQL: Una guía esencial para desarrolladores
Referencia rápida de PostgreSQL
Una referencia rápida para el trabajo diario PostgreSQL conexiones, sintaxis SQL, metacomandos de psql, rendimiento, JSON, funciones de ventana y más.
Si deseas un repaso conciso de SQL que sirva para múltiples bases de datos antes de profundizar en los detalles específicos de Postgres, esta hoja de trucos de SQL con los comandos más útiles es un compañero práctico.

Conexión y conceptos básicos
# Conectar
psql -h HOST -p 5432 -U USUARIO -d BD
psql $DATABASE_URL
# Dentro de psql
\conninfo -- mostrar conexión
\l[+] -- listar bases de datos
\c BD -- conectar a BD
\dt[+] [esquema.]patrón -- listar tablas
\dv[+] -- listar vistas
\ds[+] -- listar secuencias
\df[+] [patrón] -- listar funciones
\d[S+] nombre -- describir tabla/vista/secuencia
\dn[+] -- listar esquemas
\du[+] -- listar roles
\timing -- activar/desactivar temporizador de consultas
\x -- visualización expandida
\e -- editar búfer en $EDITOR
\i archivo.sql -- ejecutar archivo
\copy ... -- COPY del lado del cliente
\! comando_shell -- 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) - Array:
tipo[]ej.text[] - Red:
inet,cidr,macaddr - Geométrico:
point,line,polygon, etc.
Verificar la versión de PostgreSQL
SELECT version();
Versión del servidor PostgreSQL:
pg_config --version
Versión del cliente 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;
Esenciales de consultas
SELECT * FROM app.users ORDER BY created_at DESC LIMIT 20 OFFSET 40; -- paginación
-- Filtrado
SELECT * FROM app.users WHERE email ILIKE '%@example.%' AND active;
-- Agregados 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;
-- CTEs (Common Table Expressions)
WITH recent AS (
SELECT * FROM app.orders WHERE created_at > now() - interval '30 days'
)
SELECT count(*) FROM recent;
-- CTE Recursiva
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);
Arrays
-- Membresía y contención
SELECT * FROM app.users WHERE 'vip' = ANY(tags);
SELECT * FROM app.users WHERE tags @> ARRAY['beta'];
-- Añadir elemento
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
-- 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;
-- 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] nombre_tabla;
ANALYZE nombre_tabla;
REINDEX TABLE nombre_tabla;
Para una visión más amplia de las herramientas esenciales para desarrolladores, incluido Docker, Git y PostgreSQL, consulta Herramientas para desarrolladores: La guía completa de flujos de trabajo 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 trigram
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 búsqueda de texto completo de PostgreSQL vs Elasticsearch cubre las compensaciones en profundidad.
Configuraciones útiles de psql
\pset pager off -- desactivar paginador
\pset null '∅'
\pset format aligned -- otros: 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 (aproximado)
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 USUARIO -d BD -F c -f db.dump # formato personalizado
pg_restore -h HOST -U USUARIO -d NUEVA_BD -j 4 db.dump
# SQL plano
pg_dump -h HOST -U USUARIO -d BD > dump.sql
psql -h HOST -U USUARIO -d BD -f dump.sql
Para herramientas de gestión de bases de datos con interfaz gráfica, consulta DBeaver vs Beekeeper - Herramientas de gestión de bases de datos SQL y Instalar DBeaver en linux - guía.
Replicación (nivel alto)
- Los archivos WAL se transmiten desde el primario → standby
- Configuraciones clave:
wal_level,max_wal_senders,hot_standby,primary_conninfo - Herramientas:
pg_basebackup,standby.signal(PG ≥12)
Errores comunes y consejos
- Siempre usa
jsonb, nojson, para índices y operadores. - Prefiere
timestamptz(con zona horaria). DISTINCT ONes oro de Postgres para “top‑N por grupo”.- Usa
GENERATED ALWAYS AS IDENTITYen lugar deserial. - Evita
SELECT *en consultas de producción. - Crea índices para filtros selectivos y claves de unión.
- Mide con
EXPLAIN (ANALYZE)antes de optimizar.
Detalles específicos de 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;