Как передать SQL в dplyr R?

Я могу использовать следующий код в R для выбора отдельных строк в любой общей базе данных SQL. Я бы использовал dplyr::distinct(), но он не поддерживается в синтаксисе SQL. В любом случае, это действительно работает:

dbGetQuery(database_name, 
           "SELECT t.* 
           FROM (SELECT t.*, ROW_NUMBER() OVER (PARTITION BY column_name ORDER BY column_name) AS SEQNUM 
           FROM table_name t
           ) t 
           WHERE SEQNUM = 1;")

Я успешно использую его, но задаюсь вопросом, как я могу передать этот же SQL-запрос после других шагов dplyr, а не просто использовать его в качестве первого шага, как показано выше. Лучше всего это проиллюстрировать на примере:

distinct.df <- 
  left_join(sql_table_1, sql_table_2, by = "col5") %>% 
  sql("SELECT t.* 
      FROM (SELECT t.*, ROW_NUMBER() OVER (PARTITION BY column_name ORDER BY column_name) AS SEQNUM 
      FROM table_name t
      ) t 
      WHERE SEQNUM = 1;")

Итак, я dplyr::left_join() две таблицы SQL, затем я хочу посмотреть на отдельные строки и сохранить все столбцы. Могу ли я передать код SQL в R, как показано выше (просто используя функцию sql())? И если да, то что бы я использовал для table_name в строке FROM table_name t?

В моем первом примере я использую фактическое имя таблицы, из которой я беру. Это слишком очевидно! Но в этом случае я использую конвейер и привык использовать местоимение magrittr . или иногда .data местоимение из rlang, если я работал в R без баз данных в памяти.

Хотя я использую базу данных SQL ... так как мне справиться с этой ситуацией? Как мне правильно передать мой известный рабочий SQL в мой код R (с правильным местоимением имени таблицы)? справочная страница dbplyr является хорошей отправной точкой, но на самом деле не дает ответа на этот конкретный вопрос.


person Display name    schedule 30.12.2019    source источник
comment
@akrun. Я попробую tidyquery. Любая идея, как я могу использовать местоимение, например . или .data, чтобы сказать SQL, чтобы передать таблицу над %>% в запрос? Вместо манекена table_name я указал в своем коде. github.com/ianmcook/tidyquery   -  person Display name    schedule 31.12.2019
comment
Возможно, это ограничения, упомянутые в cran.r -project.org/web/packages/queryparser/readme/ также будет применяться здесь   -  person akrun    schedule 31.12.2019
comment
Можно ли разделить синтаксис dplyr и SQL? Итак, df1 <- left_join(sql_table_1, sql_table_2, by = "col5") и df2 <- sql("SELECT t.* FROM (SELECT t.*, ROW_NUMBER() ......?   -  person Ronak Shah    schedule 31.12.2019
comment
В большинстве диалектов SQL поддерживается отдельный. Попробуйте следующее: library(sqldf); BOD2 <- rbind(BOD, BOD); sqldf("select distinct * from BOD2") Также, если вы используете sqldf, вы можете сделать это: BOD2 %>% { sqldf("select distinct * from [.]") } %>% mutate(demand = demand + 1) иллюстрируя использование sql как для ввода, так и для вывода.   -  person G. Grothendieck    schedule 31.12.2019
comment
Я считаю, что ваш SQL-запрос можно написать в синтаксисе dplyr. Не могли бы вы задать новый вопрос? Я отправлю ответ относительно доступа к результату операции dbplyr в следующем SQL-запросе, который, кажется, интересен сам по себе.   -  person krlmlr    schedule 02.01.2020


Ответы (2)


Похоже, вы хотите объединить собственный код SQL с автоматически сгенерированным кодом SQL из dbplyr. Для этого важно различать:

  • DBI::db* команды - которые выполняют указанный SQL в базе данных и возвращают результат.
  • dbplyr перевод - когда вы работаете с удаленным подключением к столу

Вы можете комбинировать их только определенным образом. Ниже я привел несколько примеров в зависимости от вашего конкретного варианта использования. Все предполагают, что DISTINCT - это команда, которая принимается в вашей конкретной среде SQL.

Справочные примеры, охватывающие множество различных вариантов использования

Если вы извините за саморекламу, я рекомендую вам взглянуть на мой dbplyr_helpers репозиторий GitHub (здесь ). Это включает:

  • union_all, которая принимает две таблицы, доступ к которым осуществляется через dbplyr, и выводит одну таблицу с использованием некоторого настраиваемого кода SQL.
  • write_to_datebase функция, которая принимает таблицу, доступ к которой осуществляется через dbplyr, и преобразует ее в код, который может быть выполнен через DBI::dbExecute

Автоматическая обвязка

dbplyr автоматически перенаправляет ваш код в следующий запрос, когда вы работаете со стандартными dplyr глаголами, для которых определены переводы SQL. Пока определены переводы sql, вы можете связать вместе много каналов (я использовал 10 или более одновременно) с (почти) единственным недостатком, заключающимся в том, что переведенный запрос sql становится трудным для чтения человеком.

Например, рассмотрим следующее:

library(dbplyr)
library(dplyr)

tmp_df = data.frame(col1 = c(1,2,3), col2 = c("a","b","c"))

df1 = tbl_lazy(tmp_df, con = simulate_postgres())
df2 = tbl_lazy(tmp_df, con = simulate_postgres())

df = left_join(df1, df2, by = "col1") %>%
  distinct()

Когда вы затем вызываете show_query(df), R возвращает следующий автоматически сгенерированный код SQL:

SELECT DISTINCT *
FROM (

SELECT `LHS`.`col1` AS `col1`, `LHS`.`col2` AS `col2.x`, `RHS`.`col2` AS `col2.y`
FROM `df` AS `LHS`
LEFT JOIN `df` AS `RHS`
ON (`LHS`.`col1` = `RHS`.`col1`)

) `dbplyr_002`

Но не так красиво отформатирован. Обратите внимание, что начальная команда (левое соединение) отображается как вложенный запрос с отдельным запросом во внешнем запросе. Следовательно, df - это ссылка R на таблицу удаленной базы данных, определенную вышеуказанным запросом sql.

Создание пользовательских функций SQL

Вы можете перенаправить dbplyr в пользовательские функции SQL. Трубопровод означает, что передаваемый по трубопроводу объект становится первым аргументом принимающей функции.

custom_distinct <- function(df){
  db_connection <- df$src$con

  sql_query <- build_sql(con = db_connection,
                         "SELECT DISTINCT * FROM (\n",
                         sql_render(df),
                         ") AS nested_tbl"
  )
  return(tbl(db_connection, sql(sql_query)))
}

df = left_join(df1, df2, by = "col1") %>%
  custom_distinct()

Когда вы затем вызываете show_query(df), R должен вернуть следующий код SQL (я говорю «должен», потому что я не могу заставить его работать с смоделированными соединениями sql), но не в таком красивом формате:

SELECT DISTINCT * FROM (

SELECT `LHS`.`col1` AS `col1`, `LHS`.`col2` AS `col2.x`, `RHS`.`col2` AS `col2.y`
FROM `df` AS `LHS`
LEFT JOIN `df` AS `RHS`
ON (`LHS`.`col1` = `RHS`.`col1`)

) nested_tbl

Как и в предыдущем примере, df - это ссылка R на таблицу удаленной базы данных, определенную вышеупомянутым запросом sql.

Преобразование dbplyr в DBI

Вы можете взять код из существующей dbplyr удаленной таблицы и преобразовать его в строку, которая может быть выполнена с помощью DBI::db*.

Как еще один способ написания отдельного запроса:

df1 = tbl_lazy(tmp_df, con = simulate_postgres())
df2 = tbl_lazy(tmp_df, con = simulate_postgres())

df = left_join(df1, df2, by = "col1")

custom_distinct2 = paste0("SELECT DISTINCT * FROM (",
                          as.character(sql_render(df)),
                          ") AS nested_table")

local_table =   dbGetQuery(db_connection, custom_distinct2)

Что вернет локальный фрейм данных R с эквивалентной командой sql, как в предыдущих примерах.

person Simon.S.A.    schedule 31.12.2019

Если вы хотите выполнить пользовательскую обработку SQL для результата операции dbplyr, может быть полезно сначала compute(), который создает новую таблицу (временную или постоянную) с набором результатов в базе данных. В представлении ниже показано, как получить доступ к имени вновь созданной таблицы, если вы полагаетесь на автогенерацию. (Обратите внимание, что это зависит от внутреннего устройства dbplyr и может быть изменено без предварительного уведомления - возможно, лучше назвать таблицу явно.) Затем используйте dbGetQuery() как обычно.

library(tidyverse)
library(dbplyr)
#> 
#> Attaching package: 'dbplyr'
#> The following objects are masked from 'package:dplyr':
#> 
#>     ident, sql

lazy_query <-
  memdb_frame(a = 1:3) %>%
  mutate(b = a + 1) %>%
  summarize(c = sum(a * b, na.rm = TRUE))

lazy_query
#> # Source:   lazy query [?? x 1]
#> # Database: sqlite 3.30.1 [:memory:]
#>       c
#>   <dbl>
#> 1    20

lazy_query_computed <-
  lazy_query %>%
  compute()

lazy_query_computed
#> # Source:   table<dbplyr_002> [?? x 1]
#> # Database: sqlite 3.30.1 [:memory:]
#>       c
#>   <dbl>
#> 1    20
lazy_query_computed$ops$x
#> <IDENT> dbplyr_002

Создано 01.01.2020 с помощью пакета REPEX (v0.3.0)

Если ваш диалект SQL поддерживает CTE s, вы также можете извлечь строку запроса и использовать ее как часть пользовательского SQL, возможно, аналогично предложению Саймона.

library(tidyverse)
library(dbplyr)
#> 
#> Attaching package: 'dbplyr'
#> The following objects are masked from 'package:dplyr':
#> 
#>     ident, sql

lazy_query <-
  memdb_frame(a = 1:3) %>%
  mutate(b = a + 1) %>%
  summarize(c = sum(a * b, na.rm = TRUE))

sql <-
  lazy_query %>%
  sql_render()

cte_sql <-
  paste0(
    "WITH my_result AS (", sql, ") ",
    "SELECT c + 1 AS d FROM my_result"
  )

cte_sql
#> [1] "WITH my_result AS (SELECT SUM(`a` * `b`) AS `c`\nFROM (SELECT `a`, `a` + 1.0 AS `b`\nFROM `dbplyr_001`)) SELECT c + 1 AS d FROM my_result"

DBI::dbGetQuery(
  lazy_query$src$con,
  cte_sql
)
#>    d
#> 1 21

Создано 01.01.2020 с помощью пакета REPEX (v0.3.0)

person krlmlr    schedule 01.01.2020