Расчет разницы между двумя датами - PullRequest
2 голосов
/ 23 апреля 2009

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

По сути, запрос (прямо сейчас) выглядит примерно так:

SELECT -- some kind of information
       DATEDIFF(dd, DateOne, DateTwo) AS d1_d2_diff,
       DATEDIFF(dd, DateOne, DateThree) AS d1_d3_diff,
       DATEDIFF(dd, DateTwo, DateThree) AS d2_d3_diff,
       DATEDIFF(dd, DateTwo, DateFour) AS d2_d4_diff
  FROM some_table;

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

У меня есть таблица календаря в базе данных:

CREATE TABLE Calendar (
   Date DATETIME NOT NULL,
   IsWeekday BIT,
   IsHoliday BIT
);

Будет ли в этом случае хорошим выбором табличная функция и CROSS APPLY? Если так, то как мне написать такую ​​вещь? Или скалярная функция моя лучшая ставка?

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

Ответы [ 4 ]

3 голосов
/ 23 апреля 2009

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

1.) Сверните значения даты до полуночи соответствующих дней, чтобы вы могли легко их выяснить. Из-за дополнительной информации это не требуется!

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

SELECT ISNULL(COUNT(*), 0)
FROM Calendar
WHERE [DATE] > DateOne 
    AND [DATE] < DateTwo
    AND IsWeekDay = 1
    AND IsHoliday = 0

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

примечание на кресте применяется

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

2 голосов
/ 01 мая 2009

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

Вот как это работает:

create function BusinessDayDiffs_fn ( 
  @DateOne datetime
, @DateTwo datetime
)
returns table
as return (
  select count(*) as numBusinessDays
  from Calendar
  where date between @DateOne and @DateTwo
    and IsWeekday = 1
    and IsHoliday = 0;
)

GO

select
  d1_d2_diff = d1_d2.numBusinessDays,
  d1_d3_diff = d1_d3.numBusinessDays,
  d2_d3_diff = d2_d3.numBusinessDays,
  d3_d4_diff = d3_d4.numBusinessDays
from some_table s
cross apply BusinessDayDiffs_fn( DateOne, DayTwo  ) d1_d2
cross apply BusinessDayDiffs_fn( DateOne, DayThree) d1_d3
cross apply BusinessDayDiffs_fn( DayTwo,  DayThree) d2_d3
cross apply BusinessDayDiffs_fn( DayTwo,  DayFour ) d2_d4;

Это должно работать довольно хорошо, поскольку это то же самое, что вынуть подзапрос из функции и вставить его прямо в предложение select основного запроса. Это будет намного быстрее, чем скалярная функция.

0 голосов
/ 08 июня 2009

Ниже приведена версия, основанная на вышеуказанном, которая должна работать для MySQL

#
# This function calculates the total number of weekdays (inclusive)
# between the specified dates.
#
# If start date < end date, the value returned is negative
#
# Known issues - due to the inaccuracy of the MySQL WEEK detection
# boundaries across years may be incorrect
#

DELIMITER $$

DROP FUNCTION IF EXISTS `dbname`.`WeekdayDiff` $$
CREATE FUNCTION `dbname`.`WeekdayDiff` (start_date date, end_date date) RETURNS INT DETERMINISTIC
BEGIN
  DECLARE week_diff INT;
  DECLARE week_diff_add_days INT;
  DECLARE temp_date DATE;
  DECLARE multiplier INT;
  DECLARE wd_left_in_start_inclusive INT;
  DECLARE wd_left_in_end_exclusive INT;
  DECLARE wd_diff INT;

  SET multiplier = 1;

  IF start_date > end_date THEN
    SET temp_date = end_date;
    SET end_date = start_date;
    SET start_date = temp_date;
    SET multiplier = -1;
  END IF;

  # Note we subtract 1 from the dates here as
  # we want sunday to be included in the last week
  SET week_diff = (YEAR(end_date) * 52 + WEEK(end_date-1)) - (YEAR(start_date) * 52 + WEEK(start_date-1));
  SET week_diff_add_days = week_diff * 5;

  # Calculate the week days left in the start week
  SET wd_left_in_start_inclusive = GREATEST( 5 - WEEKDAY( start_date ), 0 );
  SET wd_left_in_end_exclusive = GREATEST( 4 - WEEKDAY( end_date ), 0 );

  SET wd_diff = week_diff_add_days + wd_left_in_start_inclusive - wd_left_in_end_exclusive;

  RETURN wd_diff * multiplier;
END $$

DELIMITER ;
0 голосов
/ 23 апреля 2009

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

CREATE FUNCTION dbo.fn_WeekdayDiff(@StartDate DATETIME, @EndDate DATETIME)
RETURNS INT
AS
--Calculdate weekdays between two dates
BEGIN
    --if @StartDate is AFTER @EndDate, swap them
    IF @StartDate > @EndDate
    BEGIN
        DECLARE @TempDate DATETIME
        SET @TempDate = @StartDate
        SET @StartDate = @EndDate
        SET @EndDate = @TempDate
    END

    RETURN
        --number of weeks x 5 weekdays/week
          (DATEDIFF(ww, @StartDate, @EndDate) * 5)
        --add weekdays left in current week
        + CASE DATEPART(dw, @StartDate + @@DATEFIRST) WHEN 1 THEN 5 ELSE (7 - DATEPART(dw, @StartDate + @@DATEFIRST)) END
        --subtract weekdays after @EndDate
        - dbo.fn_MaxInt(6 - DATEPART(dw, @EndDate + @@DATEFIRST), 0)
END
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...