Eu tenho uma tabela (no PostgreSQL 9.4) que se parece com isso:
CREATE TABLE dates_ranges (kind int, start_date date, end_date date);
INSERT INTO dates_ranges VALUES
(1, '2018-01-01', '2018-01-31'),
(1, '2018-01-01', '2018-01-05'),
(1, '2018-01-03', '2018-01-06'),
(2, '2018-01-01', '2018-01-01'),
(2, '2018-01-01', '2018-01-02'),
(3, '2018-01-02', '2018-01-08'),
(3, '2018-01-05', '2018-01-10');
Agora, quero calcular para as datas especificadas e para todo tipo, em quantas linhas de dates_ranges
cada data caem. Zeros podem ser omitidos.
Resultado desejado:
+-------+------------+----+
| kind | as_of_date | n |
+-------+------------+----+
| 1 | 2018-01-01 | 2 |
| 1 | 2018-01-02 | 2 |
| 1 | 2018-01-03 | 3 |
| 2 | 2018-01-01 | 2 |
| 2 | 2018-01-02 | 1 |
| 3 | 2018-01-02 | 1 |
| 3 | 2018-01-03 | 1 |
+-------+------------+----+
Eu vim com duas soluções, uma com LEFT JOIN
eGROUP BY
SELECT
kind, as_of_date, COUNT(*) n
FROM
(SELECT d::date AS as_of_date FROM generate_series('2018-01-01'::timestamp, '2018-01-03'::timestamp, '1 day') d) dates
LEFT JOIN
dates_ranges ON dates.as_of_date BETWEEN start_date AND end_date
GROUP BY 1,2 ORDER BY 1,2
e um com LATERAL
, que é um pouco mais rápido:
SELECT
kind, as_of_date, n
FROM
(SELECT d::date AS as_of_date FROM generate_series('2018-01-01'::timestamp, '2018-01-03'::timestamp, '1 day') d) dates,
LATERAL
(SELECT kind, COUNT(*) AS n FROM dates_ranges WHERE dates.as_of_date BETWEEN start_date AND end_date GROUP BY kind) ss
ORDER BY kind, as_of_date
Gostaria de saber se existe alguma maneira melhor de escrever esta consulta? E como incluir pares do tipo data com contagem de 0?
Na realidade, existem alguns tipos distintos, período de até cinco anos (1800 datas) e ~ 30k linhas na dates_ranges
tabela (mas pode crescer significativamente).
Não há índices. Para ser preciso, no meu caso, é resultado da subconsulta, mas eu queria limitar a pergunta a um problema, por isso é mais geral.
fonte
(1,2018-01-01,2018-01-15)
e(1,2018-01-20,2018-01-25)
deseja levar isso em consideração ao determinar quantas datas sobrepostas você tem?2018-01-31
ou2018-01-30
ou2018-01-29
em que quando a primeira faixa tem todos eles?generate_series
são parâmetros externos - eles não cobrem necessariamente todos os intervalos dadates_ranges
tabela. Quanto à primeira pergunta, suponho que não entendi - as linhasdates_ranges
são independentes, não quero determinar a sobreposição.Respostas:
A consulta a seguir também funciona se "zeros ausentes" estiverem OK:
mas não é mais rápido que a
lateral
versão com o pequeno conjunto de dados. Porém, pode ser melhor dimensionado, pois não é necessária nenhuma junção, mas a versão acima é agregada em todas as linhas; portanto, ela pode se perder novamente.A consulta a seguir tenta evitar trabalhos desnecessários removendo qualquer série que não se sobreponha:
- e eu tenho que usar o
overlaps
operador! Observe que você deve adicionarinterval '1 day'
à direita, pois o operador de sobreposições considera que os períodos de tempo estão abertos à direita (o que é bastante lógico, porque uma data geralmente é considerada um carimbo de data e hora com o componente de hora da meia-noite).fonte
generate_series
poderia ser usado assim. Após alguns testes, tenho as seguintes observações. Sua consulta realmente se adapta muito bem ao comprimento do intervalo selecionado - praticamente não há diferença entre o período de 3 e 10 anos. No entanto, por períodos mais curtos (1 ano), minhas soluções são mais rápidas - acho que o motivo é que existem alguns intervalos realmente longosdates_ranges
(como 2010-2100), que estão desacelerando sua consulta. Limitarstart_date
eend_date
dentro da consulta interna deve ajudar. Eu preciso fazer mais alguns testes.Crie uma grade de todas as combinações e
LATERAL
junte-se à sua mesa, assim:Também deve ser o mais rápido possível.
Eu tinha
LEFT JOIN LATERAL ... on true
no começo, mas há um agregado na subconsultac
, então sempre temos uma linha e podemos usarCROSS JOIN
também. Não há diferença no desempenho.Se você possui uma tabela com todos os tipos relevantes , use-a em vez de gerar a lista com subconsulta
k
.O elenco para
integer
é opcional. Senão você recebebigint
.Os índices ajudariam, especialmente um índice com várias colunas
(kind, start_date, end_date)
. Como você está construindo uma subconsulta, isso pode ou não ser possível.Usar funções de retorno de conjunto como
generate_series()
naSELECT
lista geralmente não é aconselhável nas versões do Postgres anteriores a 10 (a menos que você saiba exatamente o que está fazendo). Vejo:Se você tiver muitas combinações com poucas ou nenhuma linha, esse formulário equivalente poderá ser mais rápido:
fonte
SELECT
lista - li que não é aconselhável, no entanto, parece que funciona muito bem, se houver apenas uma dessas funções. Se tenho certeza de que haverá apenas um, algo pode dar errado?SELECT
lista funciona conforme o esperado. Talvez adicione um comentário para alertar contra a adição de outro. Ou mova-o para aFROM
lista para começar nas versões mais antigas do Postgres. Por que arriscar complicações? (Isso também é SQL padrão e não as pessoas confundem vindo de outros RDBMS.)Usando o
daterange
tipoO PostgreSQL possui um
daterange
. Usá-lo é bastante simples. Começando com os dados de amostra, passamos a usar o tipo na tabela.Agora, para consultá-lo, invertemos o procedimento e geramos uma série de datas, mas aqui está o problema: a própria consulta pode usar o
@>
operador containsment ( ) para verificar se as datas estão dentro do intervalo, usando um índice.Observe que usamos
timestamp without time zone
(para interromper os perigos do horário de verão)Quais são as sobreposições diárias discriminadas no índice.
Como bônus adicional, com o tipo de daterange, você pode interromper inserções de intervalos que se sobrepõem a outros usando um
EXCLUDE CONSTRAINT
fonte
JOIN
acho que é demais.count(DISTINCT kind)
1
data do tipo2018-01-01
está nas duas primeiras linhas dedates_ranges
, mas Sua consulta fornece8
.count(DISTINCT kind)
você adicionou aDISTINCT
palavra-chave lá?DISTINCT
palavra-chave, ela ainda não funciona conforme o esperado. Ele conta tipos distintos para cada data, mas quero contar todas as linhas de cada tipo para cada data.