Перейти к содержанию

Календарь

Календарь

Календарь это набор специального инструментария, который позволяет легко решать задачи по управлению и получению информации связанной с рабочим и нерабочим временем. Календарь оперирует датами исключительно в формате UTC. Так же стоит помнить, что календарь рассчитывается с расчётом на московское время. Календарь представляет из себя табличку, в которой записана заранее рассчитанная информация о том, является ли момент времени рабочим или нет.

Квант времени

Для отражения момента времени используется специальное понятие кванта времени. Существует два типа квантов времени - рабочий и не рабочий.

  • Рабочий квант - Длится 15 минут. На такие кванты разбит весь рабочий день.

  • Нерабочий квант - длится всё время с момента конца предыдущего рабочего кванта и до момента начала следующего рабочего кванта.

Таблица с квантами времени имеет следующую структуру:

  • QuantNumber - номер кванта.

  • StartTimeUTC – дата/время начала кванта (включительно).

  • EndTimeUTC – дата/время окончания кванта (не включительно).

  • Type - Тип кванта (0 - рабочее время, 1 - выходной).

Рассчитанные кванты придерживаются нескольких основных правил:

  • Номера квантов идут по порядку и неразрывны.

  • Кванты нерабочего времени имеют те же номера, что и идущий перед ним квант.

  • Рабочее время разбито на кванты по 15 минут, нерабочее время заносится единым квантом.

  • Между квантами не должно быть промежутка по времени. Конец одного кванта равен началу следующего за ним кванта.

Для примера рассмотрим отрывок из рассчитанного календаря:

QuantNumber
StartTimeUTC EndTimeUTC Type
0 2014-01-01 00:00:00 2014-01-01 05:00:00 1
1 2014-01-01 05:00:00 2014-01-01 05:15:00 0
2 2014-01-01 05:15:00 2014-01-01 05:30:00 0
3 2014-01-01 05:30:00 2014-01-01 05:45:00 0
14 2014-01-01 08:15:00 2014-01-01 08:30:00 0
15 2014-01-01 08:30:00 2014-01-01 08:45:00 0
16 2014-01-01 08:45:00 2014-01-01 09:00:00 0
16 2014-01-01 09:00:00 2014-01-01 11:00:00 1
17 2014-01-01 11:00:00 2014-01-01 11:15:00 0
18 2014-01-01 11:15:00 2014-01-01 11:30:00 0
19 2014-01-01 11:30:00 2014-01-01 11:45:00 0
22 2014-01-01 12:15:00 2014-01-01 12:30:00 0
30 2014-01-01 14:15:00 2014-01-01 14:30:00 0
31 2014-01-01 14:30:00 2014-01-01 14:45:00 0
32 2014-01-01 14:45:00 2014-01-01 15:00:00 0
32 2014-01-01 15:00:00 2014-01-06 05:00:00 1
33 2014-01-06 05:00:00 2014-01-06 05:15:00 0
34 2014-01-06 05:15:00 2014-01-06 05:30:00 0
35 2014-01-06 05:30:00 2014-01-06 05:45:00 0
48 2014-01-06 08:45:00 2014-01-06 09:00:00 0
48 2014-01-06 09:00:00 2014-01-06 11:00:00 1
49 2014-01-06 11:00:00 2014-01-06 11:15:00 0
64 2014-01-06 14:45:00 2014-01-06 15:00:00 0
64 2014-01-06 15:00:00 2014-01-07 05:00:00 1
65 2014-01-07 05:00:00 2014-01-07 05:15:00 0

Важно помнить, что календарь оперирует временем в UTC. Видно, что квант с номером 0, - квант нерабочего времени, он заканчивается 2014-01-01 05:00. Следом за ним идут кванты рабочего времени по 15 минут, до момента 2014-01-01 09:00, когда начнётся обед (квант нерабочего времени), который длится до 2014-01-01 11:00. Далее до 2014-01-01 15:00 идут снова кванты рабочего времени по 15 минут, пока не появится квант нерабочего времени, отражающий ночь, и длящийся до 2014-01-06 05:00.

