PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Representando Datas, Horas e Intervalos no PostgreSQL


O PostgreSQL vem com vários tipos de dados internos relacionados a data e hora. Por que você deve usá-los sobre strings ou inteiros? O que você deve observar ao usá-los? Leia para saber mais sobre como trabalhar efetivamente com esses tipos de dados no Postgres.

Muitos tipos


O padrão SQL, o padrão ISO 8601, o catálogo integrado do PostgreSQL e a compatibilidade com versões anteriores juntos definem uma infinidade de tipos de dados e convenções sobrepostos e personalizáveis ​​relacionados a data/hora que são, na melhor das hipóteses, confusos. Essa confusão geralmente se espalha no código do driver do banco de dados, código do aplicativo, rotinas SQL e resulta em bugs sutis que são difíceis de depurar.

Por outro lado, usar tipos integrados nativos simplifica as instruções SQL e as torna muito mais fáceis de ler e escrever e, consequentemente, menos propensas a erros. Usar, digamos, inteiros (número de segundos desde a época) para representar o tempo, resulta em expressões SQL complicadas e mais código de aplicação.

Os benefícios dos tipos nativos fazem com que valha a pena definir um conjunto de regras não tão dolorosas e aplicá-las em todo o aplicativo e base de código de operações. Aqui está um desses conjuntos, que deve fornecer padrões sensatos e um ponto de partida razoável para personalização adicional, se necessário.

Tipos


Use apenas os 3 tipos a seguir (embora muitos estejam disponíveis):
  • data - uma data específica, sem hora
  • timestamptz - uma data e hora específicas com resolução de microssegundos
  • intervalo - um intervalo de tempo com resolução de microssegundos

Esses três tipos juntos devem dar suporte à maioria dos casos de uso de aplicativos. Se você não tiver necessidades específicas (como conservar o armazenamento), é altamente recomendável manter apenas esses tipos.

A data representa uma data sem hora e é bastante útil na prática (veja exemplos abaixo). O tipo timestamp é a variante que inclui as informações de fuso horário – sem as informações de fuso horário, há simplesmente muitas variáveis ​​que podem afetar a interpretação e extração do valor. Por fim, o intervalo representa intervalos de tempo tão baixos quanto um microssegundo até milhões de anos.

Strings literais


Use apenas as seguintes representações literais e use o operador cast para reduzir a verbosidade sem sacrificar a legibilidade:
  • '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 do Postgres para entrada de intervalo

Omitir o fuso horário deixa você à mercê da configuração de fuso horário do servidor Postgres, a configuração de fuso horário que pode ser definida no nível do banco de dados, no nível da sessão, no nível da função ou na string de conexão, a configuração do fuso horário da máquina cliente e mais desses fatores.

Ao consultar o código do aplicativo, converta os tipos de intervalo em uma unidade adequada (como dias ou segundos) usando o extract função e leia o valor como um valor inteiro ou real.

Configuração e outras configurações

  • Não altere as configurações padrão da configuração GUC DateStyle ,TimeZone e lc_time .
  • Não defina ou use as variáveis ​​de ambiente PGDATESTYLE e PGTZ .
  • Não use SET [SESSION|LOCAL] TIME ZONE ... .
  • Se puder, defina o fuso horário do sistema como UTC na máquina que executa o servidor Postgres, bem como todas as máquinas que executam o código do aplicativo que se conectam a ele.
  • Validar se o driver de banco de dados (como um conector JDBC ou um driver Godatabase/sql) se comporta de forma sensata enquanto o cliente está sendo executado em um fuso horário e o servidor em outro. Certifique-se de que funciona corretamente quando um TimeZone válido não UTC parâmetro está incluído na string de conexão.

Por fim, observe que todas essas são apenas diretrizes e podem ser ajustadas para atender às suas necessidades – mas certifique-se de investigar as implicações de fazê-lo primeiro.

Tipos e operadores nativos


Então, como exatamente o uso de tipos nativos ajuda a simplificar o código SQL? Aqui estão alguns exemplos.

Tipo de data


Valores da data tipo pode ser subtraído para dar o intervalo entre eles. Você também pode adicionar um número inteiro de dias a uma data específica ou adicionar um intervalo a uma data para fornecer um 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;

Os valores desses tipos são comparáveis, e é por isso que você pode ordenar a última consulta por end_date - start_date , que tem um tipo de intervalo . Aqui está outro exemplo:
-- certificates expiring within the next 7 days
SELECT name
  FROM certificates
 WHERE expiry_date BETWEEN now() AND now() + '7 days'::interval;

Tipo de carimbo de data/hora


Valores do tipo timestamptz também pode ser subtraído (para dar um intervalo ), adicionado (a um intervalo para dar outro timestamptz ) e comparado.
-- 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;

Enquanto no tópico, observe que existem 3 funções internas diferentes que retornam vários valores de “timestamp atual”. Eles realmente retornam coisas 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();

Há também aliases para essas funções:
-- 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


Os valores do tipo intervalo podem ser usados ​​como tipos de dados de coluna, podem ser comparados entre si e podem ser adicionados (e subtraídos) a carimbos de data/hora e datas. Aqui estão alguns exemplos:
-- 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;

Outras funções e construções


O PostgreSQL também vem com algumas funções e construções úteis que podem ser usadas para manipular valores desses tipos.

Extrair


A função de extração pode ser usada para recuperar uma parte especificada do valor dado, como o mês de uma data. A lista completa de partes que podem ser extraídas está documentada aqui. Aqui estão alguns exemplos úteis e não óbvios:
-- 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);

O último exemplo é particularmente útil em consultas executadas por aplicativos, pois pode ser mais fácil para os aplicativos manipular um intervalo como um valor de ponto flutuante do número de segundos/minutos/dias/etc.

Conversão de fuso horário


Há também uma função útil para expressar um timestamptz em outro fuso horário. Normalmente, isso seria feito no código do aplicativo – é mais fácil testar dessa maneira e reduz a dependência do banco de dados de fuso horário ao qual o Postgresserver fará referência. No entanto, pode ser útil às vezes:
-- 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';

Convertendo de e para texto


A função to_char (docs)pode converter datas, timestamps e intervalos em texto com base em uma string de formato – o equivalente Postgres da função C clássica 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 converter de texto para datas use to_date , e para converter texto em timestamps use to_timestamp . Observe que, se você usar os formulários listados no início deste post, poderá usar apenas os operadores de conversão.
-- 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 os documentos para obter a lista completa de padrões de string de formato.

É melhor usar essas funções para casos simples. Para análise ou formatação mais complicada, é melhor confiar no código do aplicativo, que pode (sem dúvida) ser melhor testado por unidade.

Interface com o código do aplicativo


Às vezes, não é conveniente passar valores de data/hora/tempo/intervalo para e do código do aplicativo, especialmente quando são usados ​​parâmetros vinculados. Por exemplo, geralmente é mais conveniente passar um intervalo como um número inteiro de dias (ou horas ou minutos) em vez de um formato de string. Também é mais fácil ler em um intervalo como um número inteiro/ponto flutuante de dias (ou horas, ou minutos, etc.).

O make_interval A função pode ser usada para criar um valor de intervalo de um número inteiro de valores de componentes (consulte a documentação aqui). O to_timestamp A função que vimos anteriormente tem outra forma que pode criar um valor atimestamptz a partir do Unix epoch time.
-- 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;