簡體   English   中英

在 Redshift 中查找下一個最舊的行

[英]Find the next oldest row in Redshift

我在 Redshift 中有一個名為 user_activity 的表,其中包含部門、user_id、activity_type、activity_id、activity_date。

我想查詢自上次事件(任何類型)以來多少天的每日報告。 使用 CROSS APPLY (SQL Server) 或 LATERAL JOIN (Postgres 9+),我會做類似...

SELECT d.date, a.last_activity_date
FROM date_table d
CROSS JOIN (
            SELECT DISTINCT user_id FROM activity_table
        ) u
CROSS APPLY (
                SELECT TOP 1 activity_date as last_activity_date
                FROM activity_table
                WHERE user_id = u.user_id AND activity_date <= d.date
                ORDER BY activity_date DESC
            ) a

現在,我寫的和下面類似,但是有點慢,恐怕只會越來越慢。

with user_activity as (
    select distinct activity_date, user_id from activity_table
)
select
    d.date, u.user_id,
    max(u.activity_date) as last_activity_date
from date_table d
inner join user_activity u on u.activity_date <= d.date
where d.date between '2020-01-01' and current_date
group by 1, 2

有人可以為我的需求或交叉應用/橫向連接建議一個好的替代方案。

正如您所看到的,交叉連接和不平等連接會隨着數據的增長而減慢,並且通常不是您在 Redshift 中想要的方法。 這是因為當應用於 Redshift 中典型的大型 data.tables 時,此類操作會導致數據大小增加。

您想要使用 window 函數來執行此類分析。 但是您需要退后一步,重新考慮如何構造 SQL。一個 MAX(activity_date) window function,按 user_id 分區並按日期排序,並帶有前面所有行的框架子句,將找到任何活動的最新活動.

現在這將只為 data.table 中存在的 user_id 和日期生成行,看起來你想要為每個 user_id 的每個日期生成 1 行,對吧? 為此,您需要在 window function 之前的每個 user_id 的每個日期具有 1 行的數據幀中進行 UNION。其他列需要 NULL,以便數據寬度匹配。 您還需要將日期與 activity_date 放在單獨的列中。 現在所有用戶 ID 的所有日期都將在源中,window function 將為您提供所需的結果。

您還問“這比連接更好嗎?” 那么在連接中,您將根據可能變得非常大的日期數復制所有數據記錄。 在這種方法中,您只有原始數據記錄加上每個日期每個 user_id 的一行(這是輸出的大小),並且隨着每個 user_id 的記錄數增加,這種方法不會。

——— 請求根據對他們方法的評論修改提問者的代碼 ———

您的代碼絕對是在正確的軌道上,因為您已經刪除了原始代碼中的大量不等式連接。 我對此發表了 2 條評論。 首先是我相信您需要 GROUP BY user_id, date 以防止每個日期每個 user_id 多行,如果在一個日期有不同 activity_types 的相同 user_id 的記錄會導致這種情況。 這是一個簡單的疏忽。

第二個是 state,我打算讓您在結合實際數據和 user_id/date 框架時使用 UNION ALL,而不是 LEFT JOIN。 您的方法工作正常,但我發現與大量數據聯合通常比加入更快,但您確實需要確保列匹配。 無論哪種方式,我們最終都會得到一個包含 3 列的數據段 - 2 個日期列,一個框架行的 NULL 和 1 個 user_id。 您的方法很好,除非您有很大的表,否則性能差異可能很小。

由於您要求重寫,這里有兩個更改。 (注意:我的筆記本電腦在商店里,所以我目前還沒有准備好訪問 Redshift 並且這個 SQL 未經測試。如果意圖不明確並且你需要我調試它會延遲幾天.我保留了你的設置方法和 SQL 結構。)

with date_table as (
    select '2000-01-01'::date as date
    union all
    select '2000-01-02'::date
    union all
    select '2000-01-03'::date
    union all
    select '2000-01-04'::date
    union all
    select '2000-01-05'::date
    union all
    select '2000-01-06'::date
),
users as (
    select 1 as user_id
    union all
    select 2
    union all
    select 3
),
user_activity as (
    select 1 as user_id, '2000-01-01'::date as activity_date
    union all
    select 1 as user_id, '2000-01-04'::date as activity_date
    union all
    select 3 as user_id, '2000-01-03'::date as activity_date
    union all
    select 1 as user_id, '2000-01-05'::date as activity_date
    union all
    select 1 as user_id, '2000-01-06'::date as activity_date
),
user_dates as (
    select d.date, u.user_id
    from date_table d
    cross join users u
),
user_date_activity as (
    select cal_date, user_id,
        lag(max(activity_date), 1) ignore nulls over (partition by user_id order by date) as last_activity_date
    from (
        Select user_id, date as cal_date, NULL as activity_date from user_dates
        Union all
        Select user_id, activity_date as cal_date, activity_date from user_activity 
    )
    Group by user_id, cal_date
)
select * from user_date_activity
order by user_id, cal_date```

這是我根據比爾的回答提出的問題。

with date_table as (
    select '2000-01-01'::date as date
    union all
    select '2000-01-02'::date
    union all
    select '2000-01-03'::date
    union all
    select '2000-01-04'::date
    union all
    select '2000-01-05'::date
    union all
    select '2000-01-06'::date
),
users as (
    select 1 as user_id
    union all
    select 2
    union all
    select 3
),
user_activity as (
    select 1 as user_id, '2000-01-01'::date as activity_date
    union all
    select 1 as user_id, '2000-01-04'::date as activity_date
    union all
    select 3 as user_id, '2000-01-03'::date as activity_date
    union all
    select 1 as user_id, '2000-01-05'::date as activity_date
    union all
    select 1 as user_id, '2000-01-06'::date as activity_date
),
user_dates as (
    select d.date, u.user_id
    from date_table d
    cross join users u
),
user_date_activity as (
    select ud.date, ud.user_id,
        lag(ua.activity_date, 1) ignore nulls over (partition by ud.user_id order by ud.date) as last_activity_date
    from user_dates ud
    left join user_activity ua on ud.date = ua.activity_date and ud.user_id = ua.user_id
)
select * from user_date_activity
order by user_id, date

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM