sql >> Base de Datos >  >> RDS >> PostgreSQL

Representación de fechas, horas e intervalos en PostgreSQL

PostgreSQL viene con un montón de tipos de datos integrados relacionados con la fecha y la hora. ¿Por qué debería usarlos sobre cadenas o enteros? ¿Qué debe tener en cuenta al usarlos? Lea para obtener más información sobre cómo trabajar de manera efectiva con estos tipos de datos en Postgres.

Un montón de tipos

El estándar SQL, el estándar ISO 8601, el catálogo integrado de PostgreSQL y la compatibilidad con versiones anteriores juntos definen una plétora de tipos de datos y convenciones superpuestos y personalizables relacionados con la fecha/hora que, en el mejor de los casos, es confuso. Esta confusión generalmente se extiende al código del controlador de la base de datos, el código de la aplicación, las rutinas SQL y da como resultado errores sutiles que son difíciles de depurar.

Por otro lado, el uso de tipos incorporados nativos simplifica las instrucciones SQL y las hace mucho más fáciles de leer y escribir y, en consecuencia, menos propensas a errores. y más código de aplicación.

Los beneficios de los tipos nativos hacen que valga la pena definir un conjunto de reglas no tan dolorosas y aplicarlas en toda la aplicación y la base de código de operaciones. Aquí hay uno de esos conjuntos, que debería proporcionar valores predeterminados sensatos y un punto de partida razonable para una mayor personalización si es necesario.

Tipos

Use solo los siguientes 3 tipos (aunque hay muchos disponibles):

  • fecha - una fecha específica, sin hora
  • marca de tiempo - una fecha y hora específicas con una resolución de microsegundos
  • intervalo - un intervalo de tiempo con resolución de microsegundos

Estos tres tipos juntos deberían ser compatibles con la mayoría de los casos de uso de aplicaciones. Si no tiene necesidades específicas (como conservar almacenamiento), se recomienda enfáticamente ceñirse solo a estos tipos.

La fecha representa una fecha sin tiempo, y es bastante útil en la práctica (ver ejemplos a continuación). El tipo de marca de tiempo es la variante que incluye la información de la zona horaria; sin la información de la zona horaria, simplemente hay demasiadas variables que pueden afectar la interpretación y extracción del valor. Finalmente, el intervalo representa intervalos de tiempo desde tan solo un microsegundo hasta millones de años.

Cadenas Literales

Use solo las siguientes representaciones literales y use el operador de conversión para reducir la verbosidad sin sacrificar la legibilidad:

  • '2012-12-25'::date -ISO 8601
  • '2012-12-25 13:04:05.123-08:00'::timestamptz -ISO 8601
  • '1 month 3 days'::interval - Formato tradicional de Postgres para entrada de intervalos

Omitir la zona horaria lo deja a merced de la configuración de la zona horaria del servidor de Postgres, la configuración de la zona horaria que se puede establecer a nivel de base de datos, nivel de sesión, nivel de rol o en la cadena de conexión, la configuración de zona horaria de la máquina cliente y más factores de este tipo.

Mientras consulta desde el código de la aplicación, convierta los tipos de intervalo a una unidad adecuada (como días o segundos) usando el extract función y leer el valor como un valor entero o real.

Configuración y otros ajustes

  • No cambie la configuración predeterminada para la configuración de GUC DateStyle ,TimeZone y lc_time .
  • No configure ni use las variables de entorno PGDATESTYLE y PGTZ .
  • No utilice SET [SESSION|LOCAL] TIME ZONE ... .
  • Si puede, establezca la zona horaria del sistema en UTC en la máquina que ejecuta el servidor de Postgres, así como en todas las máquinas que ejecutan código de aplicación que se conectan a él.
  • Valide que el controlador de su base de datos (como un conector JDBC o un controlador Godatabase/sql) se comporte de manera sensata mientras el cliente se ejecuta en una zona horaria y el servidor en otra. Asegúrese de que funcione correctamente cuando una TimeZone válida no UTC El parámetro está incluido en la cadena de conexión.

Finalmente, tenga en cuenta que todas estas son solo pautas y pueden modificarse para satisfacer sus necesidades, pero asegúrese de investigar las implicaciones de hacerlo primero.

Tipos y operadores nativos

Entonces, ¿cómo ayuda exactamente el uso de tipos nativos a simplificar el código SQL? Aquí hay algunos ejemplos.

Tipo de fecha

Valores de la fecha tipo se puede restar para dar el intervalo entre ellos. También puede agregar un número entero de días a una fecha de partículas, o agregar un intervalo a una fecha para dar una timestamptz :

-- 10 days from now (outputs 2020-07-26)
SELECT now()::date + 10;
 
-- 10 days from now (outputs 2020-07-26 04:44:30.568847+00)
SELECT now() + '10 days'::interval;

-- days till christmas (outputs 161 days 14:06:26.759466)
SELECT '2020-12-25'::date - now();

-- the 10 longest courses
  SELECT name, end_date - start_date AS duration
    FROM courses
ORDER BY end_date - start_date DESC
   LIMIT 10;

Los valores de estos tipos son comparables, por lo que podría ordenar la última consulta por end_date - start_date , que tiene un tipo de intervalo . Aquí hay otro ejemplo:

-- certificates expiring within the next 7 days
SELECT name
  FROM certificates
 WHERE expiry_date BETWEEN now() AND now() + '7 days'::interval;

Tipo de marca de tiempo

Valores de tipo timestamptz también se puede restar (para dar un intervalo ), añadido (a un intervalo para dar otro timestamptz ) y comparar.

-- difference of timestamps gives an interval
SELECT password_last_modified - created_at AS password_age
  FROM users;

