MySQL 连续日期范围的实现
MySQL Implementation of Consecutive Date Ranges
我有一个 MySQL 包含合同的数据库 table:
CREATE TABLE IF NOT EXISTS `contracts` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`employee_id` BIGINT(20) DEFAULT NULL,
`start_date` DATE DEFAULT NULL,
`end_date` DATE DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `contracts` (`id`,`employee_id`,`start_date`,`end_date`)
VALUES
(1, 555, '2010-01-01', '2012-12-31'),
(2, 666, '2013-01-01', '2013-05-01'),
(3, 666, '2013-05-02', '2013-10-11'),
(4, 777, '2012-01-10', '2013-03-01'),
(5, 777, '2013-03-02', '2014-07-15'),
(6, 777, '2015-01-16', '2015-05-20');
查询它我得到每个员工一个或多个合同行
SELECT * FROM contracts
id employee_id start_date end_date
1 555 2010-01-01 2012-12-31
2 666 2013-01-01 2013-05-01
3 666 2013-05-02 2013-10-11
4 777 2012-01-10 2013-03-01
5 777 2013-03-02 2014-07-15
6 777 2015-01-16 2015-05-20
如何查询合同 table 以对每个员工的连续范围进行分组?我正在寻找这个输出:
employee_id start_date end_date
555 2010-01-01 2012-12-31
666 2013-01-01 2013-10-11
777 2012-01-10 2014-07-15
777 2015-01-16 2015-05-20
考虑到合同日期之间没有间隔,员工 666 的记录将 return 最低开始日期和最高结束日期。
员工 777 的记录将 return 两行,因为记录 ID 5 和 6 之间存在间隙。
有什么想法吗?
逻辑并不难,但 MySQL 中的实现很困难。这个想法是添加一个标志,指示合同开始的开始。然后,对于每一行,做一个累加和。累计总和可用于分组目的。
第一步可以使用关联子查询:
SELECT c1.*,
(NOT EXISTS (SELECT 1
FROM contracts c2
WHERE c1.employee_id = c2.employee_id AND
c1.start_date = c2.end_date + INTERVAL 1 DAY
)
) AS startflag
FROM contracts c1;
第二个将其用作子查询并进行累加:
SELECT
c0.*
,(@rn := @rn + COALESCE(startflag, 0)) AS cumestarts
FROM
(SELECT c1.*,
(NOT EXISTS (SELECT 1
FROM contracts c2
WHERE c1.employee_id = c2.employee_id AND
c1.start_date = c2.end_date + INTERVAL 1 DAY
)
) AS startflag
FROM contracts c1
ORDER BY employee_id, start_date
) c0 CROSS JOIN (SELECT @rn := 0) params;
最后一步是按这个值聚合:
SELECT
c.employee_id
,MIN(c.start_date) AS start_date
,MAX(c.end_date) AS end_date
,COUNT(*) AS numcontracts
FROM
(
SELECT
c0.*
,(@rn := @rn + COALESCE(startflag, 0)) AS cumestarts
FROM
(SELECT c1.*,
(NOT EXISTS (SELECT 1
FROM contracts c2
WHERE c1.employee_id = c2.employee_id AND
c1.start_date = c2.end_date + INTERVAL 1 DAY
)
) AS startflag
FROM contracts c1
ORDER BY employee_id, start_date
) c0 CROSS JOIN (SELECT @rn := 0) params
) c
GROUP BY c.employee_id, c.cumestarts
我有一个 MySQL 包含合同的数据库 table:
CREATE TABLE IF NOT EXISTS `contracts` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`employee_id` BIGINT(20) DEFAULT NULL,
`start_date` DATE DEFAULT NULL,
`end_date` DATE DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `contracts` (`id`,`employee_id`,`start_date`,`end_date`)
VALUES
(1, 555, '2010-01-01', '2012-12-31'),
(2, 666, '2013-01-01', '2013-05-01'),
(3, 666, '2013-05-02', '2013-10-11'),
(4, 777, '2012-01-10', '2013-03-01'),
(5, 777, '2013-03-02', '2014-07-15'),
(6, 777, '2015-01-16', '2015-05-20');
查询它我得到每个员工一个或多个合同行
SELECT * FROM contracts
id employee_id start_date end_date
1 555 2010-01-01 2012-12-31
2 666 2013-01-01 2013-05-01
3 666 2013-05-02 2013-10-11
4 777 2012-01-10 2013-03-01
5 777 2013-03-02 2014-07-15
6 777 2015-01-16 2015-05-20
如何查询合同 table 以对每个员工的连续范围进行分组?我正在寻找这个输出:
employee_id start_date end_date
555 2010-01-01 2012-12-31
666 2013-01-01 2013-10-11
777 2012-01-10 2014-07-15
777 2015-01-16 2015-05-20
考虑到合同日期之间没有间隔,员工 666 的记录将 return 最低开始日期和最高结束日期。
员工 777 的记录将 return 两行,因为记录 ID 5 和 6 之间存在间隙。
有什么想法吗?
逻辑并不难,但 MySQL 中的实现很困难。这个想法是添加一个标志,指示合同开始的开始。然后,对于每一行,做一个累加和。累计总和可用于分组目的。
第一步可以使用关联子查询:
SELECT c1.*,
(NOT EXISTS (SELECT 1
FROM contracts c2
WHERE c1.employee_id = c2.employee_id AND
c1.start_date = c2.end_date + INTERVAL 1 DAY
)
) AS startflag
FROM contracts c1;
第二个将其用作子查询并进行累加:
SELECT
c0.*
,(@rn := @rn + COALESCE(startflag, 0)) AS cumestarts
FROM
(SELECT c1.*,
(NOT EXISTS (SELECT 1
FROM contracts c2
WHERE c1.employee_id = c2.employee_id AND
c1.start_date = c2.end_date + INTERVAL 1 DAY
)
) AS startflag
FROM contracts c1
ORDER BY employee_id, start_date
) c0 CROSS JOIN (SELECT @rn := 0) params;
最后一步是按这个值聚合:
SELECT
c.employee_id
,MIN(c.start_date) AS start_date
,MAX(c.end_date) AS end_date
,COUNT(*) AS numcontracts
FROM
(
SELECT
c0.*
,(@rn := @rn + COALESCE(startflag, 0)) AS cumestarts
FROM
(SELECT c1.*,
(NOT EXISTS (SELECT 1
FROM contracts c2
WHERE c1.employee_id = c2.employee_id AND
c1.start_date = c2.end_date + INTERVAL 1 DAY
)
) AS startflag
FROM contracts c1
ORDER BY employee_id, start_date
) c0 CROSS JOIN (SELECT @rn := 0) params
) c
GROUP BY c.employee_id, c.cumestarts