Eu tenho uma situação que acho que pode ser resolvida usando a função de janela, mas não tenho certeza.
Imagine a seguinte tabela
CREATE TABLE tmp
( date timestamp,
id_type integer
) ;
INSERT INTO tmp
( date, id_type )
VALUES
( '2017-01-10 07:19:21.0', 3 ),
( '2017-01-10 07:19:22.0', 3 ),
( '2017-01-10 07:19:23.1', 3 ),
( '2017-01-10 07:19:24.1', 3 ),
( '2017-01-10 07:19:25.0', 3 ),
( '2017-01-10 07:19:26.0', 5 ),
( '2017-01-10 07:19:27.1', 3 ),
( '2017-01-10 07:19:28.0', 5 ),
( '2017-01-10 07:19:29.0', 5 ),
( '2017-01-10 07:19:30.1', 3 ),
( '2017-01-10 07:19:31.0', 5 ),
( '2017-01-10 07:19:32.0', 3 ),
( '2017-01-10 07:19:33.1', 5 ),
( '2017-01-10 07:19:35.0', 5 ),
( '2017-01-10 07:19:36.1', 5 ),
( '2017-01-10 07:19:37.1', 5 )
;
Eu gostaria de ter um novo grupo a cada alteração na coluna id_type. EG 1º grupo de 7:19:21 a 7:19:25, 2º iniciando e terminando às 7:19:26, e assim por diante.
Depois que funcionar, quero incluir mais critérios para definir grupos.
Neste momento, usando a consulta abaixo ...
SELECT distinct
min(min(date)) over w as begin,
max(max(date)) over w as end,
id_type
from tmp
GROUP BY id_type
WINDOW w as (PARTITION BY id_type)
order by begin;
Eu recebo o seguinte resultado:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:37.1 5
Enquanto eu gostaria:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:25.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:26.0 5
2017-01-10 07:19:27.1 2017-01-10 07:19:27.1 3
2017-01-10 07:19:28.0 2017-01-10 07:19:29.0 5
2017-01-10 07:19:30.1 2017-01-10 07:19:30.1 3
2017-01-10 07:19:31.0 2017-01-10 07:19:31.0 5
2017-01-10 07:19:32.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:33.1 2017-01-10 07:19:37.1 5
Depois de resolver esta primeira etapa, adicionarei mais colunas para usar como regras para quebrar grupos, e essas outras serão anuláveis.
Versão do Postgres: 8.4 (Temos o Postgres com o Postgis, por isso não é fácil atualizar. As funções do Postgis mudam de nome e existem outros problemas, mas espero que já estejamos escrevendo tudo e a nova versão use uma versão mais recente 9.X com postgis 2.x)
Respostas:
Por alguns pontos,
tmp
que fica confusa..0
)date
. Se tiver data e hora, é um carimbo de data / hora (e armazene-o como um)Melhor usar uma função de janela ..
Saídas
Explicação
Primeiro, precisamos de redefinições. Nós as geramos com
lag()
Então contamos para obter grupos.
Em seguida, embrulhe em uma subselect
GROUP BY
eORDER
e selecione o min max (intervalo)fonte
1. Funções da janela mais subconsultas
Conte as etapas para formar grupos, semelhantes à idéia de Evan , com modificações e correções:
Isso pressupõe que as colunas envolvidas são
NOT NULL
. Caso contrário, você precisa fazer mais.Supondo também
date
que esteja definidoUNIQUE
, você precisará adicionar um desempatador àsORDER BY
cláusulas para obter resultados determinísticos. Gostar:ORDER BY date, id
.Explicação detalhada (resposta a pergunta muito semelhante):
Observe em particular:
Em casos relacionados,
lag()
com 3 parâmetros pode ser essencial para cobrir a caixa de canto da primeira (ou última) linha com elegância. (O terceiro parâmetro é usado como padrão se não houver linha anterior (próxima).Como estamos interessados apenas em uma mudança real de
id_type
(TRUE
), isso não importa neste caso específico.NULL
eFALSE
ambos não contam comostep
.count(step OR NULL) OVER (ORDER BY date)
é a sintaxe mais curta que também funciona no Postgres 9.3 ou anterior.count()
conta apenas valores não nulos ...No Postgres moderno, a sintaxe mais limpa e equivalente seria:
Detalhes:
2. Subtraia duas funções da janela, uma subconsulta
Semelhante à idéia de Erik com modificações:
Se
date
definidoUNIQUE
, como mencionei acima (você nunca esclareceu),dense_rank()
seria inútil, pois o resultado é o mesmo que pararow_number()
e o último é substancialmente mais barato.Se não
date
estiver definido (e não sabemos que as únicas duplicatas estão ativadas ), todas essas consultas serão inúteis, pois o resultado é arbitrário.UNIQUE
(date, id_type)
Além disso, uma subconsulta é normalmente mais barata que uma CTE no Postgres. Use CTEs somente quando precisar deles.
Respostas relacionadas com mais explicações:
Em casos relacionados em que já temos um número em execução na tabela, podemos nos contentar com uma única função da janela:
3. Desempenho superior com função plpgsql
Como essa pergunta se tornou inesperadamente popular, adicionarei outra solução para demonstrar o melhor desempenho.
O SQL possui muitas ferramentas sofisticadas para criar soluções com sintaxe curta e elegante. Mas uma linguagem declarativa tem seus limites para requisitos mais complexos que envolvem elementos processuais.
Uma função procedural do servidor é mais rápida para isso do que qualquer coisa postada até o momento, porque precisa apenas de uma única varredura seqüencial sobre a tabela e uma operação de classificação única . Se um índice de ajuste estiver disponível, mesmo apenas uma única varredura de índice.
Ligar:
Teste com:
Você pode tornar a função genérica com tipos polimórficos e passar o tipo de tabela e os nomes de colunas. Detalhes:
Se você não deseja ou não pode persistir em uma função para isso, pagaria até criar uma função temporária em tempo real. Custa alguns ms.
dbfiddle para Postgres 9.6, comparando o desempenho dos três. Construindo nocaso de teste de Jack, modificado.
dbfiddle para Postgres 8.4, onde as diferenças de desempenho são ainda maiores.
fonte
count(x or null)
ou mesmo o que está fazendo lá. Talvez você possa mostrar algumas amostras onde é necessário, porque não é necessário aqui. E o que determinaria a exigência de cobrir esses casos de canto. BTW, mudei meu downvote para upvote apenas para o exemplo pl / pgsql. Isso é muito legal. (Mas geralmente sou contra as respostas que resumem outras respostas ou cobrem casos de canto - embora eu odeie dizer que esse é um caso de canto porque não o entendo).count(x or null)
faz. Ficarei feliz em fazer as duas perguntas, se você preferir.count(x or null)
necessário nas lacunas e ilhas?Você pode fazer isso como uma simples subtração de
ROW_NUMBER()
operações (ou se suas datas não forem únicas, embora ainda sejam únicas porid_type
, use-as emDENSE_RANK()
vez disso, embora seja uma consulta mais cara):Veja este trabalho no DB Fiddle (ou veja a versão DENSE_RANK )
Resultado:
Logicamente, você pode pensar nisso como um simples
DENSE_RANK()
com aPREORDER BY
, ou seja, você deseja queDENSE_RANK
todos os itens sejam classificados juntos e que eles sejam ordenados pelas datas, basta lidar com o incômodo problema de que a cada alteração na data,DENSE_RANK
será incrementado. Você faz isso usando a expressão como mostrei acima. Imagine se você tivesse esta sintaxe:DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)
onde oPREORDER
é excluído do cálculo de classificação e apenas oORDER BY
é contado.Observe que é importante
GROUP BY
tanto para aSeq
coluna gerada quanto para aid_type
coluna.Seq
NÃO é único por si só, pode haver sobreposições - você também deve agrupar porid_type
.Para mais informações sobre este tópico:
Esse primeiro link fornece um código que você pode usar se desejar que a data de início ou término seja igual à data de término / início do período anterior ou seguinte (para que não haja lacunas). Além de outras versões que podem ajudá-lo em sua consulta. Embora eles tenham que ser traduzidos da sintaxe do SQL Server ...
fonte
No Postgres 8.4, você pode usar uma função RECURSIVE .
Como eles fazem isso
A função recursiva adiciona um nível a cada id_type diferente, selecionando as datas uma a uma em ordem decrescente.
Em seguida, use o agrupamento MAX (data), MIN (data) por nível, id_type para obter o resultado desejado.
Verifique: http://rextester.com/WCOYFP6623
fonte
Aqui está outro método, que é semelhante ao de Evan e Erwin, na medida em que usa o GAL para determinar ilhas. Difere dessas soluções, pois utiliza apenas um nível de aninhamento, sem agrupamento e consideravelmente mais funções de janela:
A
is_start
coluna computada no SELECT aninhado marca o início de cada ilha. Além disso, o SELECT aninhado expõe a data anterior de cada linha e a data do último conjunto de dados.Para linhas que são o início de suas respectivas ilhas, a data anterior efetivamente é a data de término da ilha anterior. É assim que o SELECT principal o usa. Ele pega apenas as linhas que correspondam à
is_start = 1
condição, e para cada linha retornada que mostra a linha própriadate
comobegin
e a seguinte linha éprev_date
comoend
. Como a última linha não possui uma linha seguinte,LEAD(prev_date)
retorna um nulo, para o qual a função COALESCE substitui a última data do conjunto de dados.Você pode jogar com esta solução no dbfiddle .
Ao introduzir colunas adicionais identificando as ilhas, você provavelmente desejará introduzir uma subcláusula PARTITION BY na cláusula OVER de cada função da janela. Por exemplo, se você deseja detectar as ilhas nos grupos definidos por a
parent_id
, a consulta acima provavelmente terá a seguinte aparência:E se você optar pela solução de Erwin ou Evan, acredito que uma mudança semelhante precisará ser adicionada a ela também.
fonte
Mais por interesse acadêmico do que por uma solução prática, você também pode conseguir isso com um agregado definido pelo usuário . Como as outras soluções, isso funcionará mesmo no Postgres 8.4, mas como outros comentaram, atualize, se puder.
O agregado manipula
null
como se fosse um diferentefoo_type
, portanto, execuções de nulos teriam o mesmogrp
- que pode ou não ser o que você deseja.dbfiddle aqui
fonte
Isso pode ser feito
RECURSIVE CTE
para passar o "horário de início" de uma linha para a seguinte e alguns preparativos extras (conveniência).Esta consulta retorna o resultado que você deseja:
após a preparação ... parte recursiva
Você pode verificar isso em http://rextester.com/POYM83542
Este método não escala bem. Para uma tabela de 8_641 linhas, são necessários 7s; para uma tabela com o dobro desse tamanho, são necessários 28s. Mais algumas amostras mostram os tempos de execução parecidos com O (n ^ 2).
O método de Evan Carrol leva menos de 1s (ou seja: vá em frente!) E se parece com O (n). As consultas recursivas são absolutamente ineficientes e devem ser consideradas um último recurso.
fonte