您可以使用您已经熟悉的 R 的 tidyverse 包在 Snowflake 中执行此操作。
dbplyr 包扩展了 dplyr 包,以支持将 dplyr 动词转换为其 SQL 等效项并在数据库中执行它们。 Dbplyr 支持 Snowflake 作为数据库内执行的数据库。
首先以 Felipe Hoffa 提供的数据示例进行演示。
library(odbc)
library(DBI)
library(dbplyr)
library(dplyr)
library(lubridate)
# Snowflake Database Connection details
server <- "<your snowflake account here>" e.g."demo43.snowflakecomputing.com"
uid <- "<your user name>"
database <- "<your database>"
schema <- "<your schema>"
warehouse <- "<your virtual warehouse>"
pwd <- "<your password>"
# Obtain ODBC Connection
con <- dbConnect(odbc::odbc(),
.connection_string =
sprintf("Driver={Snowflake};server={%s};uid={%s};
pwd={%s};database={%s};schema={%s};warehouse={%s}",
server, uid, pwd, database, schema, warehouse ) ,
timeout = 10)
# Create a tbl referencing felipes sample database table in Snowflake
df_product <- tbl(con, "SAMPLE_PRODUCT_DATA")
# First we will get the data to the client R environment to show dplyr
# functionality running on a local dataframe.
(df_product_local <- df_product %>% collect())
#> #A tibble: 2 × 3
#> A B C
#> <chr> <chr> <chr>
#> 1 a b c
#> 2 x this is null z
现在使用 dplyr 动词将值 'this is null' 转换为本地数据帧上的 NA
df_product_local %>% mutate(across(everything(), ~na_if(., 'this is null')))
#> # A tibble: 2 × 3
#> A B C
#> <chr> <chr> <chr>
#> 1 a b c
#> 2 x NA z
并执行相同的代码替换引用雪花表的 tbl 的本地数据帧
df_product %>% mutate(across(everything(), ~na_if(., 'this is null')))
#> # Source: SQL [2 x 3]
#> # Database: Snowflake 6.28.0[SFIELD@Snowflake/SF_TEST]
#> A B C
#> <chr> <chr> <chr>
#> 1 a b c
#> 2 x NA z
如果您想在 Snowflake 中处理转换并将清理后的结果返回到您的本地 R 环境以进行进一步的本地处理
df_product_cleaned <- df_product %>%
mutate(across(everything(), ~na_if(., 'this is null'))) %>%
collect()
head(df_product_cleaned)
#> # A tibble: 2 × 3
#> A B C
#> <chr> <chr> <chr>
#> 1 a b c
#> 2 x NA z
现在让我们将相同的方法应用于您遇到的原始日期问题。
# First we create a table with mixed data; character and date columns.
mix_tblname = "SAMPLE_MIXED"
sql_ct <- sprintf("create or replace table %s as
select 'a' a, 'b' b, 'c' c,
'1900-01-01'::DATE x, '2022-08-17'::DATE y, '1900-01-01'::DATE z
union all
select 'x', 'this is null', 'z',
'2022-08-17'::DATE, '1900-01-01'::DATE, '2022-08-15'::DATE",
mix_tblname )
dbExecute(con, sql_ct)
# And reference the new table with a database tbl
df_mixed <- tbl(con, mix_tblname)
df_mixed_local <- df_mixed %>% collect()
# Check the raw data looks OK
head(df_mixed)
#> # Source: SQL [2 x 6]
#> # Database: Snowflake 6.28.0[SFIELD@Snowflake/SF_TEST]
#> A B C X Y Z
#> <chr> <chr> <chr> <date> <date> <date>
#> 1 a b c 1900-01-01 2022-08-17 1900-01-01
#> 2 x this is null z 2022-08-17 1900-01-01 2022-08-15
下面的代码失败了,因为我们有混合类型的列。并且非 Date 列不能被强制为 DATE
df_mixed %>% mutate(across(everything(), ~na_if(., TO_DATE('1900-01-01', 'YYYY-MM-DD'))))
我们可以将所有列隐式转换为字符并作为字符表达式进行计算。
df_mixed %>% mutate(across(everything(), ~na_if(.,'1900-01-01')))
#> # Source: SQL [2 x 6]
#> # Database: Snowflake 6.28.0[SFIELD@Snowflake/SF_TEST]
#> A B C X Y Z
#> <chr> <chr> <chr> <date> <date> <date>
#> 1 a b c NA 2022-08-17 NA
#> 2 x this is null z 2022-08-17 NA 2022-08-15
尽管这可行,但它会选择包含相同值的其他列类型,这可能是您不想要的。所以我们需要一种识别 DATE 列的方法。
这是我可以在本地数据框上执行此操作的方法
df_mixed_local %>% mutate(across(where(~ is.Date(.x)), ~na_if(.,'1900-01-01')))
#> # A tibble: 2 × 6
#> A B C X Y Z
#> <chr> <chr> <chr> <date> <date> <date>
#> 1 a b c NA 2022-08-17 NA
#> 2 x this is null z 2022-08-17 NA 2022-08-15
但它不适用于数据库 tbl。您可以看到此处生成的 SQL 显然缺少按列转换。
df_mixed %>% mutate(across(where(~ is.Date(.x)), ~na_if(.,'1900-01-01'))) %>% show_query()
#> <SQL>
#> SELECT *
#> FROM "SAMPLE_MIXED"
我尝试了一些事情,但找不到一种对日期类型进行过滤的 TIDY 方式,所以改为......
我们可以从 Snowflakes Information Schema 中获取日期列的向量
## Switch session to the Information Schema
dbExecute(con, 'USE SCHEMA INFORMATION_SCHEMA')
dateCols <- tbl(con, 'COLUMNS') %>%
filter(TABLE_CATALOG == database,
TABLE_SCHEMA == schema,
TABLE_NAME == mix_tblname,
DATA_TYPE == 'DATE') %>%
select(COLUMN_NAME) %>%
arrange(ORDINAL_POSITION) %>%
pull()
## Switch session back to our data schema
dbExecute(con, sprintf('USE SCHEMA %s',schema ))
现在使用 dateCols 我们可以选择性地将我们的转换应用到 DATE 列
df_mixed %>% mutate(across(all_of(dateCols), ~na_if(.,TO_DATE('1900-01-01', 'YYYY-MM-DD'))))
#> # Source: SQL [2 x 6]
#> # Database: Snowflake 6.28.0[SFIELD@Snowflake/SF_TEST]
#> A B C X Y Z
#> <chr> <chr> <chr> <date> <date> <date>
#> 1 a b c NA 2022-08-17 NA
#> 2 x this is null z 2022-08-17 NA 2022-08-15
如果有人发现在输入列上应用 DATE 数据类型过滤器的 TIDY 方式,我有兴趣看到它。