使用tie-breaker将tsrange值聚合到日期桶中

时间:2018-01-28 22:31:18

标签: sql postgresql

所以我有一个架构让人们可以向一组组织捐赠$,而且这种捐赠与某段任意时间有关。我正在制作一份每天查看的报告,每个组织都会显示捐赠总数以及该组织当天捐赠的累计总值。

例如,这里有3个捐赠者的模型,Alpha(橙色),Bravo(绿色)和Charlie(蓝色)在不同的时间段内捐赠给2个不同的组织(Foo和Bar):

enter image description here

我创建了一个SQLFiddle,它在一个模式中实现了上述示例,该模式在某种程度上反映了我在实际中正在使用的内容:http://sqlfiddle.com/#!17/88969/1

(由于问题陈述更好地反映了我正在使用的现实版本,因此模式分为更多表格而不是您提出的表格)

到目前为止,我设法放在一起的查询看起来像这样:

WITH report_dates AS (
  SELECT '2018-01-01'::date + g AS date
  FROM generate_series(0, 14) g
), organizations AS (
  SELECT id AS organization_id FROM users
  WHERE type = 'Organization'
)

SELECT * FROM report_dates rd
CROSS JOIN organizations o
LEFT JOIN LATERAL (
  SELECT
    COALESCE(sum(doa.amount_cents), 0) AS total_donations_cents,
    COALESCE(count(doa.*), 0) AS total_donors
  FROM users
  LEFT JOIN donor_organization_amounts doa ON doa.organization_id = users.id
  LEFT JOIN donor_amounts da ON da.id = doa.donor_amounts_id
  LEFT JOIN donor_schedules ds ON ds.donor_amounts_id = da.id
  WHERE (users.id = o.organization_id) AND (ds.period && tsrange(rd.date::timestamp, rd.date::timestamp + INTERVAL '1 day', '[)'))
) o2 ON true;

结果如下:

|       date | organization_id | total_donations_cents | total_donors |
|------------|-----------------|-----------------------|--------------|
| 2018-01-01 |               1 |                     0 |            0 |
| 2018-01-02 |               1 |                   250 |            1 |
| 2018-01-03 |               1 |                   250 |            1 |
| 2018-01-04 |               1 |                  1750 |            3 |
| 2018-01-05 |               1 |                  1750 |            3 |
| 2018-01-06 |               1 |                  1750 |            3 |
| 2018-01-07 |               1 |                   750 |            2 |
| 2018-01-08 |               1 |                   850 |            2 |
| 2018-01-09 |               1 |                   850 |            2 |
| 2018-01-10 |               1 |                   500 |            1 |
| 2018-01-11 |               1 |                   500 |            1 |
| 2018-01-12 |               1 |                   500 |            1 |
| 2018-01-13 |               1 |                  1500 |            2 |
| 2018-01-14 |               1 |                  1000 |            1 |
| 2018-01-15 |               1 |                     0 |            0 |
| 2018-01-01 |               2 |                     0 |            0 |
| 2018-01-02 |               2 |                   250 |            1 |
| 2018-01-03 |               2 |                   250 |            1 |
| 2018-01-04 |               2 |                  1750 |            2 |
| 2018-01-05 |               2 |                  1750 |            2 |
| 2018-01-06 |               2 |                  1750 |            2 |
| 2018-01-07 |               2 |                  1750 |            2 |
| 2018-01-08 |               2 |                  2000 |            2 |
| 2018-01-09 |               2 |                  2000 |            2 |
| 2018-01-10 |               2 |                  1500 |            1 |
| 2018-01-11 |               2 |                  1500 |            1 |
| 2018-01-12 |               2 |                     0 |            0 |
| 2018-01-13 |               2 |                  1000 |            2 |
| 2018-01-14 |               2 |                   500 |            1 |
| 2018-01-15 |               2 |                     0 |            0 |

