在 SQL Server 中结合 dbplyr 和 case_when

Posted

技术标签:

【中文标题】在 SQL Server 中结合 dbplyr 和 case_when【英文标题】:Combining dbplyr and case_when in SQL Server 【发布时间】:2018-08-23 03:51:24 【问题描述】:

我正在使用 dbplyr 在 SQL Server 中编写和运行查询,并希望应用条件变异。这可以使用ifelsecase_when 来完成。查询在使用ifelse 时有效,但在使用case_when 时抛出异常。

问题似乎是这两个命令被翻译成的 SQL 语法。 case_when 语法似乎不是有效的 SQL。你能告诉我为什么以及如何解决它吗?或者这是一个错误?

# libraries
library(DBI)
library(dplyr)
library(dbplyr)

# establish connection to database table
connection_string = "database.specific.string"
# mine looks something like "DRIVER=...; Trusted_Connection=...; DATABASE=...' SERVER=..."
db_connection = dbConnect(odbc::odbc(), .connection_string = connection_string)
my_table = tbl(db_connection, from = my_table_name)

# attempted query
tmp = my_table %>%
    mutate(new_col = case_when(col1 == col2 ~ "a",
                               col1 != col2 ~ "b"))

# check SQL code for query
show_query(tmp)

生成的 SQL 查询是:

SELECT 
    col1, col2,
    CASE
       WHEN CONVERT(BIT, IIF(col1 = col2, 1.0, 0.0))) THEN ('a')
       WHEN CONVERT(BIT, IIF(col1 <> col2, 1.0, 0.0))) THEN ('b')
    END AS new_col
FROM my_database.my_table_name

运行此代码会引发错误

在预期条件的上下文中指定的非布尔类型表达式,靠近“THEN”

但是ifelse 查询按预期工作:

# attempted query
tmp = my_table %>%
    mutate(new_col = ifelse(col1 == col2, "a", "b"))

# check SQL code for query
show_query(tmp)

生成的 SQL 查询是:

SELECT 
    col1, col2,
    CASE
       WHEN (CONVERT(BIT, IIF(col1 = col2, 1.0, 0.0))) = TRUE) THEN ('a')
       WHEN (CONVERT(BIT, IIF(col1 = col2, 1.0, 0.0))) = FALSE) THEN ('b')
    END AS new_col
FROM my_database.my_table_name

请注意,在这两种情况下,SQL 语法都是使用show_query 生成的。使用translate_sql 生成 SQL 代码始终可以生成更清晰的 SQL 语法,但这不是在服务器上运行的语法。

还有其他人得到这些 SQL 查询吗?关于什么是错误的以及如何解决这个问题的任何建议?

更新

以issue on the tidyverse 发布,并获悉已经为case_when(..., TRUE ~ "b") 开发了解决方案,并将其转换为ELSE 'b' (here)。

但是,因为这没有解决导致此异常的语法。编辑问题以关注导致原因的语法。

更新 2

以issue on dbplyr 发布。来自 Christophe Dervieux (cderv) 的响应表明原因似乎是 SQL 服务器需要对 case_when 进行特殊翻译,就像对 ifelse 一样。

同时,用户可以使用多个ifelseif_else 语句。

【问题讨论】:

我也尝试使用 ifelse 而不是 case_when 这给出:CASE WHERE (CONVERT(BIT, IIF(col1 = col2, 1.0, 0.0))) = TRUE) THEN ('a') 确实有效。所以也许这是一个错误? 有一个ongoing PR 建议用ELSE 子句替换WHEN(TRUE)。这应该可以解决您的问题。 【参考方案1】:

只是你的 dplyr 语法有点错误?

试试这个

# attempted query
tmp = my_table %>%
    mutate(new_col = case_when(col1 == col2 ~ "a",
                               col1 == 'TRUE' ~ "b"
              # alternatively  col1 == 1 ~ "b"
)) 

【讨论】:

抱歉,不正确的 dplyr 语法只是我试图同时在 R 和 SQL 中思考的一个错字(已在上面修复)。您提出的语法仍然会产生导致错误的相同 SQL 代码。 嗯,这很奇怪,因为它似乎在我的数据库上工作......这可能与你的表中存储的布尔值有关吗?因为它们存储为文本“真”还是 1/0?另一件事是使用ifelse(不是ifelse ifelse 可以正常工作,但case_when 不能。不同之处在于ifelse产生的SQL最后包含`=TRUE`,而case_when产生的SQL没有。 我无法控制布尔值如何存储在数据库中。我什至不确定如何检查。【参考方案2】:

请参阅上面的更新 2:

这似乎是影响 SQL Server 的 case_when 的问题。

目前的解决方法是使用多个ifelseif_else 语句:

data %>%
    mutate(new_col = ifelse(condition1, val1, NA)) %>%
    mutate(new_col = ifelse(is.na(new_col) & condition2, val2, new_col)) %>%
    mutate(new_col = ifelse(is.na(new_col) & condition3, val3, new_col))
    # etc

【讨论】:

以上是关于在 SQL Server 中结合 dbplyr 和 case_when的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有数据库连接的情况下从 dbplyr 生成 SQL?

通过 dbplyr/bigRquery 将 summarise() 调用中的分位数返回到 BigQuery SQL 数据库

在 SQL Server 中结合 CTE“WITH”和“WITH XMLNAMESPACES....”

结合 SQL Server 的“LIKE”和“IN”[重复]

将 sql server spatial 和 NHibernate 与 CreateSqlQuery 相结合

使用 memdb 和 dbplyr 设置覆盖 == TRUE