【问题标题】:Increment date within while loop using postgresql on Redshift table在 Redshift 表上使用 postgresql 在 while 循环内增加日期
【发布时间】:2020-11-27 18:39:44
【问题描述】:

我的情况:

我编写了一段代码,它返回一个数据集,其中包含一个网络用户过去 90 天的聚合活动,并在一些计算之后返回一个分数。本质上,就像 RFV。

代码的(非常)简化版本如下所示:

WITH start_data AS (
        SELECT user_id
        ,COUNT(web_visits) AS count_web_visits
        ,COUNT(button_clicks) AS count_button_clicks
        ,COUNT(login) AS count_log_in 
        ,SUM(time_on_site) AS total_time_on_site
        ,CURRENT_DATE AS run_date
        FROM web.table 
        WHERE  TO_CHAR(visit_date, 'YYYY-MM-DD') BETWEEN DATEADD(DAY, -90, CURRENT_DATE) AND CURRENT_DATE
        AND some_flag = 1
        AND some_other_flag = 2
        GROUP BY user_id
        ORDER BY user_id DESC 
)

输出可能如下所示:

| user_id | count_web_visits | count_button_clicks | count_log_in | total_time_on_site | run_date |
|---------|------------------|---------------------|--------------|--------------------|----------|
| 1234567 | 256              | 932                 |16            |  1200              | 23-01-20 |
| 2391823 | 710              | 1345                |308           |  6000              | 23-01-20 |
| 3729128 | 67               | 204                 |83            |  320               | 23-01-20 |
| 5561296 | 437              | 339                 |172           |  3600              | 23-01-20 |

然后,此输出将存储在它自己的 AWS/Redhsift 表中,并将形成任务的基表。

SELECT *
into myschema.base_table
FROM start_data 

期望的输出:

我需要做的是迭代地运行此代码,以便我每天将新数据附加到 myschema.base_table,用于前 90 天的聚合。

在我看来,我可以前进或后退,没关系。

也就是说,我可以:

  1. 从今天开始,每天运行代码,前 90 天,返回到(表中的第一个日期 + 90 天)

  1. 从(表中的第一个日期 + 90 天)开始,每天运行前 90 天的代码,一直到今天。

选项 2 对我来说似乎是最好的选择,所需的输出如下所示(仅用于说明的分区):

        | user_id | count_web_visits | count_button_clicks | count_log_in | total_time_on_site | run_date |
        |---------|------------------|---------------------|--------------|--------------------|----------|
        | 1234567 | 412              | 339                 |180           |  3600              | 20-01-20 |
        | 2391823 | 417              | 6253                |863           |  2400              | 20-01-20 |
        | 3729128 | 67               | 204                 |83            |  320               | 20-01-20 |
        | 5561296 | 281              | 679                 |262           |  4200              | 20-01-20 | 
        |---------|------------------|---------------------|--------------|--------------------|----------|
        | 1234567 | 331              | 204                 |83            |  3200              | 21-01-20 |
        | 2391823 | 652              | 1222                |409           |  7200              | 21-01-20 |
        | 3729128 | 71               | 248                 |71            |  720               | 21-01-20 |
        | 5561296 | 366              | 722                 |519           |  3600              | 21-01-20 |
        |---------|------------------|---------------------|--------------|--------------------|----------|
        | 1234567 | 213              | 808                 |57            |  3600              | 22-01-20 |
        | 2391823 | 817              | 4265                |476           |  1200              | 22-01-20 |
        | 3729128 | 33               | 128                 |62            |  120               | 22-01-20 |
        | 5561296 | 623              | 411                 |283           |  2400              | 22-01-20 |
        |---------|------------------|---------------------|--------------|--------------------|----------|
        | 1234567 | 256              | 932                 |16            |  1200              | 23-01-20 |
        | 2391823 | 710              | 1345                |308           |  6000              | 23-01-20 |
        | 3729128 | 67               | 204                 |83            |  320               | 23-01-20 |
        | 5561296 | 437              | 339                 |172           |  3600              | 23-01-20 |

我的尝试: 我已经成功创建了一个WHILE 循环来按顺序递增日期,如下所示:

CREATE OR REPLACE PROCEDURE retrospective_data()
LANGUAGE plpgsql
AS $$
DECLARE 
    start_date DATE := '2020-11-20' ;
BEGIN
  WHILE CURRENT_DATE > start_date
  LOOP 
    RAISE INFO 'Date: %', start_date;
    start_date = start_date + 1;
  END LOOP;
  RAISE INFO 'Loop Statment Executed Successfully';
END;
$$;

CALL retrospective_data();

因此产生如下日期:

INFO:  Date: 2020-11-20
INFO:  Date: 2020-11-21
INFO:  Date: 2020-11-22
INFO:  Date: 2020-11-23
INFO:  Date: 2020-11-24
INFO:  Date: 2020-11-25
INFO:  Date: 2020-11-26
INFO:  Loop Statment Executed Successfully
Query 1 OK: CALL

我需要帮助:

我需要能够将WHILE 循环应用于初始代码,以便WHERE 子句变为:

   WHERE TO_CHAR(visit_date, 'YYYY-MM-DD') BETWEEN DATEADD(DAY, -90, start_date) AND start_date 

但是 start_date 是每个增量循环的结果。此外,每次执行的结果都需要附加到上一次。

任何帮助表示赞赏。

【问题讨论】:

    标签: postgresql amazon-web-services loops while-loop amazon-redshift


    【解决方案1】:

    很明显,您来自过程编程背景,第一个建议是停止思考循环。数据库是巨大而强大的数据过滤机器,“执行第 1 步,然后第 2 步”的思维方式通常会导致错过所有这些功能。

    您想要查看窗口函数,这些函数允许您查看正在评估的每一行的其他行的范围。这正是你想要做的。

    此外,您不应该将日期转换为字符串只是为了将其与其他日期进行比较(WHERE 子句)。这只是额外的强制转换,破坏了 Redshift 的表扫描优化。 Redshift 使用块元数据来优化需要从磁盘读取哪些数据,但如果将列转换为另一种数据类型,这将无法正常工作。

    现在开始您的代码(即兴重写,仅针对第一列)。请注意,group by 子句在窗口函数之前运行,并且我假设并非所有用户每天都有访问。而且由于 Redshift 不支持窗口函数中的 RANGE,因此需要确保所有日期都代表所有用户 ID。这是通过 UNIONing 完成的,其中包含足够数量的覆盖日期范围的行。您可能有一张这样的表格,或者可能想要创建一个表格,但我会即时生成一些内容来显示该过程(并且此过程假设密集日期少于表格中的行 - 可能但不是铁包)。

    SELECT user_id
    ,COUNT(web_visits) AS count_web_visits_by_day,
    ,SUM(count_web_visits_by_day) OVER (partition by user_id order by visit_date rows between 90 preceding and current row)
    ...
    ,visit_date
    FROM (
        SELECT visit_date, user_id, web_visits, ...
        FROM web.table 
        WHERE some_flag = 1 AND some_other_flag = 2
        UNION ALL  -- this is where I want to union with a full set of dates by user_id
        ( SELECT visit_date, user_id, NULL as web_visits, ...
          FROM (
            SELECT DISTINCT user_id FROM web.table
            CROSS JOIN
            SELECT CURRENT_DATE + 1 - row_number() over (order by visit_date) as visit_date
            FROM web.table
          )
        )
    )
    GROUP BY visit_date, user_id
    ORDER BY visit_date ASC, user_id DESC ;
    

    这里的想法是设置您的数据以确保每个日期的每个 user_id 至少有一行。然后窗口函数可以对“按日期和user_id分组”信息进行操作,对过去90行(与过去90天相同)进行汇总和计数。您现在拥有所有日期的所有信息,每个日期都回溯超过 90 天。一个查询为您提供所有信息,没有 while 循环,没有存储过程。

    未经测试,但应该给你模式。您可能想要按摩输出以提供您正在寻找的范围并清理 NULL 结果行。

    【讨论】:

    • 谢谢@Bill Weiner。尽管这并没有特别回答我的问题,但您的 cmets 围绕不将 mysef 限制在过程编程概念和推荐使用窗口函数(当然我非常清楚但在这里忽略了作为一个可行的选择)帮助很大,这在turn 允许我重新考虑解决问题的方法,这意味着我能够解决它。
    • 很高兴听到它
    猜你喜欢
    • 2021-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-17
    • 1970-01-01
    • 1970-01-01
    • 2016-03-02
    • 1970-01-01
    相关资源
    最近更新 更多