A tabela Histórico do usuário a seguir contém um registro para cada dia que um determinado usuário acessa um site (em um período UTC de 24 horas). Possui muitos milhares de registros, mas apenas um registro por dia por usuário. Se o usuário não tiver acessado o site naquele dia, nenhum registro será gerado.
ID UserId CreationDate ------ ------ ------------ 750997 12 07-07-2009 18: 42: 20.723 750998 15-07-07 2009: 42: 20.927 751000 19-07-07 18: 42: 22.283
O que estou procurando é uma consulta SQL nesta tabela com bom desempenho , que me diga quais IDs de usuário acessaram o site por (n) dias contínuos sem perder um dia.
Em outras palavras, quantos usuários possuem (n) registros nesta tabela com datas seqüenciais (dia antes ou depois) ? Se algum dia estiver faltando na sequência, a sequência será interrompida e deverá reiniciar novamente em 1; estamos procurando usuários que tenham alcançado um número contínuo de dias aqui sem lacunas.
Qualquer semelhança entre esta consulta e um distintivo Stack Overflow específico é pura coincidência, é claro .. :)
fonte
Respostas:
A resposta é obviamente:
EDITAR:
Ok, aqui está a minha resposta séria:
EDITAR:
[Jeff Atwood] Esta é uma ótima solução rápida e merece ser aceita, mas a solução de Rob Farley também é excelente e sem dúvida ainda mais rápida (!). Por favor, confira também!
fonte
ON uh2.CreationDate >= uh1.CreationDate AND uh2.CreationDate < DATEADD(dd, DATEDIFF(dd, 0, uh1.CreationDate) + @days, 0)
para:, para significar "Ainda não no 31º dia depois". Também significa que você pode pular o cálculo @seconds.Que tal (e verifique se a declaração anterior terminou com ponto e vírgula):
A idéia é que, se tivermos uma lista dos dias (como um número) e um número de linha, os dias perdidos tornarão o deslocamento entre essas duas listas um pouco maior. Então, estamos procurando um intervalo que tenha um deslocamento consistente.
Você pode usar "ORDER BY NumConsecutiveDays DESC" no final deste tópico ou dizer "TENDO contagem (*)> 14" para um limite ...
Eu ainda não testei isso - apenas escrevi em cima da minha cabeça. Espero que funcione no SQL2005 e assim por diante.
... e seria muito ajudado por um índice no tablename (UserID, CreationDate)
Editado: Acontece que Offset é uma palavra reservada, então usei TheOffset.
Editado: a sugestão de usar COUNT (*) é muito válida - eu deveria ter feito isso em primeiro lugar, mas não estava pensando. Anteriormente, ele usava datediff (dia, min (CreationDate), max (CreationDate)).
Roubar
fonte
Se você pode alterar o esquema da tabela, sugiro adicionar uma coluna
LongestStreak
à tabela que você definiria para o número de dias sequenciais que terminam emCreationDate
. É fácil atualizar a tabela no momento do login (semelhante ao que você já está fazendo, se não houver linhas no dia atual, você verificará se existe alguma linha no dia anterior. Se for verdade, você aumentará o valorLongestStreak
no nova linha, caso contrário, você a definirá como 1.)A consulta será óbvia depois de adicionar esta coluna:
fonte
Alguns SQL bem expressivos ao longo das linhas de:
Assumindo que você tenha uma função agregada definida pelo usuário, algo como o seguinte (cuidado com isso é um buggy):
fonte
Parece que você poderia tirar proveito do fato de que para ser contínuo por n dias exigiria n linhas.
Então, algo como:
fonte
Fazer isso com uma única consulta SQL parece muito complicado para mim. Deixe-me dividir esta resposta em duas partes.
execute uma tarefa cron diária que verifique se todos os usuários estão logados hoje e, em seguida, incrementa um contador se ele tiver ou o define como 0 se não tiver.
- Exporte esta tabela para um servidor que não executa o site e não será necessário por um tempo. ;)
- Classifique por usuário e data.
- passe por isso sequencialmente, mantenha um contador ...
fonte
Se isso é tão importante para você, crie esse evento e conduza uma tabela para fornecer essas informações. Não há necessidade de matar a máquina com todas essas perguntas malucas.
fonte
Você pode usar um CTE recursivo (SQL Server 2005+):
fonte
Joe Celko tem um capítulo completo sobre isso no SQL for Smarties (chamando de Execuções e Sequências). Eu não tenho esse livro em casa, então, quando eu chegar ao trabalho ... na verdade eu vou responder isso. (supondo que a tabela de histórico seja chamada dbo.UserHistory e o número de dias seja @Days)
Outra vantagem é de blog SQL Team sobre execuções
A outra idéia que tive, mas não tenho um servidor SQL à mão para trabalhar aqui, é usar um CTE com um ROW_NUMBER particionado como este:
O exposto acima provavelmente é MUITO MAIS DURO do que deveria ser, mas deixou como um cócegas no cérebro quando você tem alguma outra definição de "uma corrida" além de datas.
fonte
Algumas opções do SQL Server 2012 (assumindo N = 100 abaixo).
Embora com meus dados de amostra o seguinte tenha funcionado de maneira mais eficiente
Ambos contam com a restrição declarada na pergunta de que há no máximo um registro por dia por usuário.
fonte
Algo assim?
fonte
Usei uma propriedade matemática simples para identificar quem acessou o site consecutivamente. Essa propriedade é que você deve ter a diferença do dia entre o primeiro acesso e a última vez igual ao número de registros no log da tabela de acesso.
Aqui estão os scripts SQL que eu testei no Oracle DB (ele também deve funcionar em outros DBs):
Script de preparação da tabela:
fonte
A declaração
cast(convert(char(11), @startdate, 113) as datetime)
remove a parte da hora da data e, portanto, começamos à meia-noite.Eu assumiria também que o
creationdate
euserid
colunas são indexadas.Acabei de perceber que isso não informa todos os usuários e o total de dias consecutivos. Mas informará quais usuários visitarão um número definido de dias a partir da data de sua escolha.
Solução revisada:
Eu verifiquei isso e ele consultará todos os usuários e todas as datas. É baseado na 1ª solução de Spencer (piada?) , Mas a minha funciona.
Atualização: aprimorou a manipulação de datas na segunda solução.
fonte
Isso deve fazer o que você deseja, mas não tenho dados suficientes para testar a eficiência. O material complicado CONVERT / FLOOR é retirar a parte do tempo do campo de data e hora. Se você estiver usando o SQL Server 2008, poderá usar o CAST (x.CreationDate AS DATE).
Script de criação
fonte
Spencer quase fez isso, mas este deveria ser o código de trabalho:
fonte
Em cima da minha cabeça, MySQLish:
Não testado e quase certamente precisa de alguma conversão para o MSSQL, mas acho que isso dá algumas idéias.
fonte
Que tal alguém usando tabelas Tally? Ele segue uma abordagem mais algorítmica e o plano de execução é fácil. Preencha o tallyTable com números de 1 a 'MaxDaysBehind' que você deseja escanear na tabela (ou seja, 90 ficará com 3 meses de atraso, etc).
fonte
Ajustando um pouco a consulta de Bill. Pode ser necessário truncar a data antes do agrupamento para contar apenas um login por dia ...
EDITADO para usar DATEADD (dd, DATEDIFF (dd, 0, CreationDate), 0) em vez de converter (char (10), CreationDate, 101).
@IDisposable Eu estava procurando usar o datepart anteriormente, mas estava com preguiça de procurar a sintaxe, então imaginei usar o id convert. Eu sei que teve um impacto significativo Obrigado! agora eu sei.
fonte
assumindo um esquema que seja como:
isso extrairá intervalos contíguos de uma sequência de datas com intervalos.
fonte