这非常接近,但是这个问题的问题是,在捐赠结束和同一捐赠者开始新捐赠的日子里,它应该只计算捐赠者的捐赠一次,使用更高的捐赠量作为打破平局累计$ count。组织Foo的一个例子是2018-01-13:total_donors应该是1而total_donations_cents 1000。

我尝试使用DISTINCT ON来实现打破平局但是我已经下了杂草......任何帮助都将不胜感激!

另外,考虑到CTE和CROSS JOIN,我应该担心到目前为止我的实施对性能的影响吗?

1 个答案:

答案 0 :(得分:0)

使用DISTINCT ONhttp://sqlfiddle.com/#!17/88969/4

计算出来
WITH report_dates AS (
  SELECT '2018-01-01'::date + g AS date
  FROM generate_series(0, 14) g
), organizations AS (
  SELECT id AS organization_id FROM users
  WHERE type = 'Organization'
), donors_by_date AS (
  SELECT * FROM report_dates rd
  CROSS JOIN organizations o
  LEFT JOIN LATERAL (
    SELECT DISTINCT ON (date, da.donor_id)
      da.donor_id,
      doa.id,
      doa.donor_amounts_id,
      doa.amount_cents
    FROM users
    LEFT JOIN donor_organization_amounts doa ON doa.organization_id = users.id
    LEFT JOIN donor_amounts da ON da.id = doa.donor_amounts_id
    LEFT JOIN donor_schedules ds ON ds.donor_amounts_id = da.id
    WHERE (users.id = o.organization_id) AND (ds.period && tsrange(rd.date::timestamp, rd.date::timestamp + INTERVAL '1 day', '[)'))
    ORDER BY date, da.donor_id, doa.amount_cents DESC
  ) foo ON true
)
SELECT
  date,
  organization_id,
  COALESCE(SUM(amount_cents), 0) AS total_donations_cents,
  COUNT(*) FILTER (WHERE donor_id IS NOT NULL) AS total_donors
FROM donors_by_date
GROUP BY date, organization_id
ORDER BY organization_id, date;

结果:

|       date | organization_id | total_donations_cents | total_donors |
|------------|-----------------|-----------------------|--------------|
| 2018-01-01 |               1 |                     0 |            0 |
| 2018-01-02 |               1 |                   250 |            1 |
| 2018-01-03 |               1 |                   250 |            1 |
| 2018-01-04 |               1 |                  1750 |            3 |
| 2018-01-05 |               1 |                  1750 |            3 |
| 2018-01-06 |               1 |                  1750 |            3 |
| 2018-01-07 |               1 |                   750 |            2 |
| 2018-01-08 |               1 |                   850 |            2 |
| 2018-01-09 |               1 |                   850 |            2 |
| 2018-01-10 |               1 |                   500 |            1 |
| 2018-01-11 |               1 |                   500 |            1 |
| 2018-01-12 |               1 |                   500 |            1 |
| 2018-01-13 |               1 |                  1000 |            1 |
| 2018-01-14 |               1 |                  1000 |            1 |
| 2018-01-15 |               1 |                     0 |            0 |
| 2018-01-01 |               2 |                     0 |            0 |
| 2018-01-02 |               2 |                   250 |            1 |
| 2018-01-03 |               2 |                   250 |            1 |
| 2018-01-04 |               2 |                  1750 |            2 |
| 2018-01-05 |               2 |                  1750 |            2 |
| 2018-01-06 |               2 |                  1750 |            2 |
| 2018-01-07 |               2 |                  1750 |            2 |
| 2018-01-08 |               2 |                  2000 |            2 |
| 2018-01-09 |               2 |                  2000 |            2 |
| 2018-01-10 |               2 |                  1500 |            1 |
| 2018-01-11 |               2 |                  1500 |            1 |
| 2018-01-12 |               2 |                     0 |            0 |
| 2018-01-13 |               2 |                  1000 |            2 |
| 2018-01-14 |               2 |                   500 |            1 |
| 2018-01-15 |               2 |                     0 |            0 |