mysql总和按合同的开始日期和结束日期按月和日期分组

时间:2019-09-05 16:02:47

标签: mysql group-by sum

我有一张满月合同的桌子。每个都有月度价格,开始日期和结束日期。我正在尝试绘制每个月的总收入,并想知道是否有可能在一个查询中做到这一点(相对于每个月的查询)。

我知道如何在mysql中按月和年分组,但这需要一个更复杂的解决方案,该解决方案“理解”是否基于合同的开始和结束日期将给定月/年的总和包括在内。 / p>

速记示例

|    contract_id    |    price    |     start_date      |       end_date       |
|        1          |     299     | 1546318800 (1/1/19) | 1554004800 (3/31/19) |
|        2          |     799     | 1551416400 (3/1/19) | 1559275200 (5/31/19) |

在此示例中,三月份存在重叠。这两个合同都在3月运行,因此该月返回的金额应为1098。

我希望能够生成一个包含两个日期之间每个月的报告,因此在这种情况下,我将发送1/1/19-12/31/19(2019年全年),并希望也可以看到0个结果。

|    month    |    year    |    price_sum    |
|      1      |    2019    |      299        |
|      2      |    2019    |      299        |
|      3      |    2019    |      1098       |
|      4      |    2019    |      799        |
|      5      |    2019    |      799        |
|      6      |    2019    |       0         |
|      7      |    2019    |       0         |
|      8      |    2019    |       0         |
|      9      |    2019    |       0         |
|      10     |    2019    |       0         |
|      11     |    2019    |       0         |
|      12     |    2019    |       0         |

2 个答案:

答案 0 :(得分:0)

这里是针对您问题的完整工作脚本,它使用日历表方法来表示2019年的每个月。具体来说,我们使用该月的第一个月来表示每个月。然后,如果起始和结束范围重叠,则表中给定的价格适用于该月。

WITH yourTable AS (
    SELECT 1 AS contract_id, 299 AS price, '2019-01-01' AS start_date, '2019-03-31' AS end_date UNION ALL
    SELECT 2, 799, '2019-03-01', '2019-05-31'
),
dates AS (
    SELECT '2019-01-01' AS dt UNION ALL
    SELECT '2019-02-01' UNION ALL
    SELECT '2019-03-01' UNION ALL
    SELECT '2019-04-01' UNION ALL
    SELECT '2019-05-01' UNION ALL
    SELECT '2019-06-01' UNION ALL
    SELECT '2019-07-01' UNION ALL
    SELECT '2019-08-01' UNION ALL
    SELECT '2019-09-01' UNION ALL
    SELECT '2019-10-01' UNION ALL
    SELECT '2019-11-01' UNION ALL
    SELECT '2019-12-01'
)

SELECT
    d.dt,
    SUM(t.price) AS price_sum
FROM dates d
LEFT JOIN yourTable t
    ON d.dt < t.end_date
  AND DATE_ADD(d.dt, INTERVAL 1 MONTH) > t.start_date
GROUP BY
    d.dt;

screen capture of result set

Demo

注意:

如果您的日期实际上是作为UNIX时间戳存储的,则只需调用FROM_UNIXTIME(your_date)即可将它们转换为日期,并使用上面提供的相同方法。

我必须在这里使用重叠日期范围公式,因为给定月份中重叠的标准是该月份的范围与开始日期和结束日期所指定的范围相交。请查看this SO question,以获取更多信息。

我的代码适用于MySQL 8+,尽管在实践中您可能希望创建一个真实的日历表(上面我称为dates的CTE版本),其中包含您所用的月/年范围想覆盖您的数据集。

答案 1 :(得分:0)

我了解您将获得需要报告的日期范围。我的解决方案要求您初始化一个临时表,例如date_table,并在每个月的第一天进行报告:

create temporary table date_table (
    d date,
    primary key(d)
);

set @start_date = '2019-01-01';
set @end_date = '2019-12-01';
set @months = -1;
insert into date_table(d)
select DATE_FORMAT(date_range,'%Y-%c-%d') AS result_date from (
    select (date_add(@start_date, INTERVAL (@months := @months +1 ) month)) as date_range
    from mysql.help_topic a limit 0,1000) a
where a.date_range between @start_date and last_day(@end_date);

然后应该这样做:

select month(dt.d) as month, year(dt.d) as year, ifnull(sum(c.price), 0) as price_sum
from date_table dt left join contract c on
    dt.d >= date(from_unixtime(c.start_date)) and dt.d < date(from_unixtime(c.end_date))
group by dt.d
order by dt.d
;

结果:

+-------+------+-----------+
| month | year | price_sum |
+-------+------+-----------+
|     1 | 2019 |       299 |
|     2 | 2019 |       299 |
|     3 | 2019 |      1098 |
|     4 | 2019 |       799 |
|     5 | 2019 |       799 |
|     6 | 2019 |         0 |
|     7 | 2019 |         0 |
|     8 | 2019 |         0 |
|     9 | 2019 |         0 |
|    10 | 2019 |         0 |
|    11 | 2019 |         0 |
|    12 | 2019 |         0 |
+-------+------+-----------+

See demo

我不确定end_date列的语义。现在,我正在比较第一个a:start_date <= first_of_month

dt.d >= date(from_unixtime(c.start_date)) and dt.d < date(from_unixtime(c.end_date))

成为:

dt.d between date(from_unixtime(c.start_date)) and date(from_unixtime(c.end_date))

由于end_date是一个月的最后一天,所以这两种方式都没有关系。

相关问题