【问题标题】:Function that composes functions with existing sql translations in dbplyr使用 dbplyr 中现有的 sql 翻译组合函数的函数
【发布时间】:2020-02-01 07:12:15
【问题描述】:

之所以出现这个问题,是因为我想为方便起见创建一个函数:

as.numeric_psql <- function(x) {

   return(as.numeric(as.integer(x)))
}

将远程 postgres 表中的布尔值转换为数字。转换为整数的步骤需要如下:

在数字和布尔值之间没有定义直接转换。您可以使用整数作为中间立场。 (https://stackoverflow.com/a/19290671/2109289)

当然,这个函数在本地可以正常工作:

copy_to(con_psql, cars, 'tmp_cars')

tmp_cars_sdf <-
    tbl(con_psql, 'tmp_cars')


tmp_cars_sdf %>%
    mutate(low_dist = dist < 5) %>%
    mutate(low_dist = as.numeric(as.integer(low_dist)))

# # Source:   lazy query [?? x 3]
# # Database: postgres 9.5.3
#     speed  dist low_dist
#     <dbl> <dbl>    <dbl>
# 1     4     2        1
# 2     4    10        0
# 3     7     4        1
# 4     7    22        0
# 5     8    16        0

cars %>%
    mutate(low_dist = dist < 5) %>%
    mutate(low_dist = as.numeric_psql(low_dist)) %>%
    head(5)

#   speed dist low_dist
# 1     4    2        1
# 2     4   10        0
# 3     7    4        1
# 4     7   22        0
# 5     8   16        0

但是,在远程数据帧上使用时不起作用,因为as.numeric_psql不在sql翻译列表中,所以被逐字传递给查询:

> tmp_cars_sdf %>%
+     mutate(low_dist = dist < 5) %>%
+     mutate(low_dist = as.numeric_psql(low_dist))
Error in postgresqlExecStatement(conn, statement, ...) : 
  RS-DBI driver: (could not Retrieve the result : ERROR:  syntax error at or near "as"
LINE 1: SELECT "speed", "dist", as.numeric_psql("low_dist") AS "low_...
                                ^
)

我的问题是是否存在一种简单的方法(即不定义自定义 sql 翻译)让 dplyr 了解函数 as.numeric_psql 是具有现有 sql 翻译的函数的组合,并改为使用这些翻译。

【问题讨论】:

    标签: r dplyr dbplyr


    【解决方案1】:

    避免错误的一种方法是将函数设置为对数据帧进行操作,而不是在内部变异。例如:

    copy_to(con_psql, cars, 'tmp_cars')
    
    tmp_cars_sdf <- tbl(con_psql, 'tmp_cars')
    
    as.numeric_psql <- function(data, x) {
      return(data %>% mutate({{x}} := as.numeric(as.integer({{x}}))))
    }
    
    tmp_cars_sdf %>%
      mutate(low_dist = dist < 5) %>%
      as.numeric_psql(low_dist)
    
    #> # Source:   lazy query [?? x 3]
    #> # Database: sqlite 3.30.1 [:memory:]
    #>    speed  dist low_dist
    #>    <dbl> <dbl>    <dbl>
    #>  1     4     2        1
    #>  2     4    10        0
    #>  3     7     4        1
    #>  4     7    22        0
    #>  5     8    16        0
    #>  6     9    10        0
    #>  7    10    18        0
    #>  8    10    26        0
    #>  9    10    34        0
    #> 10    11    17        0
    #> # … with more rows
    

    请注意,在您的示例中,在数据库版本中,low_dist 在创建时已经被编码为整数,而不是像在标准 R 数据框中那样合乎逻辑:

    tmp_cars_sdf %>%
      mutate(low_dist = dist < 5) 
    #> # Source:   lazy query [?? x 3]
    #> # Database: sqlite 3.30.1 [:memory:]
    #>    speed  dist low_dist
    #>    <dbl> <dbl>    <int>
    #>  1     4     2        1
    #>  2     4    10        0
    #>  3     7     4        1
    #>  4     7    22        0
    #>  5     8    16        0
    #>  6     9    10        0
    #>  7    10    18        0
    #>  8    10    26        0
    #>  9    10    34        0
    #> 10    11    17        0
    #> # … with more rows
    
    cars %>%
      mutate(low_dist = dist < 5) %>% head
    #>   speed dist low_dist
    #> 1     4    2     TRUE
    #> 2     4   10    FALSE
    #> 3     7    4     TRUE
    #> 4     7   22    FALSE
    #> 5     8   16    FALSE
    #> 6     9   10    FALSE
    

    【讨论】:

      【解决方案2】:

      只要您正在编写的所有函数都有现有的翻译,以下应该可以工作:

      1. 使用dbplyr::translate_sql包装函数调用
      2. 使用 {{ }} 在函数中包含变量
      3. 在 mutate with !! 中取消引用您的新函数调用以强制评估
      library(DBI)
      library(dplyr)
      #> 
      #> Attaching package: 'dplyr'
      #> The following objects are masked from 'package:stats':
      #> 
      #>     filter, lag
      #> The following objects are masked from 'package:base':
      #> 
      #>     intersect, setdiff, setequal, union
      con <- dbConnect(RSQLite::SQLite(), ":memory:")
      
      cars <- tibble::tribble( ~ speed, ~ dist, ~ low_dist,
                               4,    2,        1,
                               4,   10,        0,
                               7,    4,        1,
                               7,   22,        0,
                               8,   16,        0)
      
      dbWriteTable(con, "tmp_cars", cars)
      
      tmp_cars_sdf <- tbl(con, "tmp_cars")
      
      as.numeric_psql <- function(x) {
        return(as.numeric(as.integer(x)))
      }
      
      as.numeric_psql2 <- function(x) {
        dbplyr::translate_sql(
          as.numeric(
            as.integer({{x}})
            )
          )
      }
      
      tmp_cars_sdf %>%
        mutate(low_dist = dist < 5) %>%
        mutate(
          low_dist_old = as.numeric_psql(low_dist),
          low_dist_new = !!as.numeric_psql2(low_dist)
        ) %>% show_query()
      #> <SQL>
      #> SELECT `speed`, `dist`, `low_dist`, as.numeric_psql(`low_dist`) AS `low_dist_old`, CAST(CAST(`low_dist` AS INTEGER) AS NUMERIC) AS `low_dist_new`
      #> FROM (SELECT `speed`, `dist`, `dist` < 5.0 AS `low_dist`
      #> FROM `tmp_cars`)
      

      reprex package 创建于 2021-12-09 (v2.0.1)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-11-12
        • 2021-08-04
        • 1970-01-01
        • 2022-12-03
        • 2014-05-01
        • 1970-01-01
        相关资源
        最近更新 更多