|
|
|||||||||||||||||||||||||||||
|
Вычисление разницы между показателями даты и времени (исходники)Источник: Windows IT Pro, #04/2007 Ицик Бен Ган
Программисты, работающие с T-SQL, сталкиваются с неожиданно сложной проблемой: как правильно вычислить разницу между двумя значениями дат и времени. SQL Server не предоставляет встроенное решение этой задачи. Поэтому необходимо создать собственное. Хотя функция DATEDIFF SQL Server позволяет вычислить разницу между двумя значениями даты и времени в единицах определенного элемента (т.е. год, месяц, день), она учитывает только определенный элемент и вышестоящие элементы во временной иерархии - но не нижестоящие элементы. Например, если установлен элемент дня, SQL Server будет считать год, месяц и день и игнорировать нижестоящие элементы (час, минута, секунда, миллисекунда). Следующий запрос позволяет установить разницу в годах между двумя значениями, разница между которыми 3 мсек.
SQL Server возвращает ложное значение разности лет 1 (вместо 0) так как функция не проверяет единицы измерения времени меньшие года, но легко вычитает 2005 из 2006. Посмотрим, как получить правильную разность дат, принимая во внимание все единицы. Другими словами, заданы две временных метки - from_ts и to_ts. Посмотрим, как получить разницу между ними в единицах лет, месяцев, дней, часов, минут, секунд и миллисекунд. Сначала проверьте свое решениеНачните решение задачи на определение разницы между датами с выполнения программного кода в Листинге 1. Код наполняет таблицу TimeStamps. Заполните ее примерными данными. В Таблице 1 показано содержание таблицы Time-Stamps. Каждая строка содержит целочисленный ключ (keycol) и пару временных меток (from_ts и to_ts ). Задача состоит в том, чтобы вычислить разницу между временными парами, учитывая все возможные элементы времени, и получить результат, показанный в Таблице 2. Перед тем как посмотреть на мое решение, попытайтесь решить задачу сами. Решение должно обрабатывать варианты, в которых метка from_ts содержит более позднюю дату, чем метка to_ts. В этом случае программный код должен создать негативный результат. Результирующий столбец sgn должен идентифицировать позитивный (1) или негативный (-1) результат. Для экономии места в таблицу среди примерных выходных данных не вписаны значения меток from_ts и to_ts. Эти значения можно вставить обратно в таблицу TimeStamps, которая базируется на keycol. Пошаговые вычисленияПерейдем к моему решению, которое показано в Листинге 2. Сначала рассмотрим внутренний запрос в метке F Листинга 2. Он создает вторичную таблицу D1, показанную в Таблице 3. Цель запроса - поместить большее значение пары временных меток (from_ts, to_ts) в результирующем столбце to_ts и разместить меньшее значение в from_ts. Для этого используются простые выражения CASE. Данная операция предусматривает в дальнейшем упрощение расчетов, гарантируя, что дата в метке from_ts - более ранняя или такая же, как в метке to_ts. Результирующий столбец sgn будет содержать значение 1, если from_ts меньше или равно to_ts и -1, если from_ts больше, чем to_ts. Метка Е показывает запрос, который принимает D1, в качестве своих входных данных, а затем создает вторичную таблицу D2, которая показана в Таблице 4. Запрос использует функцию DATEDIFF() для вычисления разностей элементов дат (т.е год, месяц, день). Следует помнить о том, что разность элементов, которую вычисляет DATEDIFF(), может иметь отклонение от правильного значения разности на 1. Это происходит из-за того, что функция не учитывает элементы, стоящие во временной иерархии ниже заданного элемента. Следующий шаг будет контролировать это отклонение. Запрос в метке D принимает D2 в качестве его входных данных и создает вторичную таблицу D3, которая показана в Таблице 5. Этот запрос использует выражение CASE для каждого элемента даты. Он прибавляет к значению метки from_ts соответственную разность элементов, полученную из предыдущего запроса. Если результат больше, чем в метке to_ts, значит разность, вычисленная функцией DATEDIFF(), была больше правильного значения на 1, тогда программный код вычитает из разности 1. Эта логическая схема, устраняющая неточность в вычислении DATEDIFF(), является ключевым элементом решения. Почему не используют подобную логическую схему для вычисления разности временных элементов в значениях временных меток? Дело в том, что когда речь идет о временных элементах, разность двух временных меток может быть больше, чем может содержать целое число. В этом случае вычисление выйдет за пределы, и временные элементы придется обрабатывать отдельно. Вычисление временных элементовТеперь, когда правильно рассчитаны разности элементов даты, нужно возвратить из каждого элемента только часть, которую не покрывает элемент верхнего уровня. Например, даны временные метки 20030321 14:27:12.233 и 20060115 11:45:22.263. Вычисления в запросе в метке D Листинга 2 выдадут 2 года, 33 месяца и 1030 дней. При этом каждый элемент вычисляется независимо от других. Но чтобы показать результат в объединенных элементах, нужно возвратить только часть месяцев после вычитания элемента более высокого уровня, которая в этом случае составляет 9 месяцев (33 месяца минус 2 года). Подобным образом требуется возвратить только часть дней после вычитания лет и месяцев, которых в данном примере 24 дня (1030 дней минус 2 года и 9 месяцев). В итоге возвратится разница 2 года, 9 месяцев и 24 дня (плюс элементы нижних уровней). Следующие шаги возвращают элементы данных и подготавливают входные данные для расчета временных элементов. Запрос в метке С принимает D3 в качестве своих входных данных и создает вторичную таблицу D4, которая показана в Таблице 6. Этот запрос просто перемещает метку from_ts вперед через каждую из трех разностей элементов дат (г, м, и д), соответственно создавая значения y_ts, m_ts и d_ts. Решение будет использовать каждую из этих временных меток в качестве указателя для возвращения только к нужной части элемента даты. Запрос в метке В принимает D4 в качестве его входных данных и создает вторичную таблицу D5, которая показана в Таблице 7. Этот запрос возвращает только нужную часть каждого элемента даты следующим образом: он вычитает разность метки to_ts и указателя верхнего уровня из отдельного элемента даты. Запрос также вычисляет разницу в секундах (s) между указателем дня и to_ts. Листинг будет использовать s на следующем шаге для вычисления всех временных элементов за исключением миллисекунд. Следующий запрос в метке А принимает D5 в качестве его входных данных и создает окончательный искомый результат. Он показан в Таблице 2. Запрос использует целочисленное деление (/) и по модулю (%) для вычисления элементов часов (h), минут (mi) и секунд (s), основанных на исходной разности значений секунд, полученной из вторичной таблицы D5. Запрос также вычисляет разницу в миллисекундах (ms) вычитая из 1000 элемент миллисекунд метки from_ts плюс элемент миллисекунд метки to_ts по модулю 1000 Число 1000 прибавляется к элементу ms, принадлежащему метке to_ts, а затем рассчитывается по модулю 1000. Это делается на тот случай, если элемент ms, принадлежащий to_ts, меньше, чем элемент ms, принадлежащий from_ts. Выделение логики в ФункциюИтак, мы познакомились с методом правильного вычисления разницы между двумя датами. Приведенную логическую схему можно выделить в функцию, принимающую две даты в качестве входных данных. Теперь надо определить способ возвращения результата. Можно разработать функцию, основанную на значениях таблицы, и возвратить различные элементы дат и знак результата в разные столбцы результирующей строки. Либо можно разработать скалярную функцию, связывающую все выходные элементы в отдельную символьную строку. В Листинге 3 показан пример скалярной функции, вычисляющей разности дат. Единственное изменение, которое нужно сделать в начальном решении, это добавить уровень, задающий формат вывода в качестве скалярного значения, как показано в метке А Листинга 3. Для проверки функции выполните следующий программный код:
Как и множество проблем, затронутых мною в этом разделе, решение сложной задачи по определению разностей дат содержит большое количество логических схем. Ицик Бен-Ган - предподаватель в Solid Quality Learning. Читает лекции и консультирует на международном уровне. Является управляющим израильской группы пользователей SQL Server. MVP по SQL Server. Листинг 1. Creating and Populating the TimeStamps Table
Листинг 2: Query That Calculates Correct Datetime Differences
Листинг 3. Function That Calculates Correct Datetime Differences
Ссылки по теме
|
|