-- can also use the age() function
SELECT age(password_last_modified, created_at) AS password_age
  FROM users;

Mientras que en el tema, tenga en cuenta que hay 3 funciones integradas diferentes que devuelven varios valores de "marca de tiempo actual". De hecho, devuelven cosas diferentes:

-- transaction_timestamp() returns the timestampsz of the start of current transaction
-- outputs 2020-07-16 05:09:32.677409+00
SELECT transaction_timestamp();

-- statement_timestamp() returns the timestamptz of the start of the current statement
SELECT statement_timestamp();

-- clock_timestamp() returns the timestamptz of the system clock
SELECT clock_timestamp();

También hay alias para estas funciones:

-- now() actually returns the start of the current transaction, which means it
-- does not change during the transaction
SELECT now(), transaction_timestamp();

-- transaction timestamp is also returned by these keyword-style constructs
SELECT CURRENT_DATE, CURRENT_TIMESTAMP, transaction_timestamp();

Tipos de intervalo

Los valores con tipo de intervalo se pueden usar como tipos de datos de columna, se pueden comparar entre sí y se pueden agregar (y restar) a las marcas de tiempo y fechas. Estos son algunos ejemplos:

-- interval-typed values can be stored and compared 
  SELECT num
    FROM passports
   WHERE valid_for > '10 years'::interval
ORDER BY valid_for DESC;

-- you can multiply them by numbers (outputs 4 years)
SELECT 4 * '1 year'::interval;

-- you can divide them by numbers (outputs 3 mons)
SELECT '1 year'::interval / 4;

-- you can add and subtract them (outputs 1 year 1 mon 6 days)
SELECT '1 year'::interval + '1.2 months'::interval;

Otras funciones y construcciones

PostgreSQL también viene con algunas funciones y construcciones útiles que se pueden usar para manipular valores de estos tipos.

Extraer

La función de extracción se puede usar para recuperar una parte específica del valor dado, como el mes de una fecha. La lista completa de partes que se pueden extraer está documentada aquí. Aquí hay algunos ejemplos útiles y no obvios:

-- years from an interval (outputs 2)
SELECT extract(YEARS FROM '1.5 years 6 months'::interval);

-- day of the week (0=Sun .. 6=Sat) from timestamp (outputs 4)
SELECT extract(DOW FROM now());

-- day of the week (1=Mon .. 7=Sun) from timestamp (outputs 4)
SELECT extract(ISODOW FROM now());

-- convert interval to seconds (outputs 86400)
SELECT extract(EPOCH FROM '1 day'::interval);

El último ejemplo es particularmente útil en consultas ejecutadas por aplicaciones, ya que puede ser más fácil para las aplicaciones manejar un intervalo como un valor de coma flotante de la cantidad de segundos/minutos/días/etc.

Conversión de zona horaria

También hay una función útil para expresar un timestamptz en otra zona horaria. Por lo general, esto se haría en el código de la aplicación:es más fácil de probar de esa manera y reduce la dependencia de la base de datos de la zona horaria a la que se referirá el servidor de Postgres. Sin embargo, puede ser útil en ocasiones:

-- convert timestamps to a different time zone
SELECT timezone('Europe/Helsinki', now());

-- same as before, but this one is a SQL standard
SELECT now() AT TIME ZONE 'Europe/Helsinki';

Convertir a y desde texto

La función to_char (docs) puede convertir fechas, marcas de tiempo e intervalos en texto en función de una cadena de formato:el equivalente de Postgres de la función clásica de C strftime .

-- outputs Thu, 16th July
SELECT to_char(now(), 'Dy, DDth Month');

-- outputs 01 06 00 12 00 00
SELECT to_char('1.5 years'::interval, 'YY MM DD HH MI SS');

Para convertir de texto a fechas use to_date , y para convertir texto en marcas de tiempo, use to_timestamp . Tenga en cuenta que si usa los formularios que se enumeran al principio de esta publicación, puede usar los operadores de conversión en su lugar.

-- outputs 2000-12-25 15:42:50+00
SELECT to_timestamp('2000.12.25.15.42.50', 'YYYY.MM.DD.HH24.MI.SS');

-- outputs 2000-12-25
SELECT to_date('2000.12.25.15.42.50', 'YYYY.MM.DD');

Consulte los documentos para ver la lista completa de patrones de cadena de formato.

Es mejor usar estas funciones para casos simples. Para un análisis o formateo más complicado, es mejor confiar en el código de la aplicación, que puede (posiblemente) ser mejor probado por unidad.

Interfaz con el código de la aplicación

A veces no es conveniente pasar valores de fecha/marca de tiempo/intervalo hacia y desde el código de la aplicación, especialmente cuando se usan parámetros enlazados. Por ejemplo, normalmente es más conveniente pasar un intervalo como un número entero de días (u horas o minutos) en lugar de un formato de cadena. También es más fácil de leer en un intervalo como un número de días (u horas, minutos, etc.) entero/coma flotante.

El make_interval La función se puede usar para crear un valor de intervalo a partir de un número entero de valores de componentes (ver documentos aquí). El to_timestamp La función que vimos anteriormente tiene otra forma que puede crear un valor atimestamptz a partir de la época de Unix.

-- pass the interval as number of days from the application code
SELECT name FROM courses WHERE duration <= make_interval(days => $1);

-- pass timestamptz as unix epoch (number of seconds from 1-Jan-1970)
SELECT id FROM events WHERE logged_at >= to_timestamp($1);

-- return interval as number of days (with a fractional part)
SELECT extract(EPOCH FROM duration) / 60 / 60 / 24;