Мы пытаемся оптимизировать дизайн хранилища данных, который будет поддерживать отчетность по данным для многих часовых поясов. Например, у нас может быть отчет за месяц активности (миллионы строк), который должен показывать активность, сгруппированную по часу дня. И, конечно, этот час дня должен быть "местным" часом для данного часового пояса.
У нас был дизайн, который хорошо работал, когда мы просто поддерживали UTC и одно местное время. Стандартный дизайн измерений даты и времени для UTC и местного времени, идентификаторы в таблицах фактов. Однако этот подход, похоже, не масштабируется, если мы должны поддерживать отчетность для более 100 часовых поясов.
Наши таблицы фактов станут очень широкими. Кроме того, нам нужно было бы решить проблему синтаксиса в SQL, указав, какие идентификаторы даты и времени будут использоваться для группировки при любом прогоне отчета. Возможно, очень большое CASE заявление?
Я видел несколько предложений, чтобы получить все данные по временному диапазону UTC, который вы охватили, а затем вернуть их на уровень представления для преобразования в локальное и агрегирования там, но ограниченное тестирование с использованием SSRS предполагает, что это будет очень медленно.
Я также проконсультировался с некоторыми книгами по этому вопросу, и все они, кажется, говорят, что у вас есть UTC и конвертируется на дисплее, или есть UTC и один местный. Буду признателен за любые мысли и предложения.
Примечание. Этот вопрос похож на: Обработка часовых поясов в витрине / хранилище данных , но я не могу комментировать этот вопрос, поэтому счел, что это заслуживает отдельного вопроса.
Обновление: я выбрал ответ Аарона после того, как он сделал несколько значительных обновлений и опубликовал пример кода и диаграмм. Мои предыдущие комментарии к его ответу больше не будут иметь особого смысла, поскольку они ссылались на оригинальную редакцию ответа. Я постараюсь вернуться и обновить это снова, если это оправдано
Ответы:
Я решил эту проблему, имея очень простую календарную таблицу - каждый год имеет одну строку для каждого поддерживаемого часового пояса со стандартным смещением и датой начала / окончания даты DST и его смещением (если этот часовой пояс поддерживает это). Затем встроенная привязанная к схеме табличная функция, которая берет время источника (конечно, в UTC) и добавляет / вычитает смещение.
Это, очевидно, никогда не будет работать очень хорошо, если вы сообщаете о большой части данных; разделение может показаться полезным, но у вас все еще будут случаи, когда последние несколько часов в году или первые несколько часов в следующем году на самом деле принадлежат другому году при преобразовании в определенный часовой пояс - так что вы никогда не сможете получить истинный раздел изоляция, за исключением случаев, когда ваш диапазон отчетности не включает 31 декабря или 1 января.
Есть несколько странных крайних случаев, которые вы должны рассмотреть:
2014-11-02 05:30 UTC и 2014-11-02 06:30 UTC, например, конвертируются в 01:30 в восточном часовом поясе (например, первый раз в 01:30 был достигнут локально, а затем один во второй раз, когда часы откатились с 2:00 до 1:00, и прошло еще полчаса). Таким образом, вам нужно решить, как обрабатывать этот час отчетности - согласно UTC, вы должны увидеть удвоение трафика или объема того, что вы измеряете, после того, как эти два часа сопоставлены с одним часом в часовом поясе, в котором наблюдается DST. Это также может играть в забавные игры с последовательностью событий, поскольку что-то, что логически должно было произойти после того, как могло появиться что-то ещепроизойдет до того, как время будет установлено на один час вместо двух. Крайним примером является просмотр страницы, который произошел в 05:59 UTC, а затем щелчок, произошедший в 06:00 UTC. В UTC это происходило с интервалом в одну минуту, но при преобразовании в восточное время просмотр происходил в 1:59, а щелчок происходил на час раньше.
2014-03-09 02:30 в США никогда не бывает Это потому, что в 2:00 утра мы переводим часы вперед на 3:00 утра. Скорее всего, вы захотите вызвать ошибку, если пользователь введет такое время и попросит вас преобразовать его в UTC или спроектировать форму так, чтобы пользователи не могли выбрать такое время.
Даже с учетом этих крайних случаев, я все еще думаю, что у вас есть правильный подход: хранить данные в UTC. Гораздо проще сопоставить данные с другими часовыми поясами из UTC, чем из некоторого часового пояса в другой часовой пояс, особенно когда разные часовые пояса начинают / заканчивают летнее время в разные даты, и даже один и тот же часовой пояс может переключаться с использованием разных правил в разные годы ( например, США изменили правила 6 лет назад или около того).
Вы захотите использовать таблицу календаря для всего этого, а не какое-то гигантское
CASE
выражение (не утверждение ). Я только что написал серию из трех частей для MSSQLTips.com по этому вопросу; Думаю, 3-я часть будет наиболее полезной для вас: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/
Настоящий живой пример, тем временем
Допустим, у вас есть очень простая таблица фактов. Единственный факт, который меня волнует в этом случае - это время события, но я добавлю бессмысленный GUID, чтобы таблица была достаточно широкой, чтобы о ней заботиться. Опять же, чтобы быть явным, таблица фактов хранит события только по времени UTC и UTC. Я даже добавил суффикс столбца,
_UTC
чтобы не было путаницы.Теперь давайте загрузим нашу таблицу фактов с 10 000 000 строк, представляющих каждые 3 секунды (1200 строк в час) с 2013-12-30 в полночь по UTC до примерно после 5:00 UTC 2014-12-12. Это гарантирует, что данные пересекают границу года, а также DST вперед и назад для нескольких часовых поясов. Это выглядит действительно страшно, но в моей системе это заняло ~ 9 секунд. Таблица должна быть около 325 МБ.
И просто чтобы показать, как будет выглядеть типичный поисковый запрос для этой таблицы строк размером 10 мм, если я выполню этот запрос:
Я получаю этот план, и он возвращается через 25 миллисекунд *, выполняя 358 операций чтения, и возвращает 72 ежечасных результата:
* Длительность измеряется нашим бесплатным SQL Sentry Plan Explorer , который отбрасывает результаты, так что сюда не входит время передачи данных по сети, рендеринг и т. Д. В качестве дополнительного отказа я работаю для SQL Sentry.
Очевидно, это займет немного больше времени, если я сделаю свой диапазон слишком большим - месяц данных занимает 258 мс, два месяца - более 500 мс и так далее. Параллелизм может вызвать:
Здесь вы начинаете думать о других, более эффективных решениях для удовлетворения запросов отчетности, и это не имеет никакого отношения к тому, в каком часовом поясе будут отображаться ваши выходные данные. Я не буду вдаваться в подробности, я просто хочу продемонстрировать, что преобразование часового пояса на самом деле не заставит ваши запросы отчетности отстать намного больше, и они могут уже отстой, если вы получаете большие диапазоны, которые не поддерживаются должным образом. индексов. Я собираюсь придерживаться небольших диапазонов дат, чтобы показать, что логика верна, и позволить вам беспокоиться о том, чтобы ваши отчеты о запросах на основе диапазонов работали адекватно, с преобразованиями часовых поясов или без них.
Хорошо, теперь нам нужны таблицы для хранения наших часовых поясов (со смещением, в минутах, поскольку не у всех есть даже часы по Гринвичу) и даты изменения летнего времени для каждого поддерживаемого года. Для простоты я собираюсь ввести только несколько часовых поясов и один год, чтобы соответствовать данным выше.
Включено несколько часовых поясов для разнообразия, некоторые со смещением в полчаса, некоторые не соблюдают летнее время. Обратите внимание, что в Австралии в южном полушарии наблюдается летнее время в течение нашей зимы, поэтому их часы возвращаются в апреле и вперед в октябре. (Таблица выше переворачивает названия, но я не уверен, как сделать это менее запутанным для часовых поясов южного полушария.)
Теперь, календарь таблицы, чтобы знать, когда TZ меняются. Я только собираюсь вставить интересующие строки (каждый часовой пояс выше, и только изменения летнего времени за 2014 год). Для простоты вычислений я сохраняю момент в UTC, где меняется часовой пояс, и один и тот же момент в местном времени. Для часовых поясов, которые не соблюдают летнее время, это стандартно в течение всего года, и летнее время «начинается» с 1 января.
Вы можете определенно заполнить это алгоритмами (и в следующей серии советов используются некоторые умные методы, основанные на множествах, если я так скажу), а не зацикливаться, заполнять вручную, что у вас. Для этого ответа я решил просто вручную ввести один год для пяти часовых поясов, и я не собираюсь прибегать к каким-либо хитроумным трюкам.
Итак, у нас есть данные фактов и таблицы «измерений» (я съеживаюсь, когда говорю это), так в чем же логика? Что ж, я предполагаю, что вы будете предлагать пользователям выбирать свой часовой пояс и вводить диапазон дат для запроса. Я также предполагаю, что диапазон дат будет полными днями в их собственном часовом поясе; никаких неполных дней, не говоря уже о неполных часах. Таким образом, они передадут дату начала, дату окончания и TimeZoneID. Оттуда мы будем использовать скалярную функцию для преобразования даты начала / окончания из этого часового пояса в UTC, что позволит нам фильтровать данные на основе диапазона UTC. После того, как мы это сделали и выполнили наши агрегации, мы можем применить преобразование сгруппированных времен назад к часовому поясу источника, прежде чем отобразить его пользователю.
Скаляр UDF:
И табличная функция:
И процедура, которая его использует ( edit : updated для обработки 30-минутной группировки смещений):
(Возможно, вы захотите попробовать там короткое замыкание или отдельную хранимую процедуру, если пользователь хочет отправлять отчеты в UTC - очевидно, что перевод в и из UTC будет расточительной занятой работой.)
Образец звонка:
Возвращает в 41мс *, и генерирует этот план:
* Опять же, с отброшенными результатами.
В течение 2 месяцев он возвращается через 507 мс, и план идентичен, кроме количества строк:
Несмотря на то, что он немного более сложный и немного увеличивает время выполнения, я довольно уверен, что этот тип подхода сработает намного, намного лучше, чем подход с промежуточным столом. И это случайный пример ответа dba.se; Я уверен, что моя логика и эффективность могут быть улучшены людьми намного умнее меня.
Вы можете просмотреть данные, чтобы увидеть граничные случаи, о которых я говорю - нет строки вывода для часа, в котором часы идут вперед, две строки для часа, когда они откатились (и этот час произошел дважды). Вы также можете играть с плохими ценностями; например, если вы уйдете в 20140309 02:30 по восточному времени, это не сработает.
Возможно, у меня не все правильные предположения о том, как будут работать ваши отчеты, поэтому вам, возможно, придется внести некоторые коррективы. Но я думаю, что это охватывает основы.
источник
Можете ли вы сделать преобразование в сохраненном процессе или параметризованном представлении вместо уровня представления? Другой вариант - создать куб и выполнить вычисления в кубе.
Объяснение из комментариев:
источник