发生重叠的日期范围时,PHP汇总金额

时间:2018-08-12 21:48:14

标签: php mysql

我有一个如下所示的MySQL表:

record_id    amount    start_date    end_date
1            20000     2018-01-01   2018-12-01
1            -15000    2018-02-01   2018-04-01
1            50000     2018-04-02   2018-06-30

这将在PHP中转换为如下所示的数组:

[1] => [
    [
       "start_date" => "2018-01-01",
       "end_date" => "2018-12-01",
       "amount"   => 20000
    ],
    [
       "start_date" => "2018-02-01",
       "end_date" => "2018-04-01",
       "amount"   => -15000
    ],
    [
       "start_date" => "2018-04-02",
       "end_date" => "2018-06-30",
       "amount"   => 50000
    ],
]

问题是,我需要对日期范围内的重叠金额求和,并为最长的日期范围保留原始金额。因此,生成的MySQL表将如下所示:

record_id    amount   start_date    end_date
1            20000    2018-01-01    2018-01-30
1            5000     2018-02-01    2018-04-01 
1            70000    2018-04-02    2018-06-30
1            20000    2018-07-01    2018-12-01

生成的PHP数组如下所示:

[1] => [
    [
       "start_date" => "2018-01-01",
       "end_date" => "2018-01-30",
       "amount"   => 20000
    ],
    [
       "start_date" => "2018-02-01",
       "end_date" => "2018-04-01",
       "amount"   => 5000
    ],
    [
       "start_date" => "2018-04-02",
       "end_date" => "2018-06-30",
       "amount"   => 70000
    ],
    [
       "start_date" => "2018-07-01",
       "end_date" => "2018-12-01",
       "amount"   => 20000 
    ],
]

基本上,从第一个开始日期到下一个开始日期的前一天的金额是当前金额。然后,对于任何重叠的日期范围,该金额将相加。因此20,000 + -15,000 = 5,000。然后,对于日期范围为2018-04-02至2018-06-30的初始金额(自12月份起仍然有效)将被添加到50,000中,使其达到70,000。最终,结束日期仍在运行,因此我们输入了20,000个新条目。

我可以为单个情况创建伪代码,但是这些情况都是动态的,并且重叠的日期和范围可能会发生变化。

显然,在MySQL中做到这一点是不可能/非常困难的,因此我想知道是否有人在PHP中进行过这些重叠的日期范围的体验。

编辑:我已经将草莓的答案标记为正确,因为它起作用了。如果可能,他可以在答案中粘贴该内容,并且可以删除此编辑,但是在答案有效之前(mysql 8.0之前),还需要做一些工作。您可以先执行以下脚本以获取他的答案,然后再执行其他任何操作(对this version进行了稍微修改):

DROP TABLE IF EXISTS calendar;
CREATE TABLE calendar(
        id                      INTEGER PRIMARY KEY,  -- year*10000+month*100+day
        dt                      DATE NOT NULL,
        year                    INTEGER NOT NULL,
        month                   INTEGER NOT NULL, -- 1 to 12
        day                     INTEGER NOT NULL, -- 1 to 31
        quarter                 INTEGER NOT NULL, -- 1 to 4
        week                    INTEGER NOT NULL, -- 1 to 52/53
        day_name                VARCHAR(9) NOT NULL, -- 'Monday', 'Tuesday'...
        month_name              VARCHAR(9) NOT NULL, -- 'January', 'February'...
        holiday_flag            CHAR(1) DEFAULT 'f' CHECK (holiday_flag in ('t', 'f')),
        weekend_flag            CHAR(1) DEFAULT 'f' CHECK (weekend_flag in ('t', 'f')),
        event                   VARCHAR(50),
        UNIQUE td_ymd_idx (year,month,day),
        UNIQUE td_dt_idx (dt)

) Engine=MyISAM;

DROP PROCEDURE IF EXISTS fill_date_dimension;
DELIMITER //
CREATE PROCEDURE fill_date_dimension(IN startdate DATE,IN stopdate DATE)
BEGIN
    DECLARE currentdate DATE;
    SET currentdate = startdate;
    WHILE currentdate < stopdate DO
        INSERT INTO calendar VALUES (
                        YEAR(currentdate)*10000+MONTH(currentdate)*100 + DAY(currentdate),
                        currentdate,
                        YEAR(currentdate),
                        MONTH(currentdate),
                        DAY(currentdate),
                        QUARTER(currentdate),
                        WEEKOFYEAR(currentdate),
                        DATE_FORMAT(currentdate,'%W'),
                        DATE_FORMAT(currentdate,'%M'),
                        'f',
                        CASE DAYOFWEEK(currentdate) WHEN 1 THEN 't' WHEN 7 then 't' ELSE 'f' END,
                        NULL);
        SET currentdate = ADDDATE(currentdate,INTERVAL 1 DAY);
    END WHILE;
END
//
DELIMITER ;

TRUNCATE TABLE calendar;

CALL fill_date_dimension('1-01-01','2040-01-01');
OPTIMIZE TABLE calendar;

1 个答案:

答案 0 :(得分:2)

这是使用简单日历(dt)实用程序表的解决方案...

DROP TABLE IF EXISTS my_table;

CREATE TABLE my_table
(start_date DATE NOT NULL PRIMARY KEY
,end_date DATE NOT NULL
,amount INT NOT NULL
);

INSERT INTO my_table VALUES
('2018-01-01','2018-12-01', 20000),
('2018-02-01','2018-04-01',-15000),
('2018-04-02','2018-06-30', 50000);

SELECT MIN(dt) range_start
     , MAX(dt) range_end
     , MIN(amount) amount
  FROM
     ( 
     SELECT dt
     , amount
     , CASE WHEN @prev=amount THEN @i:=@i ELSE @i:=@i+1 END i
     , @prev:=amount
  FROM 
     ( SELECT x.*
            , SUM(y.amount) amount 
         FROM calendar x 
         JOIN my_table y 
           ON x.dt BETWEEN y.start_date AND y.end_date 
        GROUP 
           BY x.dt 
      ) a
   JOIN (SELECT @prev:=null,@i:=0) vars
 ORDER 
    BY dt
   ) n
   GROUP 
     BY i;

+-------------+------------+--------+
| range_start | range_end  | amount |
+-------------+------------+--------+
| 2018-01-01  | 2018-01-31 |  20000 |
| 2018-02-01  | 2018-04-01 |   5000 |
| 2018-04-02  | 2018-06-30 |  70000 |
| 2018-07-01  | 2018-12-01 |  20000 |
+-------------+------------+--------+
相关问题