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

时间:2016-01-05 14:43:56

标签: mysql postgresql postgresql-9.4 integral

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

我想要获取的结果部分值,基于total_days金额超过的天数。 E.g。

  • start_date:01/01/2016,
  • duration_in_months:2,
  • total_days:60,
  • value_x:120。

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

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

在我的数据集中,我有超过100k的记录,我需要按月分组这个部分值(逐月添加部分值)。

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

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

非常感谢您提前了解并随时提出更多详情。

3 个答案:

答案 0 :(得分:1)

首先,您的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而不是日期/时间表达式中提取日期。我认为它符合你的要求。

答案 1 :(得分:1)

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

或者,您可以使用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
在PostgreSQL中可以使用DATEDIFF计算

date1 - date2,不需要使用EXTRACT(但参数的类型必须为date; timestamp(tz)差异会产生interval S)。

您可以使用GREATEST and LEAST来约束passed_days(如果您想要选择所有行),但如果您愿意,也可以在passed_days中使用WHERE。< / p>

答案 2 :(得分:0)

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

  • 按月分组:在查询开头使用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