【发布时间】: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 中相同的复杂逻辑并返回一个标志来指示是否应保留该行。
标签: r loops amazon-redshift lag dense-rank