Postgresql 9.4:在同一查询中使用计算列并按月对部分结果进行分组

Postgresql 9.4: Use calculated column in the same query and group partial results by month

我正在查询 postgresql 9.4 数据库,我想使用同一查询中的列执行计算。

我试图获得的 结果 是一个 部分值,该值基于 total_days 数量 [=51] 中过去的天数=].例如

  • start_date:2016 年 1 月 1 日,
  • duration_in_months: 2,
  • total_days: 60,
  • value_x: 120.

如果我今天启动查询,05/01/2016,我想获得:

partial_result = value_x * passed_days / total_days
                  120   *      5      /    60

在我的数据集中,我有超过 10 万条记录,我需要获取按月分组的部分值(按月添加部分值)。

============================================= ============================

MySQL中我可以这样计算:

SELECT 
  start_date,
  duration_in_months, 
  @end_date:= DATE_ADD(start_date, INTERVAL duration_in_months MONTH) as end_date,
  @total_days:= DATEDIFF(@end_date, start_date),
  @passed_days:= DATEDIFF(CURDATE(), start_date),
  value_x,
  (value_x * @passed_days / @total_days) as partial_result

  FROM table;

按照 question previously asked 中的说明,我目前在 PostgreSQL 中使用如下查询:

SELECT
  start_date,
  duration_in_months,
  end_date,
  total_days,
  value_x,
  (value_x * passed_days / total_days) as partial_result

  FROM (SELECT *,
         (start_date + (duration_in_months || ' month')::INTERVAL) as end_date,
         EXTRACT(DAY FROM (start_date + (duration_in_months || ' month')::INTERVAL) - start_date) as total_days, 
         EXTRACT(DAY FROM current_date - start_date) as passed_days
        FROM table) as table1;

我需要你的帮助才能:

  • 在 PostgreSQL 中使用计算变量,如 MySQL 在查询中使用公式或找到另一种方法使查询更具可读性
  • 按月对部分结果进行分组
  • 插入一个 where 子句以确保

    passed_days >= 0 且 passed_days <= total_days

非常感谢您,欢迎随时询问更多详情。

首先,您的 MySQL 查询不能保证有效。 MySQL 文档非常明确地指出 SELECT 中表达式的求值顺序可以是任意的。因此,可以在设置变量之前评估最后一个表达式(好吧,实际上它们将被设置为前一行中的值)。

在 Postgres 中,我认为您对子查询或 CTE 的想法是正确的。您只需引用没有 @ 的列。我不知道具体的日期算法是否正确,但这是等效的查询:

SELECT start_date, duration_in_months, end_date, total_days, value_x,
       (value_x * passed_days / total_days) as partial_result
FROM (SELECT t.*,
             (start_date + (duration_in_months || ' month')::INTERVAL) as end_date,
             EXTRACT(DAY FROM (start_date + (duration_in_months || ' month')::INTERVAL) - start_date) as total_days, 
             EXTRACT(DAY FROM current_date - start_date) as passed_days
      FROM table t
     ) t;

extract(day) 在我看来是错误的,但是您是从 interval 而不是 date/time 表达式中提取日期。我认为它可以满足您的需求。

因为你的表达式相互使用,你应该使用多个子查询(如果你不想重复任何表达式)。

或者,您可以使用 LATERAL subqueries、f.ex:

SELECT  start_date,
        duration_in_months, 
        end_date,
        total_days,
        passed_days,
        value_x,
        (value_x * passed_days / total_days) as partial_result
FROM    table,
LATERAL (SELECT (start_date + (duration_in_months * INTERVAL '1 month'))::date end_date) end_date,
LATERAL (SELECT end_date - start_date::date total_days) total_days,
LATERAL (SELECT current_date - start_date::date passed_days) passed_days

DATEDIFF在PostgreSQL中可以用date1 - date2计算,不需要用EXTRACT(但参数的类型必须是datetimestamp(tz)差异产生 intervals).

您可以使用 GREATEST and LEAST 来约束 passed_days(如果您想要 select 所有行),但您也可以在 WHERE 中使用 passed_days , 如果你愿意的话。

我在 PostgreSQL 中找到了一个合适的解决方案:

  • 按月分组:在查询的开头使用 with table as ( ) 语句。然后做一个内连接
  • 声明变量: 使用子查询

=========================================== ============================

WITH time_ranges AS (
SELECT       to_date('2014-07-01', 'yyyy-mm-dd') as START_DATE, to_date('2014-07-31', 'yyyy-mm-dd') as END_DATE
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2014-08-31', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2014-09-30', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2014-10-31', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2014-11-30', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2014-12-31', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-01-31', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-02-28', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-03-31', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-04-30', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-05-31', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-06-30', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-07-31', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-08-31', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-09-30', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-10-31', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-11-30', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2015-12-31', 'yyyy-mm-dd')
  UNION SELECT to_date('2014-07-01', 'yyyy-mm-dd'), to_date('2016-01-05', 'yyyy-mm-dd')
)

SELECT time_ranges.end_date, round(SUM(gross_pdu * LEAST(total_days, GREATEST( EXTRACT(DAY FROM(time_ranges.end_date - guarantees_days.start_date)), 0) ) / total_days)::numeric, 2)
FROM
(SELECT
  *,
  EXTRACT(DAY FROM (start_date + (duration_in_months || ' month')::INTERVAL) - start_date) as total_days
FROM subscribed_guarantees
) as guarantees_days
INNER JOIN
time_ranges ON
time_ranges.start_date <= guarantees_days.start_date AND guarantees_days.start_date <= time_ranges.end_date
WHERE INSURANCE_COMPANY = 'INSURANCE COMPANY' AND TAX = 13.5
 GROUP BY
  time_ranges.end_date
 ORDER BY
  time_ranges.end_date