Карточка настроек календаря

Для настройки календаря в системе есть специальная карточка настроек.
Она содержит в себе:

  • Таблица исключений.

  • Период действия календаря.

  • Интервалы рабочего дня и обеденного перерыва.

  • Кнопки, вызывающие пересчёт календаря и проверку целостности рассчитанного календаря.

Подробнее о настройке календаря см. Руководство администратора.

Работа с календарём

Для работы с календарём существует специальное API - IBusinessCalendarService и набор хранимых функций и процедур. Для использования API - достаточно отрезолвить его из Unity. Так как в большинстве случаев API календаря используется внутри расширений - достаточно запросить его в конструкторе расширения.

class SomeExtension : CardGetExtension { private readonly IBusinessCalendarService calendarService;

public CalendarCardButtonsExtension( IBusinessCalendarService calendarService) { this.calendarService = calendarService; } // ... }

Далее можно использовать любой из предоставленных в API методов. Так же есть специальный набор хранимых процедур, которые можно использовать внутри sql-запросов.

Проверить - является ли рабочим указанное в UTC дата/время

При помощи функции IsWorkTime - можно проверить, является ли указанный момент времени рабочим.

/// <summary> /// Проверить - является ли рабочим указанное дата\время в UTC. /// </summary> /// <param name="dateTimeUTC">Дата\время в UTC.</param> /// <returns> <see cref="BusinessCalendarTimeType"/> /// Work - рабочее время. /// Holiday - нерабочее время.</returns> BusinessCalendarTimeType IsWorkTime(DateTime dateTimeUTC);

Функция принимает параметр dateTimeUTC DateTime и на выходе возвращает значение перечисления BusinessCalendarTimeType.

public enum BusinessCalendarTimeType { Work, // Рабочее время Holiday // Нерабочее время }

Так же можно воспользоваться хранимой функцией CalendarIsWorkTime.

FUNCTION CalendarIsWorkTime(@DateTimeUtc datetime) RETURNS bit

Функция принимает параметры аналогично методу из API:

  • @DateTimeUtc - дата\время в UTC, для которой производится расчёт.

  • Возвращает - 0 - рабочее время, 1 - нерабочее время.

Описание логики работы

Допустим, необходимо проверить является ли выбранный момент времени рабочим временем. Для этого можно вызвать метод API IsWorkTime или хранимую функцию CalendarIsWorkTime. Полученный результат сообщает о типе кванта, к которому относится запрашиваемый момент времени.

Для примера возьмём - 2014-01-01 08:49:00 (момент времени указывается в UTC).

Для того, чтобы понять является ли момент времени 2014-01-01 08:49:00 рабочим, нужно запросить из таблицы календаря квант, в который этот момент времени попадает. По типу полученного кванта можно будет понять, является ли момент времени рабочим или нет.

Если мы обратимся к таблице (см. выше), то мы увидим, что момент времени 2014-01-01 08:49:00 соответствует кванту с номером 16, который начинается 2014-01-01 08:45:00 и длится до 2014-01-01 09:00:00, а его тип - 0. Значит квант является квантом рабочего времени и момент времени 2014-01-01 08:49:00 является рабочим временем.

Расчёт рабочего времени между датами.

При помощи функции GetDateDiff - можно получить количество рабочих квантов между двумя моментами времени.

/// Расчёт рабочего времени между датами. /// </summary> /// <param name="dateTimeStartUTC">Первая дата в UTC.</param> /// <param name="dateTimeEndUTC">Вторая дата в UTC (должна быть больше первой).</param> /// <returns>Возвращает рабочее время между датами в квантах.</returns> long GetDateDiff(DateTime dateTimeStartUTC, DateTime dateTimeEndUTC);

Функция принимает два параметра dateTimeStartUTC и dateTimeEndUTC (начало и конец соответственно) типа DateTime и на выходе возвращает количество рабочих квантов между этими двумя моментами времени.

Так же можно воспользоваться хранимой функцией CalendarGetDateDiff.

FUNCTION CalendarGetDateDiff(@FirstDateUtc datetime, @SecondDateUtc datetime) RETURNS int

Функция принимает данные аналогично методу из API:

  • @FirstDateUtc - первая дата в UTC.

  • @SecondDateUtc - вторая дата в UTC.

  • Возвращает количество рабочих квантов между датами (1 квант = 15 минут).

Описание логики работы

Допустим нам необходимо получить оставшееся на заданиях время в рабочих часах. Для этого мы делаем соответствующий запрос к API или вызываем хранимую функцию. Не забываем, что полученный результат будет в рабочих квантах (интервалах по 15 минут).

Для примера возьмём моменты времени - 2014-01-01 05:29:00 и 2014-01-01 11:20:00 (моменты времени указываются в UTC). Для того, чтобы найти количество рабочих квантов между двумя моментами времени (2014-01-01 05:29:00 и 2014-01-01 11:20:00) необходимо найти два кванта, которые относятся к первому и второму моменту времени соответственно.

Если мы обратимся к таблице (см. выше), то увидим, что это кванты с номерами 2 и 18 соответственно. А затем вычесть из номера кванта второго момента времени - номер кванта первого момента времени, получится 18-2=16. Полученная разница будет являться количеством рабочих квантов между двумя моментами времени. Так как 1 рабочий квант это 15 минут, то между моментами времени 2014-01-01 05:29:00 и 2014-01-01 11:20:00 будет 4 рабочих часа.

Отсчёт в квантах рабочего времени от указанной даты

При помощи функции AddWorkingQuantsToDate - можно получить ближайшее рабочее время, которое наступит, если добавить к моменту времени некоторое количество рабочих квантов.

/// <summary> /// Отсчёт рабочего времени от указанной даты. /// </summary> /// <param name="dateTimeUTC">Дата\время в UTC, к которому производится добавление.</param> /// <param name="quants">рабочее время в квантах.</param> /// <returns>Возвращает дату\время в UTC. Дата округляется до 15 минут в большую сторону.</returns> DateTime AddWorkingQuantsToDate(DateTime dateTimeUTC, long quants);

Функция принимает параметр dateTimeUTC типа DateTime - момент времени, от которого необходимо начинать отсчёт и параметр quants типа long - количество рабочих квантов, которые необходимо добавить. На выходе возвращает ближайшее рабочее время, которое наступит, если добавить к моменту времени dateTimeUTC рабочие кванты в количестве quants.

Так же можно воспользоваться хранимой функцией CalendarAddWorkQuants.

FUNCTION CalendarAddWorkQuants(@DateUtc datetime, @Quants int) RETURNS datetime

Функция принимает данные аналогично методу из API:

  • @DateUtc - дата\время в UTC, к которому производится добавление.

  • @Quants - рабочее время в квантах (1 квант = 15 минут).

