我有一个如下所示的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;
答案 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 |
+-------------+------------+--------+