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
elc_time
. - Não defina ou use as variáveis de ambiente
PGDATESTYLE
ePGTZ
. - 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;