  • Возвращает дата\время в UTC. Дата округляется до 15 минут в большую сторону.

Описание логики работы

Допустим, нам необходимо узнать к какому рабочему дню будет завершено задание, если начать его выполнение сейчас и на его выполнение нужно какое-то количество рабочих часов.

Для этого мы делаем соответствующий запрос к API или вызываем хранимую функцию. Не забываем, что при обращении к календарю мы оперируем рабочими квантами (интервалами по 15 минут).

Для примера возьмём моменты времени - 2014-01-01 05:25:00 (момент времени указывается в UTC) и 5 рабочих часов (20 рабочих квантов). Для того, чтобы найти момент времени в ближайшем рабочем дне, когда пройдут 5 рабочих часов относительно момента времени 2014-01-01 05:25:00 нужно для начала найти квант, к которому относится выбранный момент времени.

Если мы обратимся к таблице (см. выше), то увидим, что это квант с номером 2. Далее прибавим 20 квантов (5 часов) к 2-ум и получим 22. Найдём квант с номером 22 и типом 0. Начало этого кванта будет искомым моментом времени 2014-01-01 12:30:00.

Отсчёт в рабочих днях от указанной даты

При помощи функции AddWorkingDaysToDate - можно получить ближайшее рабочее время, которое наступит, если добавить к моменту времени некоторое количество расчётных рабочих дней.

/// <summary> /// Получение даты рабочего времени смещением в рабочих днях относительно заданной даты. /// </summary> /// <param name="dateTimeUTC">Дата в UTC.</param> /// <param name="daysOffset">Смещение в рабочих днях.</param> /// <returns>Возвращает дату рабочего времени.</returns> DateTime AddWorkingDaysToDate(DateTime dateTimeUTC, double daysOffset);

Функция принимает параметр dateTimeUTC типа DateTime - момент времени, от которого необходимо начинать отсчёт и параметр daysOffset типа double - количество рабочих дней, которые необходимо добавить. На выходе возвращает ближайшее рабочее время, которое наступит, если добавить к моменту времени dateTimeUTC рабочие дни в количестве daysOffset.

Так же можно воспользоваться хранимой функцией CalendarAddWorkingDaysToDate.

FUNCTION CalendarAddWorkingDaysToDate(@DateTimeUtc datetime, @Offset float) RETURNS datetime

Функция принимает данные аналогично методу из API:

  • @DateTimeUtc - дата\время в UTC, к которому производится добавление.

  • @Offset - рабочее время в днях.

  • Возвращает дата\время в UTC. Дата округляется до 15 минут в большую сторону.

Описание логики работы

Допустим, нам необходимо узнать к какой дате будет завешено задание, если начать его выполнение сейчас и на его выполнение нужно какое-то количество рабочих дней.

Для этого мы делаем соответствующий запрос к API или вызываем хранимую функцию. Не забываем, что при обращении к календарю мы оперируем рабочими квантами (интервалами по 15 минут).

Для примера возьмём моменты времени - 2014-01-01 05:25:00 (момент времени указывается в UTC) и 1,5 рабочих дней. Для того, чтобы найти момент времени в ближайшем рабочем дне, когда пройдут 1,5 рабочих дня относительно момента времени 2014-01-01 05:25:00 нужно для начала найти количество квантов в 1,5 рабочих днях. (32 * 1,5 = 48, исходя из 8-ми часового рабочего дня). Следовательно, должно пройти 48 рабочих кванта, с момента времени 2014-01-01 05:25:00

Если мы обратимся к таблице (см. выше), то увидим, что это квант с номером 2. Далее прибавим 48 квантов (1,5 дня) к 2-ум и получим 50. Найдём квант с номером 50 и типом 0. Начало этого кванта будет искомым моментом времени 2014-01-06 11:15:00.

Отсчёт в фактических рабочих днях от указанной даты

При помощи функции AddWorkingDaysToDateExact - можно получить ближайшее рабочее время, которое наступит, если добавить к моменту времени некоторое количество фактических рабочих дней.

/// <summary> /// Добавление нужного количества рабочих дней к дате. /// </summary> /// <param name="dateTimeUTC">Дата в UTC.</param> /// <param name="interval">Количество рабочих дней.</param> /// <returns>Возвращает дату рабочего времени.</returns> DateTime CalendarAddWorkingDaysToDateExact(DateTime dateTimeUTC, int interval);

Функция принимает параметр dateTimeUTC типа DateTime - момент времени, от которого необходимо начинать отсчёт и параметр daysOffset типа int - количество рабочих дней, которые необходимо добавить. На выходе возвращает ближайшее рабочее время, которое наступит, если добавить к моменту времени dateTimeUTC рабочие дни в количестве interval.

Так же можно воспользоваться хранимой функцией CalendarAddWorkingDaysToDateExact.

FUNCTION CalendarAddWorkingDaysToDateExact(@dateTimeUTC datetime, @Interval int) RETURNS datetime

Функция принимает данные аналогично методу из API:

