我想计算自合同开始之日起30天周期组中客户拥有的“服务”数量。因此,我必须从他的开始日期开始,每月计算一次服务时间。简化表格是这样的:
services
------------------
id serial
id_customer bigint
service_date date
让成像只有一种服务。我这样解决:
SELECT
DATE_PART('year',service_date)||'-'|| CASE WHEN DATE_PART('day',service_date) >= 15 THEN
DATE_PART('month',service_date)
ELSE
CASE WHEN DATE_PART('month',service_date) = 1 THEN
12
ELSE
DATE_PART('month',service_date)-1
END
END bill, count(id)
FROM services
WHERE id_customer = 1
GROUP BY bill
结果应为
bill | count
-------------------
2019-02 | 2455333
在此示例中,id_customer 1的开始日期为2019-02-15,但对于2019-02期间,我将一直计算服务至2019-03-14。
我想知道的是,有一个更好/更有效的解决方案吗?
我看到了解决方法here,但是它暗示了一个带有GROUP BY的INNER JOIN和同一张表,我认为这样做会比较慢,因为我的表有很多记录。
答案 0 :(得分:0)
您不必担心一个月中的实际天数,也不必担心月,年或月中的某天。
只需使用客户的开始日期,让PostgreSQL为您生成正确的计费周期即可。
要对所有客户运行单个查询,我使用了一个单独的表,其中配置了客户id
并配置了一个billing_start
日期,然后我们可以针对该日期运行查询,如下所示:
WITH
periods (id, period_start, period_end) AS (
SELECT
id,
generate_series(billing_start, current_date, '1 month'::interval)::date,
(generate_series(billing_start, current_date, '1 month'::interval) + '1 month'::interval)::date
FROM test_customers
),
data AS (
SELECT
periods.id AS customer,
period_start,
count(test_services.*) AS service_calls
FROM periods INNER JOIN test_services ON (test_services.id_customer = periods.id)
WHERE test_services.service_date >= periods.period_start AND test_services.service_date < periods.period_end
GROUP BY 1, 2
)
SELECT customer, to_char(period_start, 'YYYY-MM') AS bill, service_calls
FROM data
ORDER BY 1, 2
;
...产生如下输出:
customer | bill | service_calls
----------+---------+---------------
1 | 2018-12 | 382736
1 | 2019-01 | 382735
1 | 2019-02 | 345696
2 | 2018-12 | 382736
2 | 2019-01 | 382734
2 | 2019-02 | 234580
3 | 2018-12 | 382734
3 | 2019-01 | 382736
3 | 2019-02 | 123463
4 | 2018-12 | 382734
4 | 2019-01 | 382736
4 | 2019-02 | 12346
5 | 2019-01 | 382735
5 | 2019-02 | 283965
6 | 2019-01 | 382735
6 | 2019-02 | 172848
7 | 2019-01 | 382734
7 | 2019-02 | 61732
8 | 2019-02 | 333351
9 | 2019-02 | 222234
10 | 2019-02 | 111117
(21 rows)
完整的在线示例:https://rextester.com/IHLJ95398
要快速注意的重要一点是id_customer
和service_date
上的多列索引,因为这是进行计数的地方,然后可以不进行排序就完成:
CREATE INDEX idx_svc_customer_date ON test_services (id_customer, service_date);
(否则,排序很可能会在磁盘上完成,而不是在大型数据集的内存中进行)
如果您只想要单个客户的周期,请像这样使用它:
WITH
periods (id, period_start, period_end) AS (
SELECT
id,
generate_series(billing_start, current_date, '1 month'::interval)::date,
(generate_series(billing_start, current_date, '1 month'::interval) + '1 month'::interval)::date
FROM test_customers WHERE id = 4
),
data AS (
SELECT
periods.id AS customer,
period_start,
count(test_services.*) AS service_calls
FROM periods INNER JOIN test_services ON (test_services.id_customer = periods.id)
WHERE test_services.service_date >= periods.period_start AND test_services.service_date < periods.period_end
GROUP BY 1, 2
)
SELECT customer, to_char(period_start, 'YYYY-MM') AS bill, service_calls
FROM data
ORDER BY 1, 2
;
...给予:
bill | service_calls
---------+---------------
2018-12 | 382734
2019-01 | 382736
2019-02 | 12346
(3 rows)