Tenho um relatório que mostra a contagem de eventos nas últimas 12 horas, agrupados por hora. Parece fácil, mas estou lutando para incluir registros que cubram as lacunas.
Aqui está uma tabela de exemplo:
Event
(
EventTime datetime,
EventType int
)
Os dados ficam assim:
'2012-03-08 08:00:04', 1
'2012-03-08 09:10:00', 2
'2012-03-08 09:11:04', 2
'2012-03-08 09:10:09', 1
'2012-03-08 10:00:17', 4
'2012-03-08 11:00:04', 1
Preciso criar um conjunto de resultados que tenha um registro para cada hora das últimas 12 horas, independentemente de haver eventos durante essa hora ou não.
Supondo que a hora atual seja '2012-03-08 11:00:00', o relatório mostraria (aproximadamente):
Hour EventCount
---- ----------
23 0
0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 1
9 3
10 1
Eu criei uma solução que usa uma tabela que possui um registro para cada hora do dia. Consegui obter os resultados que procurava usando um UNION e alguma lógica de caso complicada na cláusula where, mas esperava que alguém tivesse uma solução mais elegante.
Para o SQL Server 2005+, você pode gerar esses 12 registros com muita facilidade com um loop ou um CTE recursivo. Aqui está um exemplo de um CTE recursivo:
Então você só precisa juntá-lo com sua tabela de eventos.
Tabelas de contagem podem ser usadas para coisas como esta. Eles podem ser muito eficientes. Crie a tabela de contagem abaixo. Eu criei a tabela de contagem com apenas 24 linhas para o seu exemplo, mas você pode criá-la com quantas quiser para atender a outros propósitos.
Presumi que sua tabela se chama dbo.tblEvents, execute a consulta abaixo. Acredito que seja isso que você procura:
Acredito que os créditos vão para os seguintes links, acredito que foi aqui que me deparei com isso:
http://www.sqlservercentral.com/articles/T-SQL/62867/
http://www.sqlservercentral.com/articles/T-SQL/74118/
Em primeiro lugar, minhas desculpas pela demora em minha resposta desde meus últimos comentários.
Surgiu nos comentários o assunto de que usar um CTE Recursivo (rCTE daqui em diante) roda rápido o suficiente por causa do baixo número de linhas. Embora possa parecer assim, nada poderia estar mais longe da verdade.
CONSTRUIR TABELA DE CONTAGEM E FUNÇÃO DE CONTAGEM
Antes de começarmos os testes, precisamos construir uma Tabela de Tally física com o Índice Agrupado apropriado e uma Função de Tally estilo Itzik Ben-Gan. Também faremos tudo isso no TempDB para que não deixemos cair acidentalmente as guloseimas de ninguém.
Aqui está o código para construir a Tally Table e minha versão de produção atual do maravilhoso código de Itzik.
A propósito... observe que construiu uma tabela de contagem de um milhão e uma linha e adicionou um índice agrupado a ela em cerca de um segundo. Tente ISSO com um rCTE e veja quanto tempo leva! ;-)
CONSTRUIR ALGUNS DADOS DE TESTE
Também precisamos de alguns dados de teste. Sim, concordo que todas as funções que vamos testar, incluindo o rCTE, são executadas em milissegundos ou menos por apenas 12 linhas, mas essa é a armadilha em que muitas pessoas caem. Falaremos mais sobre essa armadilha mais tarde, mas, por enquanto, vamos simular chamar cada função 40.000 vezes, que é sobre quantas vezes certas funções na minha loja são chamadas em um dia de 8 horas. Imagine quantas vezes essas funções podem ser chamadas em um grande negócio de varejo online.
Então, aqui está o código para criar 40.000 linhas com datas aleatórias, cada uma com um número de linha apenas para fins de rastreamento. Não me dei ao trabalho de fazer horas inteiras porque isso não importa aqui.
CONSTRUA ALGUMAS FUNÇÕES PARA FAZER A COISA DAS 12 HORAS LINHAS
A seguir, converti o código rCTE em uma função e criei outras 3 funções. Todos eles foram criados como iTVFs (funções com valor de tabela embutida) de alto desempenho. Você sempre pode dizer porque os iTVFs nunca têm um BEGIN neles como o Scalar ou os mTVFs (Multi-statement Table Valued Functions).
Aqui está o código para construir essas 4 funções... Eu as nomeei de acordo com o método que elas usam e não o que elas fazem apenas para facilitar a identificação delas.
CONSTRUA O ARNÊS DE TESTE PARA TESTAR AS FUNÇÕES
Por último, mas não menos importante, precisamos de um equipamento de teste. Eu faço uma verificação de linha de base e, em seguida, testo cada função de maneira idêntica.
Aqui está o código para o arnês de teste ...
Uma coisa a se notar na estrutura de teste acima é que eu desvio toda a saída para variáveis "descartáveis". Isso é para tentar manter as medições de desempenho o mais puras possível, sem nenhuma saída para os resultados de distorção do disco ou da tela.
UMA PALAVRA DE CUIDADO NAS ESTATÍSTICAS DE SET
Also, a word of caution for would-be testers... You MUST NOT use SET STATISTICS when testing either Scalar or mTVF functions. It can only be safely used on iTVF functions like the ones in this test. SET STATISTICS has been proven to make SCALAR functions run hundreds of times slower than they actually do without it. Yeah, I'm trying to tilt another windmill but that would be a whole 'nuther article-length post and I don't have the time for that. I have an article on SQLServerCentral.com talking all about that but there's no sense in posting the link here because someone will get all bent out of shape about it.
THE TEST RESULTS
So, here are the test results when I run the test harness on my little i5 laptop with 6GB of RAM.
The "BASELINE SELECT", which only selects data (each row created 12 times to simulate the same volume of return), came in right about 1/5th of a second. Everything else came in at about a quarter of a second. Well, everything except that bloody rCTE function. It took 4 and 1/4 seconds or 16 times longer (1,600% slower).
And look at the logical reads (memory IO)... The rCTE consumed a whopping 2,960,000 (almost 3 MILLION reads) whereas the other functions only consumed about 82,100. That means the rCTE consumed more than 34.3 times more memory IO than any of the other functions.
CLOSING THOUGHTS
Let's summarize. The rCTE method for doing this "small" 12 row thing used 16 TIMES (1,600%) more CPU (and duration) and 34.3 TIMES (3,430%) more memory IO than any of the other functions.
Heh... I know what you're thinking. "Big Deal! It's just one function."
Yeah, agreed, but how many other functions do you have? How many other places outside of functions do you have? And do you have any of those that work with more than just 12 rows each run? And, is there any chance that someone in a lurch for a method might copy that rCTE code for something much bigger?
Ok, time to be blunt. It makes absolutely no sense for people to justify performance challenged code just because of supposed limited row counts or usage. Except for when you purchase an MPP box for perhaps millions of dollars (not to mention the expense of rewriting code to get it to work on such a machine), you can't buy a machine that runs your code 16 times faster (SSD's won't do it either... all this stuff was in high speed memory when we tested it). Performance is in the code. Good performance is in good code.
Can you imagine if all of your code ran "just" 16 times faster?
Never justify bad or performance challenged code on low rowcounts or even low usage. If you do, you might have to borrow one of the windmills I was accused of tilting at to keep your CPUs and disks cool enough. ;-)
A WORD ON THE WORD "TALLY"
Yeah... I agree. Semantically speaking, the Tally Table contains numbers, not "tallies". In my original article on the subject (it wasn't the original article on the technique but it was my first on it), I called it "Tally" not because of what it contains, but because of what it does... it's used to "count" instead of looping and to "Tally" something is to "Count" something. ;-) Call it what you will... Numbers Table, Tally Table, Sequence Table, whatever. I don't care. For me, "Tally" is more meaning full and, being a good lazy DBA, contains only 5 letters (2 are identical) instead of 7 and it's easier to say for most folks. It's also "singular", which follows my naming convention for tables. ;-) It's also what the article that contained a page from a book from the 60's called it. I'll always refer to it as a "Tally Table" and you'll still know what I or someone else means. I also avoid Hungarian Notation like the plague but called the function "fnTally" so that I could say "Well, if you used the eff-en Tally Function I showed you, you wouldn't have a performance problem" without it actually being an HR violation. ;-)
What I'm more concerned about is people learning to use it properly instead of resorting to things like performance challenged rCTEs and other forms of Hidden RBAR.
Você precisará de
RIGHT JOIN
seus dados com uma consulta que retorne um registro para cada hora necessária.Veja algumas maneiras de obter números de linha que você pode subtrair como horas da hora atual .
No Oracle, uma consulta hierárquica em dual gerará linhas: