PostgreSQL
Tipos de datos y semántica
Selección de tipos de datos según semántica, precisión, rango, almacenamiento, operadores y compatibilidad con aplicaciones y drivers.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
PostgreSQL
Selección de tipos de datos según semántica, precisión, rango, almacenamiento, operadores y compatibilidad con aplicaciones y drivers.
Elegir un tipo significa modelar semántica. text, integer, numeric, timestamptz, uuid y jsonb no son intercambiables aunque todos puedan representar información “parecida”. El tipo influye en:
El criterio correcto es:
significado del dato
→ operaciones necesarias
→ rango y precisión
→ almacenamiento y acceso
→ tipo PostgreSQLGuardar todo como text parece flexible, pero desplaza errores al runtime:
El tipo convierte parte del contrato en una regla ejecutada por PostgreSQL.
2 bytes, rango aproximado de −32 mil a 32 mil. Útil para valores pequeños y verdaderamente limitados, no para IDs que pueden crecer.
4 bytes, rango aproximado de −2.1 a 2.1 mil millones. Suele bastar para contadores y cantidades moderadas.
8 bytes, rango mucho mayor. Es una elección común para IDs y contadores de largo plazo.
Ejemplo:
quantity integer NOT NULL CHECK (quantity >= 0),
order_id bigint NOT NULLEn JavaScript, bigint de PostgreSQL puede superar Number.MAX_SAFE_INTEGER. Muchos drivers lo entregan como string para evitar pérdida silenciosa.
No conviertas automáticamente todos los int8 a number sin comprobar rango.
price numeric(14,2)Representa decimales exactos dentro de precisión y escala. Es apropiado para dinero, porcentajes contractuales y cálculos que no toleran error binario.
Costes:
Usan punto flotante binario. Son adecuados para mediciones, estadísticas y cálculos científicos donde existe tolerancia.
Ejemplo:
SELECT 0.1::double precision + 0.2::double precision;El resultado puede no ser exactamente 0.3 por representación binaria.
Para dinero:
numeric
→ exactitud decimal
double precision
→ aproximación rápidaPostgreSQL tiene tipo money, pero depende de locale y tiene menos flexibilidad que numeric. En la mayoría de sistemas conviene:
amount numeric(14,2) NOT NULL,
currency_code char(3) NOT NULLEl valor monetario completo incluye moneda. 100.00 sin currency_code no tiene significado suficiente.
Para sistemas multi-moneda, evita sumar importes de monedas distintas sin conversión explícita.
Longitud variable sin límite declarado. Es el tipo general para strings.
Añade una restricción máxima. No suele ser más rápido que text.
Prácticamente equivalente a text.
Rellena espacios y aplica semántica especial. Rara vez es la mejor elección.
Usa varchar(n) cuando el límite forma parte real del contrato, no por costumbre:
country_code text NOT NULL CHECK (char_length(country_code) = 2)Un CHECK puede expresar mejor reglas complejas.
La collation define reglas de orden y comparación:
ORDER BY customer_name COLLATE "es_CO";Puede provenir del sistema operativo o ICU. Afecta:
Un cambio de versión de collation puede requerir REINDEX.
No confundas orden lingüístico con reglas de negocio. Un código o identificador puede necesitar collation binaria o comparación exacta.
Opciones:
lower(email).citext.Ejemplo:
CREATE UNIQUE INDEX users_email_lower_uidx
ON users (lower(email));La estrategia debe definir también Unicode, espacios y normalización. lower() no convierte automáticamente una dirección de email en identidad canónica perfecta.
is_active boolean NOT NULL DEFAULT trueCon NULL permitido existen tres estados: true, false y unknown. Si el dominio solo tiene dos, usa NOT NULL.
Muchos campos booleanos pueden indicar un state machine mal modelado:
is_paid
is_cancelled
is_refundedPodrían permitir combinaciones imposibles. Un status con reglas puede representar mejor el lifecycle.
Fecha de calendario sin hora.
birth_date dateHora sin fecha. Puede o no incluir zona, pero time with time zone rara vez modela correctamente horarios reales.
Fecha y hora local sin contexto de zona.
store_opens_at timestampPuede servir para un horario local asociado a una zona guardada aparte.
Representa un instante absoluto. PostgreSQL convierte la entrada a una línea temporal interna y muestra según TimeZone de sesión.
occurred_at timestamptz NOT NULL DEFAULT now()No guarda el nombre original de la zona. Si necesitas saber que el evento se programó en America/Bogota, guarda esa zona.
Requisito: una tienda abre todos los días a las 08:00 hora Bogotá.
No es un instante fijo; es una regla local recurrente:
opens_at time NOT NULL,
time_zone text NOT NULLRequisito: un pago ocurrió en un momento concreto:
paid_at timestamptz NOT NULLLa diferencia es semántica, no estética.
expires_at = created_at + interval '30 minutes'interval puede contener meses, días y tiempo. Un mes no equivale a 30 días. Define si el requisito es duración exacta o calendario.
id uuid PRIMARY KEY DEFAULT gen_random_uuid()Ventajas:
Costes:
PostgreSQL 18 incluye generación core de UUID v4 y v7; versiones anteriores pueden requerir funciones distintas o extensiones. Marca la dependencia de versión al documentar uuidv7() u otras funciones nuevas.
tags text[]Son apropiados cuando el conjunto pertenece a una sola fila y sus elementos no tienen identidad ni atributos.
Mal uso:
product_ids bigint[]Si los productos necesitan FKs, cantidades o joins, usa una tabla relacionada.
Conserva el texto original y reparsea para muchas operaciones.
Almacena representación binaria normalizada, permite operadores e índices GIN.
Úsalos para datos semiestructurados, no para esconder un esquema conocido. Los detalles viven en una nota propia.
bytea guarda bytes dentro de la base. Puede servir para objetos pequeños y transaccionalmente ligados.
Archivos grandes suelen vivir mejor en object storage:
PostgreSQL
→ metadata, ownership, checksum, URL
object storage
→ contenido binarioGuardar binarios grandes incrementa backups, WAL y replicación.
CREATE TYPE order_status AS ENUM ('pending', 'confirmed', 'cancelled');Ventajas:
Costes:
Para estados que cambian con frecuencia, text + CHECK o tabla de catálogo puede ser más flexible.
CREATE DOMAIN positive_money AS numeric(14,2)
CHECK (VALUE >= 0);Permite reutilizar tipo y constraints. Es útil cuando existe una semántica realmente compartida. Cambiar un domain afecta todas las columnas que lo usan.
valid_during tstzrange NOT NULLLos range types representan intervalos con inclusión/exclusión de límites. Permiten operadores como solapamiento y exclusion constraints.
Ejemplo:
EXCLUDE USING gist (
room_id WITH =,
reserved_during WITH &&
);Impide reservas solapadas por room.
PostGIS añade tipos geográficos y espaciales. No guardes coordenadas en strings ni inventes cálculos de distancia si necesitas semántica geoespacial real.
Las extensiones cambian requisitos de instalación, backup y compatibilidad.
SELECT '42'::integer;
SELECT CAST(total AS numeric(14,2));PostgreSQL puede convertir valores automáticamente. Esto puede:
Mantén parámetros con el tipo esperado.
Un driver puede mapear:
numeric a string.bigint a string.timestamptz a Date.jsonb a objeto.Cada mapping puede perder información. Decide explícitamente:
Tipos flotantes y numeric moderno pueden aceptar valores especiales. Evalúa si el dominio los permite y añade constraints.
'' no es NULL. Define si ambos representan estados distintos.
No conviertas automáticamente a instante sin saber la zona del usuario o negocio.
numeric(14,2) redondea escala; valida si truncar o rechazar es la regla deseada.
Dificulta enumeración, pero no reemplaza autorización.
Produce errores acumulativos y comparaciones inesperadas.
No aporta semántica ni rendimiento si el límite no existe en el dominio.
Mezcla instantes con horas locales y causa errores de zona.
Rompe orden, validación y operaciones.
Aumenta tamaño y puede empeorar locality sin necesidad.
Pierde integridad y vuelve más complejas consultas comunes.
numeric es exacto; floats son aproximados.text y varchar sin límite son equivalentes en la mayoría de casos.timestamptz conserva el instante, no la zona original.numeric suele ser mejor para dinero?timestamptz?NULL y lógica de tres valores explica cómo la ausencia modifica comparaciones, filtros, agregaciones y constraints.