Estamos tentando otimizar um design de data warehouse que ofereça suporte a relatórios de dados por muitos fusos horários. Por exemplo, podemos ter um relatório de um mês de atividade (milhões de linhas) que precisa mostrar a atividade agrupada por hora do dia. E é claro que a hora do dia deve ser a hora "local" para o fuso horário especificado.
Tivemos um design que funcionou bem quando apenas suportamos o UTC e uma hora local. O design padrão das dimensões Data e Hora para UTC e hora local, IDs nas tabelas de fatos. No entanto, essa abordagem não parece escalável se precisarmos suportar relatórios para mais de 100 fusos horários.
Nossas tabelas de fatos ficariam muito amplas. Além disso, teríamos que resolver o problema de sintaxe no SQL para especificar quais IDs de data e hora usar para agrupar em qualquer execução do relatório. Talvez uma declaração CASE muito grande?
Vi algumas sugestões para obter todos os dados pelo intervalo de horário UTC que você está cobrindo e depois retorná-lo à camada de apresentação para converter em local e agregado, mas testes limitados com o SSRS sugerem que será extremamente lento.
Também consultei alguns livros sobre o assunto, e todos parecem dizer que apenas têm UTC e convertem em exibição ou têm UTC e um local. Gostaria de receber quaisquer pensamentos e sugestões.
Nota: Esta pergunta é semelhante a: Manipulação de fusos horários no data mart / warehouse , mas não posso comentar sobre essa pergunta, então senti que isso merecia sua própria pergunta.
Atualização: selecionei a resposta de Aaron depois que ele fez algumas atualizações significativas e publicou exemplos de código e diagramas. Meus comentários anteriores sobre a resposta dele não farão mais muito sentido, pois se referiram à edição original da resposta. Vou tentar voltar e atualizar isso novamente, se necessário
Respostas:
Resolvi isso com uma tabela de calendário muito simples - cada ano tem uma linha por fuso horário suportado , com o deslocamento padrão e o horário de início / fim do horário de verão do DST e seu deslocamento (se esse fuso horário suportar). Em seguida, uma função embutida, vinculada ao esquema e com valor de tabela, que leva o tempo de origem (no UTC, é claro) e adiciona / subtrai o deslocamento.
Obviamente, isso nunca terá um desempenho extremamente bom se você estiver reportando uma grande parte dos dados; o particionamento pode parecer útil, mas você ainda terá casos em que as últimas horas em um ano ou as primeiras horas no próximo ano pertencem a um ano diferente quando convertidas em um fuso horário específico - para que você nunca possa obter uma partição verdadeira isolamento, exceto quando o intervalo de relatórios não incluir 31 de dezembro ou 1º de janeiro.
Existem alguns casos extremos estranhos que você precisa considerar:
2014-11-02 05:30 UTC e 2014-11-02 06:30 UTC, ambos convertem para 01:30 no fuso horário do leste, por exemplo (um pela primeira vez 01:30 foi atingido localmente e depois um pela segunda vez, quando os relógios voltaram das 2:00 às 1:00 e mais meia hora se passou). Portanto, você precisa decidir como lidar com essa hora de geração de relatórios - de acordo com a UTC, você deve ver o dobro do tráfego ou volume do que estiver medindo quando essas duas horas forem mapeadas para uma única hora em um fuso horário que observe o horário de verão. Isso também pode jogar jogos divertidos com a sequência de eventos, já que algo que logicamente teve que acontecer depois que algo mais pudesse apareceracontecer antes que o tempo seja ajustado para uma única hora em vez de duas. Um exemplo extremo é uma exibição de página que aconteceu às 05:59 UTC e, em seguida, um clique que aconteceu às 06:00 UTC. No horário UTC, eles aconteciam com um minuto de diferença, mas, quando convertidos para o horário do leste, a exibição acontecia às 1:59 e o clique ocorria uma hora antes.
2014-03-09 02:30 nunca acontece nos EUA. Isso ocorre porque às 02:00 rolamos os relógios para as 03:00. É provável que você queira gerar um erro se o usuário digitar esse horário e solicitar que você o converta para UTC ou crie seu formulário para que os usuários não possam escolher esse horário.
Mesmo com esses casos extremos em mente, ainda acho que você tem a abordagem correta: armazene os dados no UTC. Muito mais fácil mapear dados para outros fusos horários do UTC do que de algum fuso horário para outro fuso horário, especialmente quando fusos horários diferentes iniciam / encerram o horário de verão em datas diferentes e até o mesmo fuso horário pode alternar usando regras diferentes em anos diferentes ( por exemplo, os EUA mudaram as regras há seis anos).
Você desejará usar uma tabela de calendário para tudo isso, não alguma
CASE
expressão gigantesca (não declaração ). Acabei de escrever uma série de três partes para o MSSQLTips.com sobre isso; Eu acho que a terceira parte será a mais útil para você:http://www.mssqltips.com/sqlservertip/3173/handle-conversion-between-time-zones-in-sql-server--part-1/
http://www.mssqltips.com/sqlservertip/3174/handle-conversion-between-time-zones-in-sql-server--part-2/
http://www.mssqltips.com/sqlservertip/3175/handle-conversion-between-time-zones-in-sql-server--part-3/
Um verdadeiro exemplo ao vivo, enquanto isso
Digamos que você tenha uma tabela de fatos muito simples. O único fato que me interessa nesse caso é a hora do evento, mas adicionarei um GUID sem sentido apenas para tornar a tabela suficientemente ampla para se preocupar. Novamente, para ser explícito, a tabela de fatos armazena eventos apenas na hora UTC e na hora UTC. Eu mesmo coloquei o sufixo na coluna
_UTC
para que não haja confusão.Agora, vamos carregar nossa tabela de fatos com 10.000.000 de linhas - representando a cada 3 segundos (1.200 linhas por hora) de 30/12/2013 à meia-noite UTC até um pouco depois das 05:00 UTC em 12/12/2014. Isso garante que os dados ultrapassem o limite de um ano, bem como o horário de verão para a frente e para trás em vários fusos horários. Isso parece realmente assustador, mas levou ~ 9 segundos no meu sistema. A tabela deve acabar tendo cerca de 325 MB.
E apenas para mostrar como será uma consulta de pesquisa típica nessa tabela de linhas de 10MM, se eu executar esta consulta:
Eu recebo esse plano e ele retorna em 25 milissegundos *, fazendo 358 leituras, para retornar 72 totais por hora:
* Duração conforme medida pelo nosso SQL Sentry Plan Explorer gratuito , que descarta os resultados; portanto, isso não inclui o tempo de transferência de dados, renderização etc. da rede. Como um aviso adicional, trabalho para o SQL Sentry.
Obviamente, levará um pouco mais de tempo se eu aumentar meu alcance - um mês de dados leva 258ms, dois meses leva 500ms e assim por diante. O paralelismo pode entrar em ação:
É aqui que você começa a pensar em outras soluções melhores para atender às consultas de relatórios e isso não tem nada a ver com o fuso horário em que a saída será exibida. Eu não vou entrar nisso, só quero demonstrar que a conversão de fuso horário realmente não fará com que suas consultas de relatórios sejam muito mais difíceis, e elas já podem ser ruins se você estiver obtendo grandes intervalos que não são compatíveis com o adequado índices. Vou manter pequenos intervalos de datas para mostrar que a lógica está correta e deixar você se preocupar em garantir que suas consultas de relatórios com base em intervalos tenham um desempenho adequado, com ou sem conversões de fuso horário.
Ok, agora precisamos de tabelas para armazenar nossos fusos horários (com deslocamentos, em minutos, já que nem todo mundo fica horas fora do UTC) e o horário de verão altera as datas para cada ano suportado. Para simplificar, vou inserir apenas alguns fusos horários e um único ano para corresponder aos dados acima.
Incluiu alguns fusos horários para variedade, alguns com desvios de meia hora, outros que não observam o horário de verão. Observe que a Austrália, no hemisfério sul, observa o horário de verão durante o inverno, então seus relógios voltam em abril e avançam em outubro. (A tabela acima inverte os nomes, mas não tenho certeza de como tornar isso menos confuso para os fusos horários do hemisfério sul.)
Agora, uma tabela de calendário para saber quando as TZs mudam. Vou inserir apenas linhas de interesse (cada fuso horário acima e apenas as alterações de horário de verão para 2014). Para facilitar os cálculos, eu armazeno o momento no UTC em que um fuso horário muda e o mesmo momento no horário local. Para fusos horários que não observam o horário de verão, é padrão o ano todo e o horário de verão "começa" em 1º de janeiro.
Você pode definitivamente preencher isso com algoritmos (e a próxima série de dicas usa algumas técnicas inteligentes baseadas em conjuntos, se é que eu digo), em vez de fazer um loop, preencher manualmente, o que você tem. Para esta resposta, decidi preencher manualmente um ano nos cinco fusos horários, e não vou incomodar nenhum truque sofisticado.
Ok, então temos nossos dados de fatos e nossas tabelas de "dimensão" (eu me encolho quando digo isso), então qual é a lógica? Bem, presumo que os usuários selecionem seu fuso horário e insiram o período da consulta. Também assumirei que o período será de dias inteiros no fuso horário; sem dias parciais, não importa horas parciais. Então eles passarão em uma data de início, uma data de término e um TimeZoneID. A partir daí, usaremos uma função escalar para converter a data de início / término desse fuso horário em UTC, o que nos permitirá filtrar os dados com base no intervalo UTC. Depois de fazer isso e executar nossas agregações, podemos aplicar a conversão dos tempos agrupados de volta ao fuso horário de origem, antes de exibir para o usuário.
O UDF escalar:
E a função com valor de tabela:
E um procedimento que o utiliza ( edit : updated para lidar com o agrupamento de deslocamento de 30 minutos):
(Você pode tentar entrar em curto-circuito lá, ou um procedimento armazenado separado, caso o usuário deseje gerar relatórios no UTC - obviamente, a conversão de e para o UTC será um trabalho muito trabalhoso).
Exemplo de chamada:
Retorna em 41ms * e gera este plano:
* Novamente, com resultados descartados.
Por 2 meses, ele retorna em 507ms, e o plano é idêntico, exceto contas de linha:
Embora um pouco mais complexo e aumentando o tempo de execução, estou bastante confiante de que esse tipo de abordagem funcionará muito, muito melhor do que a abordagem da tabela de pontes. E este é um exemplo imediato para uma resposta dba.se; Tenho certeza de que minha lógica e eficiência podem ser melhoradas por pessoas muito mais inteligentes que eu.
Você pode ler os dados para ver os casos extremos dos quais falo - nenhuma linha de saída para a hora em que os relógios avançam, duas linhas para a hora em que eles revertem (e essa hora aconteceu duas vezes). Você também pode jogar com valores ruins; se você passar 20140309 02:30 no horário do leste, por exemplo, não vai funcionar muito bem.
Talvez eu não tenha todas as suposições corretas sobre como seus relatórios funcionarão, portanto, talvez você precise fazer alguns ajustes. Mas acho que isso cobre o básico.
fonte
Você pode fazer a transformação em um processo armazenado ou em uma visualização parametrizada em vez da camada de apresentação? Outra opção é criar um cubo e ter os cálculos em cubo.
Explicação dos comentários:
fonte