MySQL: рассчитать оставшиеся дни продажи в месяце

У меня есть расчет ежедневного целевого дохода, в котором указано, какой доход мне нужно зарабатывать ежедневно для достижения цели ежемесячного дохода:

'Revenue_Goal' / 'Selling_Days'

(Примечание: "Selling_Days" - это целое уже в наборе данных.)

То, что я пытаюсь достичь, - это получение скорректированной ежедневной цели, основанной на том, сколько дней прошло. Я ЧАСТИЧНО достиг этого с:

('Revenue_Goal' - SUM('Revenue')) / DATEDIFF(MAX('Date'),NOW())

Однако, часть, которую я не могу понять, это только расчет дней продажи в скорректированной формуле. Мне нужно использовать формулу выше (или что-то подобное), которая считает дни только когда:

  • С понедельника по пятницу = 1 день продажи
  • Суббота = 0,5 день продажи
  • Воскресенье = 0

Итак, если сегодня 25.03.16, мой расчет DATEDIFF вернул бы 6, но моя цель - получить 4.5.

Любая помощь приветствуется!

2 ответа

Решение

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

Способ 1: начать с первой даты, увеличивать до второй даты. Для каждой даты звоните DAYOFWEEK(). Если воскресенье, то добавить 0, если суббота добавить 0,5, в противном случае добавить 1.

DROP FUNCTION IF EXISTS `count_days_goal`;

DELIMITER $$
CREATE FUNCTION `count_days_goal`(start_date DATE, end_date DATE) RETURNS FLOAT
    DETERMINISTIC
BEGIN
    DECLARE sell_days FLOAT;
    DECLARE dow INT;

    SET sell_days = 0.0;
    WHILE start_date <= end_date DO
        SET dow = DAYOFWEEK(start_date);
        IF (dow > 1) THEN 
            IF (dow = 7) THEN SET sell_days = sell_days + 0.5;
            ELSE SET sell_days = sell_days + 1.0;
            END IF;
        END IF;
        SET start_date = start_date + INTERVAL 1 DAY;
    END WHILE;

    RETURN (sell_days);
END$$
DELIMITER ;

Пара тестов (обратите внимание, что моя реализация включает последний день в подсчитанных днях):

select count_days_goal(DATE('2016-03-21'), DATE('2016-03-27'));
select count_days_goal(DATE('2016-03-01'), DATE('2016-03-27')); 

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

Способ 2: каждая полная неделя составляет 5,5 дней продажи. Итак, разделите количество дней между датами на 7, на минуту не обращайте внимания на остаток и умножьте на 5,5.

Пример: 32 дня (4 недели + 4 дня), что составляет 22 дня продажи плюс 4 остатка.

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

Итак, теперь код выглядит так:

DROP FUNCTION IF EXISTS `count_days_goal`;
DELIMITER $$
CREATE FUNCTION `count_days_goal`(start_date DATE, end_date DATE) RETURNS FLOAT
    DETERMINISTIC
BEGIN
    DECLARE sell_days FLOAT;
    DECLARE dow INT;
    DECLARE whole_weeks INT;

    SET whole_weeks = FLOOR(DATEDIFF(end_date, start_date)/7);
    SET start_date = start_date + (7 * whole_weeks);
    SET sell_days = whole_weeks * 5.5;

    WHILE start_date <= end_date DO
        SET dow = DAYOFWEEK(start_date);
        IF (dow > 1) THEN 
            IF (dow = 7) THEN SET sell_days = sell_days + 0.5;
            ELSE SET sell_days = sell_days + 1.0;
            END IF;
        END IF;
        SET start_date = start_date + INTERVAL 1 DAY;
    END WHILE;

    RETURN (sell_days);
END$$
DELIMITER ;

Метод 3: Вы также можете использовать метод 2, но полностью избавиться от цикла. Чтобы увидеть, как это можно сделать, полезно иметь перед вами календарь. (Я не удосужился реализовать это; метод 2 довольно хорош.)

Вместо зацикливания звоните DAYOFWEEK в первое и последнее свидание Давайте назовем тех dow_start а также dow_end,

Если dow_start < dow_end тогда все эти досадные оставшиеся дни лежат внутри недели, они не переходят с субботы на воскресенье. Так что все, что вам нужно сделать, это:

SET answer = DATEDIFF(dow_end, dow_start) + 1;
IF dow_start = 1 (i.e. Sunday) THEN subtract 1 from answer
IF dow_end is 7 (i.e. Saturday) THEN subtract 0.5 from answer

Если dow_start > dow_end затем эти досадные оставшиеся дни оборачиваются. Я верю, что в этом случае вы можете поменяться dow_start а также dow_end (возвращая нас к предыдущему делу). Вычислите его, как и раньше, но затем вычтите ответ из 5.5 и добавьте 1. (Объяснение: посчитайте дни недели, которые НЕ находятся между day_start а также day_end, а затем вычтите их из общей суммы за неделю.)

Как я уже сказал, я не писал и не проверял этот, так что это может быть не совсем правильно.

Как обрабатывать банковские праздники: если у вас есть их в таблице, вы можете просто COUNT количество строк между двумя датами. Вычтите это из своего ответа. Легко! (Предполагая, что праздничные дни никогда не бывают выходными.) Существуют веб-сайты, на которых перечисляются банковские праздники для разных стран, даже один, который генерирует список дат для вас в далеком будущем, что облегчает создание таблицы выходных.

Это один из n способов вычислить это

SELECT
  SUM( 
    ELT( DAYOFWEEK(CONCAT( DATE_FORMAT(now(), '%Y-%m-'),nr)),
    '0','1','1','1','1','1','0.5') ) calc_days
FROM (
SELECT d2.a*10+d1.a  AS nr FROM (
  SELECT 0  a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 
  UNION ALL SELECT 
  5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8  UNION ALL SELECT 9) AS d1
  CROSS JOIN (
  SELECT 0 a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3  ) AS d2
) AS x
WHERE nr BETWEEN  DAY(NOW()) AND DAY(LAST_DAY(NOW()))
ORDER BY nr;
Другие вопросы по тегам