【问题标题】:Is there a way to replace a specific value in multiple columns to null in SQL snowflake?有没有办法在 SQL 雪花中将多列中的特定值替换为空?
【发布时间】:2022-08-19 00:50:33
【问题描述】:

我在雪花中有一个表,其中多个数据列默认空值以1900-01-01 的形式出现,我将其导入,然后在我的机器上的 R 中手动将这些更改为null。但是,由于我正在处理 30M+ 行,因此我想尝试在雪花中而不是在我的本地机器中执行此操作,因为这需要很长时间。

我知道有一个replace() 函数,我可以手动引用每一列并将1900-01-01 替换为null 但是有没有办法引用数据类型等于数据的所有列,然后运行这个replace() 参数?

在 R 中,我们有 tidyselect 动词,因此在数据框中,我们可以根据列名或列类型中的模式动态引用许多列 - 看看 SQL 中是否有类似的东西?

  • NULLIF 是在一列上执行此操作的方法 NULLIF(date_col,\'1900-01-01\'::date) as date_col docs.snowflake.com/en/sql-reference/functions/nullif.html
  • 但不是简单的答案,因为 SQL 是一种 SET 逻辑,它的默认逻辑是每一列都是不同且有意义的东西,没有像桌面计算的 ARRAY 逻辑那样的“针对所有列”。因此,为什么您必须以一种或另一种形式命名所有列。
  • @SimeonPilgrim 检查很酷的 Python 替代方案 :)

标签: python sql stored-procedures snowflake-cloud-data-platform


【解决方案1】:

让我们用 Python 和 Snowpark 做一些魔术——因为这是处理问题所要求的多列的简单方法。

但首先,让我们建立一个表,我们想用 null 替换一个值:

create or replace table sample_product_data 
as 
select 'a' a, 'b' b, 'c' c
union all select 'x', 'this is null', 'z'

然后这是 Snowflake 中的 Python 存储过程,它将在该表上取任何等于 this is null 的值,并将其替换为 null:

create or replace temporary procedure replace_this_is_null() 
returns VARIANT 
language python 
runtime_version=3.8 
packages=('snowflake-snowpark-python') 
handler='main' 
as 
$$

import snowflake.snowpark as snowpark

def main(session: snowpark.Session):
    tbn = 'sample_product_data'
    session.table(tbn).replace('this is null', None).write.mode('overwrite').save_as_table(tbn)
    return 'done'
$$;

然后你可以用call replace_this_is_null() 调用它,它会按预期工作。

现在,由于问题想要替换日期:只需import datetime,而不是字符串,与datetime.date(1900, 1, 1) 进行比较。

【讨论】:

  • 打得好..我喜欢你解决了它。我发现自己被“我有大量 json blob,用一个 SP 处理所有不同形状的数据的表”的性质或这个表亲问题“我一般如何修复很多东西”的性质引发了我会给你一些网点/爱
  • 谢谢西蒙!这些 Snowpark 库的有趣之处在于它应该大规模执行(数据帧在内部被重写)
  • ? 很好地使用了 Snowpark。对于任何想知道纯 SQL 是否可以实现类似的人。是的,使用动态 SQL(从元数据构建查询)- 乏味。第二种方法是使用多态表函数(PTF)它们是 SQL:2016 标准的一部分——不幸的是,Snowflake 中尚不可用。它从字面上解决了所有需要动态结果集的情况,例如:读取 CSV 文件、真正的动态 PIVOT、SELECT EXCEPT 等。对于这种情况,它将是:CREATE OR REPLACE TABLE ... AS SELECT FROM my_ptf(table_name, datatype, new_default)
  • (续)。 PTF 的desribe 组件是非常强大的概念,因为它允许确定结果集模式在运行时. Sample of PTFPolymorphic Table Functions
【解决方案2】:

您可以使用您已经熟悉的 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 方式,我有兴趣看到它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 2021-02-22
    • 2022-11-29
    相关资源
    最近更新 更多