Estamos tratando de optimizar un diseño de depósito de datos que admitirá la presentación de informes contra datos para muchas zonas horarias. Por ejemplo, podríamos tener un informe de un mes de actividad (millones de filas) que necesita mostrar la actividad agrupada por hora del día. Y, por supuesto, esa hora del día tiene que ser la hora "local" para la zona horaria dada.
Teníamos un diseño que funcionaba bien cuando solo admitíamos UTC y una hora local. El diseño estándar de las dimensiones de fecha y hora para UTC y hora local, identificación en las tablas de hechos. Sin embargo, ese enfoque no parece escalar si tenemos que admitir informes para más de 100 zonas horarias.
Nuestras tablas de hechos serían muy amplias. Además, tendríamos que resolver el problema de sintaxis en SQL de especificar qué id. De fecha y hora usar para agrupar en cualquier ejecución del informe. Tal vez una declaración de CASO muy grande?
He visto algunas sugerencias para obtener todos los datos por el rango de tiempo UTC que está cubriendo, luego devolverlo a la capa de presentación para convertirlo a local y agregado allí, pero las pruebas limitadas con SSRS sugieren que será extremadamente lento.
También he consultado algunos libros sobre el tema, y todos parecen decir que solo tienen UTC y convertir en exhibición o tienen UTC y uno local. Agradecería cualquier pensamiento y sugerencia.
Nota: Esta pregunta es similar a: Manejo de zonas horarias en data mart / warehouse , pero no puedo comentar sobre esa pregunta, por lo que sentí que merecía su propia pregunta.
Actualización: Seleccioné la respuesta de Aaron después de que realizó algunas actualizaciones significativas y publicó códigos de muestra y diagramas. Mis comentarios anteriores sobre su respuesta ya no tendrán mucho sentido, ya que se referían a la edición original de la respuesta. Intentaré volver y actualizar esto nuevamente si se justifica
Respuestas:
He resuelto esto teniendo una tabla de calendario muy simple: cada año tiene una fila por zona horaria admitida , con el desplazamiento estándar y la fecha / hora de inicio / finalización del horario de verano y su desplazamiento (si esa zona horaria lo admite). Luego, una función en línea, vinculada al esquema y con valores de tabla que toma el tiempo de origen (en UTC, por supuesto) y suma / resta el desplazamiento.
Obviamente, esto nunca funcionará extremadamente bien si está informando sobre una gran parte de los datos; La partición puede parecer útil, pero aún tendrá casos en los que las últimas horas en un año o las primeras horas en el próximo año realmente pertenecen a un año diferente cuando se convierten a una zona horaria específica, por lo que nunca puede obtener una partición verdadera aislamiento, excepto cuando su rango de informes no incluye el 31 de diciembre o el 1 de enero.
Hay un par de casos extraños que debes considerar:
2014-11-02 05:30 UTC y 2014-11-02 06:30 UTC ambos se convierten a 01:30 AM en la zona horaria del Este, por ejemplo (uno por primera vez 01:30 fue golpeado localmente, y luego uno por segunda vez cuando los relojes retrocedieron de las 2:00 a.m. a la 1:00 a.m., y transcurrió otra media hora). Por lo tanto, debe decidir cómo manejar esa hora de informes; de acuerdo con UTC, debería ver el doble del tráfico o el volumen de lo que esté midiendo una vez que esas dos horas se asignen a una sola hora en una zona horaria que observe el horario de verano. Esto también puede jugar juegos divertidos con secuencia de eventos, ya que algo que lógicamente tuvo que suceder después de que algo más pudiera aparecerocurrir antes de eso una vez que el tiempo se ajusta a una sola hora en lugar de dos. Un ejemplo extremo es una vista de página que ocurrió a las 05:59 UTC, luego un clic que ocurrió a las 06:00 UTC. En la hora UTC, esto sucedió con un minuto de diferencia, pero cuando se convirtió a la hora del Este, la vista ocurrió a la 1:59 a.m., y el clic ocurrió una hora antes.
2014-03-09 02:30 nunca sucede en los Estados Unidos. Esto se debe a que a las 2:00 a.m., adelantamos los relojes a las 3:00 a.m. Por lo tanto, es probable que desee generar un error si el usuario ingresa ese tiempo y le pide que lo convierta a UTC, o que diseñe su formulario para que los usuarios no puedan elegir ese tiempo.
Incluso con esos casos límite en mente, sigo pensando que tiene el enfoque correcto: almacenar los datos en UTC. Es mucho más fácil asignar datos a otras zonas horarias desde UTC que desde alguna zona horaria a otra zona horaria, especialmente cuando diferentes zonas horarias comienzan / terminan el horario de verano en diferentes fechas, e incluso la misma zona horaria puede cambiar usando diferentes reglas en diferentes años ( por ejemplo, EE. UU. cambió las reglas hace aproximadamente 6 años).
Deberá usar una tabla de calendario para todo esto, no una
CASE
expresión gigantesca (no una declaración ). Acabo de escribir una serie de tres partes para MSSQLTips.com sobre esto; Creo que la tercera parte será la más útil para ti: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/
Un verdadero ejemplo en vivo, mientras tanto
Digamos que tiene una tabla de hechos muy simple. El único hecho que me importa en este caso es el tiempo del evento, pero agregaré un GUID sin sentido solo para hacer que la tabla sea lo suficientemente amplia como para preocuparse. Nuevamente, para ser explícitos, la tabla de hechos almacena eventos en tiempo UTC y solo en tiempo UTC. Incluso he agregado el sufijo a la columna
_UTC
para que no haya confusión.Ahora, carguemos nuestra tabla de hechos con 10,000,000 filas, que representan cada 3 segundos (1,200 filas por hora) desde 2013-12-30 a la medianoche UTC hasta algún momento después de las 5 AM UTC del 2014-12-12. Esto garantiza que los datos abarquen un límite de un año, así como el horario de verano hacia adelante y hacia atrás para múltiples zonas horarias. Esto parece realmente aterrador, pero tardó ~ 9 segundos en mi sistema. La tabla debería terminar siendo de unos 325 MB.
Y solo para mostrar cómo se verá una consulta de búsqueda típica en esta tabla de filas de 10MM, si ejecuto esta consulta:
Recibo este plan, y regresa en 25 milisegundos *, haciendo 358 lecturas, para devolver 72 totales por hora:
* Duración medida por nuestro Explorador de planes de SQL Sentry gratuito , que descarta los resultados, por lo que esto no incluye el tiempo de transferencia de red de los datos, la representación, etc. Como descargo de responsabilidad adicional, trabajo para SQL Sentry.
Obviamente, tarda un poco más si hago que mi rango sea demasiado grande: un mes de datos tarda 258 ms, dos meses lleva más de 500 ms, y así sucesivamente. El paralelismo puede entrar en acción:
Aquí es donde comienza a pensar en otras soluciones mejores para satisfacer las consultas de informes, y no tiene nada que ver con la zona horaria que mostrará su salida. No voy a entrar en eso, solo quiero demostrar que la conversión de zona horaria realmente no hará que sus consultas de informes absorban mucho más, y es posible que ya lo hagan si obtiene grandes rangos que no son compatibles con el adecuado índices Me limitaré a los pequeños intervalos de fechas para mostrar que la lógica es correcta y dejar que se preocupe por asegurarse de que sus consultas de informes basadas en el rango funcionen adecuadamente, con o sin conversiones de zona horaria.
Bien, ahora necesitamos tablas para almacenar nuestras zonas horarias (con compensaciones, en minutos, ya que no todos están incluso horas sin UTC) y las fechas de cambio de horario de verano para cada año admitido. Para simplificar, solo voy a ingresar unas pocas zonas horarias y un solo año para que coincida con los datos anteriores.
Incluyó algunas zonas horarias para la variedad, algunas con compensaciones de media hora, algunas que no observan el horario de verano. Tenga en cuenta que Australia, en el hemisferio sur, observa el horario de verano durante nuestro invierno, por lo que sus relojes retroceden en abril y avanzan en octubre. (La tabla anterior voltea los nombres, pero no estoy seguro de cómo hacer que esto sea menos confuso para las zonas horarias del hemisferio sur).
Ahora, una tabla de calendario para saber cuándo cambian las TZ. Solo voy a insertar filas de interés (cada zona horaria anterior, y solo los cambios de horario de verano para 2014). Para facilitar los cálculos de ida y vuelta, almaceno el momento en UTC donde cambia la zona horaria y el mismo momento en la hora local. Para las zonas horarias que no observan el horario de verano, es estándar durante todo el año, y el horario de verano "comienza" el 1 de enero.
Definitivamente, puede completar esto con algoritmos (y la próxima serie de consejos utiliza algunas técnicas inteligentes basadas en conjuntos, si lo digo yo mismo), en lugar de bucle, complete manualmente, ¿qué tiene? Para esta respuesta, decidí rellenar manualmente un año para las cinco zonas horarias, y no voy a molestarme con ningún truco elegante.
Bien, entonces tenemos nuestros datos de hechos y nuestras tablas de "dimensiones" (me estremezco cuando digo eso), entonces, ¿cuál es la lógica? Bueno, supongo que va a hacer que los usuarios seleccionen su zona horaria e ingresen el rango de fechas para la consulta. También supondré que el rango de fechas será días completos en su propia zona horaria; sin días parciales, no importa las horas parciales. Por lo tanto, pasarán una fecha de inicio, una fecha de finalización y un TimeZoneID. A partir de ahí, utilizaremos una función escalar para convertir la fecha de inicio / finalización de esa zona horaria a UTC, lo que nos permitirá filtrar los datos en función del rango UTC. Una vez que hayamos hecho eso, y hayamos realizado nuestras agregaciones en él, podemos aplicar la conversión de los tiempos agrupados nuevamente a la zona horaria de origen, antes de mostrar al usuario.
El UDF escalar:
Y la función con valores de tabla:
Y un procedimiento que lo usa ( editar : actualizado para manejar la agrupación de desplazamiento de 30 minutos):
(Es posible que desee realizar un cortocircuito allí, o un procedimiento almacenado por separado, en el caso de que el usuario quiera informar en UTC; obviamente, la traducción hacia y desde UTC será un trabajo muy ocupado).
Llamada de muestra:
Devuelve en 41 ms * y genera este plan:
* Nuevamente, con resultados descartados.
Durante 2 meses, regresa en 507 ms, y el plan es idéntico aparte de los recuentos de filas:
Si bien es un poco más complejo y aumenta un poco el tiempo de ejecución, estoy bastante seguro de que este tipo de enfoque funcionará mucho, mucho mejor que el enfoque de la mesa de bridge. Y este es un ejemplo poco convencional para una respuesta dba.se; Estoy seguro de que mi lógica y eficiencia podrían ser mejoradas por personas mucho más inteligentes que yo.
Puede leer detenidamente los datos para ver los casos límite de los que hablo: no hay fila de salida para la hora en que los relojes avanzan, dos filas para la hora en que retrocedieron (y esa hora sucedió dos veces). También puedes jugar con malos valores; si pasa en 20140309 02:30 hora del este, por ejemplo, no va a funcionar demasiado bien.
Es posible que no tenga todas las suposiciones correctas sobre cómo funcionarán sus informes, por lo que es posible que deba hacer algunos ajustes. Pero creo que esto cubre lo básico.
fuente
¿Se puede hacer la transformación en un proceso almacenado o una vista parametrizada en lugar de una capa de presentación? Otra opción es crear un cubo y tener los cálculos en cubo.
Explicación de los comentarios:
fuente