[英]mysql sum group by month and date using a contract start and end date
我有一張滿月合同的桌子。 每個都有月度價格,開始日期和結束日期。 我試圖繪制每個月的總收入圖表,想知道是否有可能在一個查詢中做到這一點(相對於每個月的查詢)。
我知道如何在mysql中按月和年分組,但這需要更復雜的解決方案,該方案“理解”是否基於合同的開始和結束日期將給定月/年的總和包括在內。
速記示例
| 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 |
這是解決您問題的完整工作腳本,它使用日歷表方法來表示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;
筆記:
如果您的日期實際上存儲為UNIX時間戳,則只需調用FROM_UNIXTIME(your_date)
即可將它們轉換為日期,並使用與上面相同的方法。
我必須在此處使用重疊日期范圍公式,因為給定月份中重疊的標准是該月份的范圍與開始日期和結束日期所指定的范圍相交。 請查看此SO問題以獲取有關此信息的更多信息。
我的代碼適用於MySQL 8+,盡管在實踐中您可能希望創建一個真實的日歷表(我在上面稱為dates
的CTE版本),其中包含要覆蓋數據集的月/年范圍。
我了解您將獲得需要報告的日期范圍。 我的解決方案要求您初始化一個臨時表,例如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 |
+-------+------+-----------+
我不確定end_date
列的語義。 現在,我正在比較第一個a:start_date <= first_of_month <end_date。 也許測試應該是start_date <= first_of_month <= end_date,在這種情況下:
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是該月的最后一天,所以無論哪種方式都沒有關系。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.