Mesa:
UserId, Value, Date.
Quero obter o UserId, Value para o máximo (Data) de cada UserId. Ou seja, o valor para cada UserId que possui a data mais recente. Existe uma maneira de fazer isso simplesmente no SQL? (Preferencialmente Oracle)
Atualização: desculpas por qualquer ambiguidade: preciso obter TODOS os UserIds. Mas para cada UserId, apenas a linha em que esse usuário tem a data mais recente.
sql
oracle
greatest-n-per-group
Umang
fonte
fonte
Respostas:
Isso recuperará todas as linhas para as quais o valor da coluna my_date é igual ao valor máximo de my_date para esse ID do usuário. Isso pode recuperar várias linhas para o ID do usuário em que a data máxima está em várias linhas.
"Funções analíticas rock"
Edit: Com relação ao primeiro comentário ...
"o uso de consultas analíticas e uma associação automática anulam o objetivo das consultas analíticas"
Não há auto-junção neste código. Em vez disso, existe um predicado colocado no resultado da exibição em linha que contém a função analítica - uma questão muito diferente e uma prática completamente padrão.
"A janela padrão no Oracle é da primeira linha da partição até a atual"
A cláusula de janelas é aplicável apenas na presença da ordem por cláusula. Sem ordem por cláusula, nenhuma cláusula de janelas é aplicada por padrão e nenhuma pode ser especificada explicitamente.
O código funciona.
fonte
MAX(...) OVER (...)
você também pode usarROW_NUMBER() OVER (...)
(para o principal n por grupo) ouRANK() OVER (...)
(para o maior n por grupo).Vejo muitas pessoas usarem subconsultas ou outros recursos específicos do fornecedor para fazer isso, mas geralmente faço esse tipo de consulta sem subconsultas da seguinte maneira. Ele usa SQL simples e padrão, portanto, deve funcionar em qualquer marca de RDBMS.
Em outras palavras: busque a linha de
t1
onde não existe outra linha com a mesma dataUserId
e uma data maior.(Coloquei o identificador "Data" nos delimitadores porque é uma palavra reservada ao SQL.)
Caso isso
t1."Date" = t2."Date"
ocorra, a duplicação será exibida. Normalmente, as tabelas têmauto_inc(seq)
chave, por exemploid
. Para evitar a duplicação pode ser usado a seguir:Re comentário de @Farhan:
Aqui está uma explicação mais detalhada:
Uma junção externa tentativas para se juntar
t1
comt2
. Por padrão, todos os resultados det1
são retornados e, se houver uma correspondênciat2
, ele também será retornado. Se não houver correspondênciat2
para uma determinada linha det1
, a consulta ainda retornará a linha det1
e será usadaNULL
como espaço reservado para todast2
as colunas de. É assim que as junções externas funcionam em geral.O truque nesta consulta é projetar a condição de correspondência da junção, que
t2
deve corresponder à mesmauserid
e maiordate
. A ideia é que, se existir uma linhat2
com uma maiordate
, então a linha na qualt1
ela é comparada não pode ser a melhordate
para issouserid
. Mas se não houver correspondência - ou seja, se nenhuma linha existirt2
com uma maiordate
que a linha exibidat1
-, sabemos que a linha int1
foi a linha com a maiordate
para o dadouserid
.Nesses casos (quando não há correspondência), as colunas
t2
serãoNULL
- mesmo as colunas especificadas na condição de junção. É por isso que usamosWHERE t2.UserId IS NULL
, porque estamos pesquisando os casos em que nenhuma linha foi encontrada com uma maiordate
para o dadouserid
.fonte
fonte
Não sei o nome exato das colunas, mas seria algo como isto:
fonte
Não estando no trabalho, não tenho o Oracle em mãos, mas me lembro que o Oracle permite que várias colunas sejam correspondidas em uma cláusula IN, que deve pelo menos evitar as opções que usam uma subconsulta correlacionada, o que raramente é bom. idéia.
Algo assim, talvez (não lembro se a lista de colunas deve estar entre parênteses ou não):
EDIT: Apenas tentei de verdade:
Por isso, funciona, embora algumas das coisas novas mencionadas em outros lugares possam ter mais desempenho.
fonte
Eu sei que você pediu pelo Oracle, mas no SQL 2005 agora usamos isso:
fonte
Não tenho o Oracle para testá-lo, mas a solução mais eficiente é usar consultas analíticas. Deve ser algo como isto:
Eu suspeito que você pode se livrar da consulta externa e colocar distintas no interior, mas não tenho certeza. Enquanto isso, eu sei que este funciona.
Se você quiser aprender sobre consultas analíticas, sugiro ler http://www.orafaq.com/node/55 e
http://www.akadia.com/services/ora_analytic_functions.html. Aqui está o breve resumo.Nas consultas analíticas, classifique todo o conjunto de dados e processe-o sequencialmente. Ao processá-lo, você particiona o conjunto de dados de acordo com certos critérios e, em seguida, para cada linha observa alguma janela (o padrão é o primeiro valor da partição para a linha atual - esse padrão também é o mais eficiente) e pode calcular valores usando um número de funções analíticas (cuja lista é muito semelhante às funções agregadas).
Nesse caso, aqui está o que a consulta interna faz. O conjunto de dados inteiro é classificado por UserId e Data DESC. Em seguida, processa-o de uma só vez. Para cada linha, você retorna o UserId e a primeira data vista para esse UserId (como as datas são classificadas em DESC, essa é a data máxima). Isso fornece sua resposta com linhas duplicadas. Em seguida, o DISTINCT externo esmaga duplicatas.
Este não é um exemplo particularmente espetacular de consultas analíticas. Para uma vitória muito maior, considere tomar uma tabela de recebimentos financeiros e calcular para cada usuário e recebedor, um total contínuo do que eles pagaram. As consultas analíticas resolvem isso com eficiência. Outras soluções são menos eficientes. É por isso que eles fazem parte do padrão SQL 2003. (Infelizmente, o Postgres ainda não os possui. Grrr ...)
fonte
Uma cláusula QUALIFY não seria mais simples e melhor?
Por contexto, no Teradata aqui, um teste de tamanho decente é executado nos anos 17 com esta versão QUALIFY e nos 23 com a solução 'inline view' / Aldridge # 1.
fonte
rank()
função em situações em que há laços. Você pode acabar com mais de umrank=1
. Melhor usarrow_number()
se você realmente deseja apenas um registro retornado.QUALIFY
cláusula é específica para o Teradata. No Oracle (pelo menos), é necessário aninhar sua consulta e filtrar usando umaWHERE
cláusula na instrução de seleção de empacotamento (que provavelmente atinge um toque de desempenho, eu imagino).Em
Oracle 12c+
, você pode usar as consultas Top n junto com a função analíticarank
para conseguir isso de forma muito concisa, sem subconsultas:O acima retorna todas as linhas com max my_date por usuário.
Se você deseja apenas uma linha com data máxima, substitua
rank
porrow_number
:fonte
Use
ROW_NUMBER()
para atribuir uma classificação exclusiva em descendenteDate
para cada um eUserId
, em seguida, filtre para a primeira linha de cada umUserId
(por exemplo,ROW_NUMBER
= 1).fonte
Com o PostgreSQL 8.4 ou posterior, você pode usar isto:
fonte
Eu acho que você deve fazer essa variante para a consulta anterior:
fonte
fonte
Só tive que escrever um exemplo "ao vivo" no trabalho :)
Este suporta vários valores para UserId na mesma data.
Colunas: ID do usuário, Valor, Data
Você pode usar FIRST_VALUE em vez de MAX e procurá-lo no plano de explicação. Não tive tempo de brincar com isso.
Obviamente, se você pesquisar em tabelas enormes, provavelmente será melhor usar dicas COMPLETAS na sua consulta.
fonte
fonte
Eu acho algo assim. (Perdoe-me por qualquer erro de sintaxe; estou acostumado a usar o HQL neste momento!)
EDIT: Também interpretou mal a pergunta! Corrigida a consulta ...
fonte
(T-SQL) Primeiro, obtenha todos os usuários e seus maxdate. Associe-se à tabela para encontrar os valores correspondentes para os usuários nas datas máximas.
resultados:
fonte
A resposta aqui é apenas Oracle. Aqui está uma resposta um pouco mais sofisticada em todo o SQL:
Quem tem o melhor resultado geral da lição de casa (soma máxima de pontos de lição de casa)?
E um exemplo mais difícil, que precisa de explicações, para o qual não tenho tempo atm:
Forneça o livro (ISBN e título) mais popular em 2008, ou seja, emprestado com mais frequência em 2008.
Espero que isso ajude (alguém) .. :)
Atenciosamente, Guus
fonte
Supondo que a data seja exclusiva para um determinado ID do usuário, aqui estão alguns TSQL:
fonte
Estou muito atrasado para a festa, mas o seguinte hack superará as subconsultas correlacionadas e qualquer função de análise, mas tem uma restrição: os valores devem ser convertidos em strings. Por isso, funciona para datas, números e outras strings. O código não parece bom, mas o perfil de execução é ótimo.
A razão pela qual esse código funciona tão bem é que ele só precisa varrer a tabela uma vez. Ele não requer índices e, o mais importante, não precisa classificar a tabela, o que a maioria das funções de análise exige. Os índices ajudarão, se você precisar filtrar o resultado para um único ID do usuário.
fonte
IMHO isso funciona. HTH
fonte
Eu acho que isso deve funcionar?
fonte
Na primeira tentativa, eu li mal a pergunta, seguindo a resposta principal, eis um exemplo completo com resultados corretos:
-
-
fonte
Isso também cuidará das duplicatas (retorne uma linha para cada user_id):
fonte
Acabei de testar isso e parece funcionar em uma tabela de registro
fonte
Isso deve ser tão simples quanto:
fonte
Solução para MySQL que não possui conceitos de partição KEEP, DENSE_RANK.
Referência: http://benincampus.blogspot.com/2013/08/select-rows-which-have-maxmin-value-in.html
fonte
Se você estiver usando o Postgres, poderá usar
array_agg
comoNão estou familiarizado com o Oracle. Isto é o que eu vim com
Ambas as consultas retornam os mesmos resultados que a resposta aceita. Consulte SQLFiddles:
fonte
Se (UserID, Data) for único, ou seja, nenhuma data aparecer duas vezes para o mesmo usuário, então:
fonte
fonte