sqlpostgresqldatetimetimestamp

CURRENT_TIMESTAMP y LOCALTIMESTAMP en SQL

En que se diferencian CURRENT_TIMESTAMP y LOCALTIMESTAMP, por que se congelan en la transaccion y cuando usar clock_timestamp().

2 min de lecturaReferencesql · postgresql · datetime · timestamp · audit

Cuando necesitas registrar "cuando paso esto", casi todo el mundo recurre a CURRENT_TIMESTAMP. Pero PostgreSQL trae toda una familia de funciones de tiempo, y las diferencias deciden que tipo obtienes y como de "reciente" es ese instante. Vamos a lo concreto.

Los dos protagonistas: con zona y sin zona

CURRENT_TIMESTAMP devuelve timestamp with time zone (alias timestamptz), mientras que LOCALTIMESTAMP devuelve timestamp without time zone. No es algo cosmetico: el primero guarda un instante absoluto, el segundo guarda la hora "de reloj de pared" sin zona asociada.

SELECT
  CURRENT_TIMESTAMP AS with_zone,    -- timestamptz
  LOCALTIMESTAMP    AS without_zone; -- timestamp

La conclusion practica: en una aplicacion global casi siempre quieres timestamptz. Una columna users.created_at deberia ser timestamptz, para que un usuario en Lisboa y otro en Moscu registren el mismo instante absoluto y no dos lecturas "locales" distintas.

CREATE TABLE users (
  id         bigserial PRIMARY KEY,
  email      text NOT NULL UNIQUE,
  name       text,
  country    text,
  created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Hora de la transaccion, no de la sentencia

El rasgo clave: CURRENT_TIMESTAMP y LOCALTIMESTAMP devuelven el momento en que empezo la transaccion, y ese valor queda congelado hasta el commit. Dentro de una misma transaccion cada llamada devuelve un valor identico, aunque pasen segundos entre ellas.

BEGIN;
SELECT CURRENT_TIMESTAMP;     -- p. ej. 12:00:00.000
-- ... consulta pesada de 3 segundos ...
SELECT CURRENT_TIMESTAMP;     -- SIGUE siendo 12:00:00.000
COMMIT;

Esto es muy util: si una sola transaccion inserta un pedido y una fila de auditoria, ambas reciben exactamente el mismo created_at, sin desfase de fracciones de segundo.

BEGIN;
INSERT INTO orders (user_id, amount, status, created_at)
VALUES (42, 99.90, 'paid', CURRENT_TIMESTAMP);

INSERT INTO order_audit (order_id, event, at)
VALUES (currval('orders_id_seq'), 'created', CURRENT_TIMESTAMP);
COMMIT;

Cuando necesitas el "ahora real": clock_timestamp()

A veces la congelacion estorba, por ejemplo al perfilar o al marcar filas una a una dentro de una sola consulta. PostgreSQL ofrece una gradacion:

  • transaction_timestamp() es sinonimo de CURRENT_TIMESTAMP, el momento de inicio de la transaccion.
  • statement_timestamp() es el momento en que empezo la sentencia actual.
  • clock_timestamp() es el "ahora" real del reloj, cambia en cada llamada, incluso dentro de un mismo SELECT.
SELECT
  clock_timestamp() AS row_time
FROM generate_series(1, 3);
-- tres valores de tiempo DISTINTOS

Regla practica: para auditoria y datos de negocio usa CURRENT_TIMESTAMP (gana la consistencia). Para medir cuanto tardan los pasos dentro de una funcion, usa clock_timestamp().

Trampa: now() en PostgreSQL es exactamente transaction_timestamp(), asi que tambien esta congelado. Si un bucle de PL/pgSQL espera que now() "avance", no lo hara. Usa clock_timestamp() en su lugar.

Valores DEFAULT para columnas de auditoria

El escenario mas comun es autocompletar columnas de creacion y actualizacion. created_at se resuelve con un DEFAULT, mientras que updated_at necesita un trigger, porque un DEFAULT solo se dispara en el INSERT.

CREATE TABLE employees (
  id         bigserial PRIMARY KEY,
  name       text NOT NULL,
  manager_id bigint REFERENCES employees(id),
  dept       text,
  salary     numeric(12,2),
  created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE OR REPLACE FUNCTION touch_updated_at()
RETURNS trigger AS $$
BEGIN
  NEW.updated_at := CURRENT_TIMESTAMP;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_touch
BEFORE UPDATE ON employees
FOR EACH ROW EXECUTE FUNCTION touch_updated_at();

Diferencias en MySQL y ClickHouse

  • MySQL: CURRENT_TIMESTAMP devuelve un DATETIME (sin zona) y, a diferencia de PostgreSQL, refleja la hora de la sentencia y no de la transaccion. A cambio ofrece la comoda sintaxis created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP mas updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP para un updated_at automatico sin trigger.
  • ClickHouse: usa now() (tipo DateTime) o now64() para precision de milisegundos; LOCALTIMESTAMP se admite como alias. No existe la congelacion por transaccion de PostgreSQL, porque el modelo de ejecucion es totalmente distinto.

En resumen: CURRENT_TIMESTAMP para tiempo absoluto con zona, LOCALTIMESTAMP cuando no necesitas zona, ambos estables dentro de la transaccion, y clock_timestamp() como herramienta cuando necesitas un reloj real que avanza.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador