用 redshift SQL 替换基于循环的重复数据删除代码

Posted

技术标签:

【中文标题】用 redshift SQL 替换基于循环的重复数据删除代码【英文标题】:replacing loop-based deduplication code with redshift SQL 【发布时间】:2018-12-05 20:26:28 【问题描述】:

我们正在尝试将大量用于数据集操作的遗留 R 代码迁移到 redshift SQL。所有这些都很容易移植,除了下面的位,这已被证明是难以处理的。这就是我来找你的原因,温柔的读者。我怀疑我问的是不可能的,但我没有能力证明它。

下面的 R 代码所做的是使用循环机制对唯一整数标识符进行重复数据删除。您将在内联 cmets 中看到完整的详细信息。

在我们开始之前,这里有一个带注释的小示例集,可让您了解所需的 SQL 代码应该产生的影响:

这是我们尝试用 redshift SQL 替换的带注释的 R 代码:

# the purpose of this function is to dedupe a set of identifiers
    # so that each month, the set if identifiers grouped under that month
    # will not have appeared in the previous two months
    # it does this by building 3 sets:
        # current month
        # previous month
        # 2 months ago
        # In a loop, it sets the current month set for the current year-month value in the loop
            # then filters that set against the contents of previous 2 months' sets
            # then unions the surving months set against the survivors of previous months so far

# I believe the functionality below is mainly taken from library(dplyr)
library(dplyr)
library(tidyverse)
library(lubridate)
library(multidplyr) 
library(purrr)
library(stringr)
library(RJDBC)

dedupeIdentifiers <- function(dataToDedupe, YearToStart = 2014, YearToEnd = 2016)  
    # dataToDedupe is input set
    # YearToStart = default starting year
    # YearToEnd = default ending year

    monthYearSeq <- expand.grid(Month = 1:12, Year = YearToStart:YearToEnd) %>% tbl_df() # make a grid having all months 1:12 from starting to ending year
    twoMonthsAgoIdentifiers <- data_frame(propertyid = integer(0)) # make empty data frame to hold list of unique identifiers
    oneMonthAgoIdentifiers  <- data_frame(propertyid = integer(0)) # make empty data frame to hold list of unique identifiers
    identifiersToKeep <- dataToDedupe %>% slice(0) # make empty data frame to hold list of unique identifiers

    for(i in 1:nrow(monthYearSeq)) 
        curMonth <- monthYearSeq$Month[i] # get current month for row in loop of monthYearSeq
        curYear <- monthYearSeq$Year[i] # get current year for row in loop of monthYearSeq

        curIdentifiers <- dataToDedupe %>% filter(year(initialdate) == curYear, month(initialdate) == curMonth)%>% 
            # initialdate is the date variable in the set by which the set is filtered
            # start by filtering to make a subset, curIdentifiers, which is the set where initialdate == current month and year in the loop
            group_by(uniqueidentifier) %>% slice(1) %>% ungroup() %>%  # take just 1 example of each unique identifier in the subset
            anti_join(twoMonthsAgoIdentifiers) %>% # filter out uniqueidentifier that were in set two months ago
            anti_join(oneMonthAgoIdentifiers) # filter out uniqueidentifier that were in set one month ago

        twoMonthsAgoIdentifiers <- oneMonthAgoIdentifiers # move one month set into two month set
        oneMonthAgoIdentifiers <- curIdentifiers %>% select(uniqueidentifier) # move current month set into one month set
        identifiersToKeep <- bind_rows(identifiersToKeep, curIdentifiers) # add "surviving" unique identifiers after filtering for last 2 months
            # to updated set of deduped indentifiers
     # lather, rinse, repeat

    return(identifiersToKeep) # return all survivors

最后,以下是我们迄今为止尝试过的一些事情,但没有成功:

    建议使用递归 CTE。 Redshift 不允许递归 CTE。 使用滞后来评估“当前”日期值和以前的日期值之间的日期差异,根据唯一标识符进行分区。这不适用于同一唯一标识符 123 的连续 1-5 个月集合。在这种情况下将保留第 4 个月和第 5 个月,但实际上应该删除第 5 个月。 在唯一标识符上自动将集合与自身进行左连接,以便可以评估所有月份的排列。 -- 这实际上和使用滞后有同样的问题。 使用包含所有所需月份和年份的虚拟日期集,将缺失的月份和年份注入要过滤的集合中。标记来自原始待过滤集合的行。然后使用dense_rank,根据唯一标识符和标志分区,选择rank % 3 = 0 的每一行。这样做的问题是,您不能总是让 dense_rank 值在分区中按需要进行计数,因此 % 3 值出现错误。 结合使用上述方法。 Replacing loop with set-based operation。

我们可以与原始循环代码达到约 90% 的奇偶性,但不幸的是我们必须有一个完美的替代品。

请尊重我们在 SQL 中重现这一点的目标,或者证明在这种情况下,用 SQL 重现循环的结果是不可能的。诸如“坚持使用 R”、“在 python 中执行循环”、“试试这个新包”之类的回答不会有帮助。

非常感谢您提供任何积极的建议。

【问题讨论】:

具有相同唯一标识符的值可以有多少?最大? 谢谢@JonScott。我之前打错了:在给定的月份(基于初始日期),应该只有一个唯一标识符值的实例,并且该值不应该出现在前 2 个月内。 可以为此使用redshift python UDF,对于每一行,您可以传递当前行数据和所有先前值的数组(您可以通过加入到array_agg 汇总子表)。然后,python 可以应用与 R 中相同的复杂逻辑并返回一个标志来指示是否应保留该行。 【参考方案1】:

您的流程可以在 Redshift 中使用“sql sessionization”技术完成。

基本上,您使用许多 LAG() 语句来比较特定窗口上的数据,然后比较结果以完成最终分类。

https://sonra.io/2017/08/14/redshifts-window-functions-advanced-use-case-sessionization/ https://www.dataiku.com/learn/guide/code/reshaping_data/sessionization.html https://blog.modeanalytics.com/finding-user-sessions-sql/

【讨论】:

谢谢!到目前为止,使用滞后还没有奏效,但我会试试这个,看看它是否提供了任何新的东西。 不要犹豫使用临时表或临时表。将逻辑分解成更简单的部分。还要确保为临时表声明 dist 键。如果它们都具有相同的蒸馏键,它将加快最终计算。 这些文章的精髓似乎是“使用滞后回溯标记您想要保留/操作的行”。我还没有找到一种可以处理所有情况的滞后方法,但我会试一试。只是为了让您知道对所有数据的方法的完整测试有点涉及,所以如果我不快速响应,这就是原因。我会看看我是否可以从过去的努力中挖掘出一些滞后没有奏效的例外情况;也许这会暴露出我所缺少的有关您的会话化方法的任何信息。

以上是关于用 redshift SQL 替换基于循环的重复数据删除代码的主要内容,如果未能解决你的问题,请参考以下文章

SQL - Redshift 滞后函数获取重复项

快乐数(编写一个算法来判断一个数是不是“快乐数”。 一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和, 然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不

AWS Glue to Redshift:是否可以替换,更新或删除数据?

SQL Server用最后一个值替换NULL [重复]

调试 Firehose 未交付给 Redshift 的原因 [重复]

用基于该字符串的变量替换bash中的字符串[重复]