  • @dateTimeUTC - дата\время в UTC, к которому производится добавление.

  • @Interval - рабочее время в днях.

  • Возвращает дата\время в UTC. Дата округляется до 15 минут в большую сторону.

Описание логики работы

Берем подмножество квантов календаря за период, в котором гарантированно окажется нужно количество рабочих дней. Эмпирически это (нужное кол-во рабочих дней)*3+14. 14 - это самый длинные в мире каникулы (новогодние). Такая фильтрация нужна, чтобы не обрабатывать весь календарь, что ОЧЕНЬ медленно и ОЧЕНЬ тяжело для сервера. Выбираем все рабочие кванты за этот период. В итоге мы получаем, что на каждый рабочий день есть хотя бы один квант. Конвертируем дату\время начала кванта в дату и делаем дистинкт - т.е. у нас остается всего по одной строке на каждый рабочий день. В итоге мы получили список рабочих дней в этом периоде. Сортируем, нумеруем и выбираем нужный по номеру = (нужное кол-во рабочих дней).

Получение начала первого рабочего кванта рабочего дня, полученного смещением относительно заданной даты

При помощи функции GetFirstQuantStart - можно получить начало первого рабочего кванта рабочего дня, полученного смещением относительно заданной даты.

/// <summary> /// Получение начала первого рабочего кванта рабочего дня, /// полученного смещением относительно заданной даты. /// </summary> /// <param name="dateTimeUTC">Дата в UTC.</param> /// <param name="daysOffset">Смещение в рабочих днях.</param> /// <returns>Возвращает дату\время в UTC начала первого рабочего кванта рабочего дня.</returns> DateTime GetFirstQuantStart(DateTime dateTimeUTC, int daysOffset);

Функция принимает параметр dateTimeUTC типа DateTime - момент времени, от которого необходимо выполнять смещение и параметр daysOffset типа int количество дней, на которые необходимо произвести смещение. На выходе возвращает момент времени, в который начинается ближайший рабочий день. Параметр daysOffset может иметь отрицательное значение, а если он равен нулю, то будет получено начало ближайшего рабочего дня.

Пример использования:

var dayStart = calendarService.GetFirstQuantStart(DateTime.UtcNow, -5);

Так же можно воспользоваться хранимой функцией CalendarGetFirstQuantStart.

FUNCTION CalendarGetFirstQuantStart(@DateTimeUtc datetime, @Offset int) RETURNS datetime

Функция принимает данные аналогично методу из API.

  • @DateTimeUtc - дата\время в UTC, для которой производится расчёт.

  • @Offset - смещение в днях.

  • Возвращает дата/время начала рабочего дня.

Описание логики работы

Допустим нам необходимо узнать, какой рабочий день будет через некоторое количество рабочих дней после момента времени или за несколько дней до и во сколько этот рабочий день начнётся. Для этого мы делаем соответствующий запрос к API или вызываем хранимую функцию.

Для примера возьмём моменты времени - 2014-01-01 05:25:00 (момент времени указывается в UTC) и смещение в 1 рабочий день. Для того, чтобы найти начало ближайшего дня смещением относительно заданного момента времени на один день - нужно найти первый рабочий квант, захватывающий момент времени 2014-01-01 05:25:00 + 1 день.

Если мы обратимся к таблице (см. выше), то увидим, что это квант с номером 33. Начало этого кванта будет искомым моментом времени 2014-01-06 05:00:00.

Получение конца последнего рабочего кванта рабочего дня, полученного смещением относительно заданной даты

При помощи функции GetLastQuantEnd - можно получить конец первого рабочего кванта рабочего дня, полученного смещением относительно заданной даты.

/// <summary> /// Получение конца последнего рабочего кванта рабочего дня, /// полученного смещением относительно заданной даты. /// </summary> /// <param name="dateTimeUTC">Дата в UTC.</param> /// <param name="daysOffset">Смещение в рабочих днях.</param> /// <returns>Возвращает дату\время в UTC конца последнего рабочего кванта рабочего дня.</returns> DateTime GetLastQuantEnd(DateTime dateTimeUTC, int daysOffset);

Функция принимает параметр dateTimeUTC типа DateTime - момент времени, от которого необходимо выполнять смещение и параметр daysOffset типа int количество дней, на которые необходимо произвести смещение. На выходе возвращает момент времени, в который заканчивается ближайший рабочий день. Параметр daysOffset может иметь отрицательное значение, а если он равен нулю, то будет получен конец ближайшего рабочего дня.

Пример использования:

var dayEnd = calendarService.GetLastQuantEnd(DateTime.UtcNow, 0);

Так же можно воспользоваться хранимой функцией CalendarGetLastQuantStart.

FUNCTION CalendarGetLastQuantStart(@DateTimeUtc datetime, @Offset int) RETURNS datetime

Функция принимает данные аналогично методу из API.

  • @DateTimeUtc - дата\время в UTC, для которой производится расчёт.

  • @Offset - смещение в днях.

  • Возвращает - дата/время начала рабочего дня.

Описание логики работы

Допустим нам необходимо узнать, какой рабочий день будет через некоторое количество рабочих дней после момента времени или за несколько дней до и во сколько этот рабочий день закончится. Для этого мы делаем соответствующий запрос к API или вызываем хранимую функцию.

Для примера возьмём моменты времени - 2014-01-01 05:25:00 (момент времени указывается в UTC) и смещение в 1 рабочий день. Для того, чтобы найти конец ближайшего дня смещением относительно заданного момента времени на один день - нужно найти первый рабочий квант, захватывающий момент времени 2014-01-01 05:25:00 + 1 день. Начало этого кванта будет 2014-01-06 05:00:00. Затем найти начало следующего дня при помощи полученных данных. Это 2014-01-07 00:00:00. Далее найдём все рабочие кванты начало которых больше 2014-01-06 05:00:00, но меньше 2014-01-07 00:00:00 и, отсортировав по убыванию возьмём первый из списка квант. Это будет квант с номером 64 и типом 0. Его время окончания и будет искомым значением 2014-01-06 15:00:00.

Служебные методы

Служебные методы реализуют специфический служебный функционал, такой как перестраивание календаря и проверка на наличие ошибок. Вызывать эти методы внутри пользовательских расширений запрещается.

Перестраивание календаря

При помощи функции RebuildCalendar - можно вызвать пересчёт календаря аналогично тому, как календарь перестраивается из карточки.

/// <summary> /// Вызывает перестройку календаря на основе внесённых исключений. /// </summary> /// <param name="operationGuid">Guid операции.</param> /// <param name="dateTimeStartUTC">Первая дата в UTC.</param> /// <param name="dateTimeEndUTC">Вторая дата в UTC (должна быть больше первой).</param> /// <param name="workTimeStartUTC">Начало рабочего дня.</param> /// <param name="workTimeEndUTC">Конец рабочего дня.</param> /// <param name="lunchTimeStartUTC">Начало обеда.</param> /// <param name="lunchTimeEndUTC">Конец обеда.</param> void RebuildCalendar( Guid operationGuid, DateTime dateTimeStartUTC, DateTime dateTimeEndUTC, DateTime workTimeStartUTC, DateTime workTimeEndUTC, DateTime lunchTimeStartUTC, DateTime lunchTimeEndUTC);

Функция реализована с использованием API операций и принимает параметр operationGuid типа Guid - ID операции, а так же параметры начала/конца рабочего дня, начала/конца обеденного перерыва и период действия календаря.

Хранимая процедура CalendarPrepareQuants:

CREATE PROCEDURE CalendarPrepareQuants ( @PeriodStartTimeUTC datetime, -- дата\время, начиная с которой должны быть перерасчитаны кванты @PeriodEndTimeUTC datetime, -- дата\время, до которой должные быть перерасчитаны кванты. @WorkDayStartTimeUTC datetime, -- время начала рабочего дня. @WorkDayEndTimeUTC datetime, -- время конца рабочего дня. @LaunchStartTimeUTC datetime, -- время начала обеда. @LaunchEndTimeUTC datetime-- время конца обеда. )

Проверка календаря на наличие ошибок

При помощи функции ValidateCalendar - можно проверить календарь на наличие ошибок.

/// <summary> /// Проверяет календарь на отсутствие пропусков между квантами. /// </summary> /// <param name="message">Сообщение с кратким отчётом.</param> /// <returns>Признак наличия ошибок.</returns> bool ValidateCalendar(out string message);

Функция out принимает параметр message типа string, в который записывается краткий отчёт. На выходе идёт признак наличия ошибок.

Пример использования

string message = ""; var isErrors = calendarService.ValidateCalendar(out message);

Проверка календаря на наличие ошибок реализована исключительно как функция API и не имеет своей хранимой процедуры или функции.

Применение календаря на больших выборках/отчётах

Для больших выборок или отчётов использовать хранимые функции не целесообразно. Для таких случаев целесообразнее общаться с таблицей календаря напрямую.

Следующий пример иллюстрирует, как можно получить количество рабочих часов выделенных на задания, зная дату\время создания задания и дату\время момента, когда задание должно быть выполнено. Мы просто считаем разницу между номерами квантов.

select ((cqu2.QuantNumber - cqu1.QuantNumber)/4.0) as WorkHoursLeft, * from Tasks as tsk with(nolock) outer apply ( select top 1 q.QuantNumber from CalendarQuants q with(nolock) where q.StartTimeUTC <= tsk.Created order by q.StartTimeUTC desc ) as cqu1 outer apply ( select top 1 q.QuantNumber from CalendarQuants q with(nolock) where q.StartTimeUTC <= tsk.Planned order by q.StartTimeUTC desc ) as cqu2

Warning

Пожалуйста, не используйте вариант запроса, представленный ниже. Несмотря на свою внешнюю простоту, он выполняется в тясячи раз медленнее!

Неправильный запрос

select ((cqu2.QuantNumber - cqu1.QuantNumber)/4.0) as WorkHoursLeft, * from Tasks as tsk with(nolock) left join CalendarQuants as cqu1 with(nolock) on cqu1.StartTimeUTC <= tsk.Created and cqu1.EndTimeUTC > tsk.Created left join CalendarQuants as cqu2 with(nolock) on cqu2.StartTimeUTC <= tsk.Planned and cqu2.EndTimeUTC > tsk.Planned

Причина лучшей производительности первого варианта

И в первом и во втором случае используется поиск по кластерному индексу:

--Первый вариант Clustered Index Seek( OBJECT:([tessa].[dbo].[CalendarQuants].[idx_CalendarQuants_StartTimeUTCEndTimeUTC] AS [q]), SEEK:([q].[StartTimeUTC] <= [tessa].[dbo].[Tasks].[Planned] as [tsk].[Planned]) ORDERED FORWARD)

--Второй вариант Clustered Index Seek( OBJECT:([tessa].[dbo].[CalendarQuants].[idx_CalendarQuants_StartTimeUTCEndTimeUTC] AS [cqu2]), SEEK:([cqu2].[StartTimeUTC] <= [tessa].[dbo].[Tasks].[Planned] as [tsk].[Planned]), WHERE:([tessa].[dbo].[CalendarQuants].[EndTimeUTC] as [cqu2].[EndTimeUTC]>[tessa].[dbo].[Tasks].[Planned] as [tsk].[Planned]) ORDERED FORWARD)

Но в первом варианте на каждое задание происходит ровно одно чтение значения (благодаря топу и индексу построенному по возрастанию), а во втором варианте, для каждого из найденных значений (дата планируемого завершения больше даты начала кванта) проверяется, что дата завершения меньше даты конца кванта - кол-во чтений зависит от размера таблицы календаря, и близости самого старого найденного кванта к концу таблицы.

Back to top