使用纯 R 使用 dbplyr 处理日期
Handle dates with dbplyr using pure R
dbplyr 将 dplyr 和基本 R 命令转换为 SQL,以便开发人员可以编写 R 代码并在数据库中执行 (tidyverse reference)。在 R 中处理日期时,通常使用 lubridate 包。但是,目前不存在 lubridate 函数的 dbplyr 翻译。因此,使用 dbplyr 的开发人员需要找到处理日期的替代方法。
我以前的方法是在我的 dplyr 命令中使用 SQL 语法片段(参见示例答案: and here)。但是,这需要开发人员知道(或找出)相应的 SQL 命令,而 dbplyr 的部分要点是它会为您翻译成 SQL。
这让我问:仅当连接到远程数据库时使用 dbplyr 转换来操作日期的最佳方法是什么?
理想的解决方案将:
- 仅使用 dbplyr 翻译,因此无法使用没有 dbplyr 翻译的函数。
- 使用纯 R,没有 SQL 个片段。
- 运行 在数据库上,所以远程 table 而不是本地 table.
我认为至少我们应该能够:
- 提取年月日
- 将年月日组合成一个新日期
从这些您可以手动执行其他操作,例如:
- 递增日期
- 找出两个日期之间的差异
- 查找月末日期
但是 faster/elegant 执行这些更高级操作的方法会更可取。
想到的第一个方法是将日期转换为文本,因为已经有针对不同形式的文本操作的 dbplyr 翻译。此方法依赖于 as.character
将日期转换为字符,并依赖于 substr
将年、月或日提取为文本。然后可以将其转换为数字并进一步操作。
(1) 设置模拟数据库连接来测试翻译(选择你喜欢的 SQL 风格):
library(dplyr)
library(dbplyr)
df = data.frame(start_dates = c('2020-01-31', '2020-02-28', '2020-03-31'))
# simulate a connection to test translation (pick your preferred flavor)
df = tbl_lazy(df, con = simulate_mssql())
# df = tbl_lazy(df, con = simulate_hive())
# df = tbl_lazy(df, con = simulate_impala())
# df = tbl_lazy(df, con = simulate_oracle())
# df = tbl_lazy(df, con = simulate_postgres())
# df = tbl_lazy(df, con = simulate_mysql())
# df = tbl_lazy(df, con = simulate_sqlite())
(2) 示例 - 提取日期组件、增加年份并重新组合:
output = df %>%
mutate(text_date = as.character(start_dates)) %>%
mutate(text_year = substr(text_date, 1, 4),
text_month = substr(text_date, 6, 7),
text_day = substr(text_date, 9, 10)) %>%
mutate(num_year = as.numeric(text_year),
num_month = as.numeric(text_month),
num_day = as.numeric(text_day)) %>%
select(start_dates, num_year, num_month, num_day) %>%
mutate(next_year = num_year + 1) %>%
mutate(next_year_text_date = paste0(next_year, '-', num_month, '-', num_day)) %>%
mutate(next_year_date = as.Date(next_year_text_date)) %>%
select(start_dates, next_year_date)
调用 show_query(output)
然后给出以下翻译,但格式不那么好。我知道嵌套查询不被认为是好的 SQL 做法,但这就是 dbplyr 翻译的工作方式。
SELECT `start_dates`
, TRY_CAST(`next_year_text_date` AS DATE) AS `next_year_date`
FROM (
SELECT `start_dates`
, `num_year`
, `num_month`
, `num_day`
, `next_year`
, `next_year` + '-' + `num_month` + '-' + `num_day` AS `next_year_text_date`
FROM (
SELECT `start_dates`
, `num_year`
, `num_month`
, `num_day`
, `num_year` + 1.0 AS `next_year`
FROM (
SELECT `start_dates`
, TRY_CAST(`text_year` AS FLOAT) AS `num_year`
, TRY_CAST(`text_month` AS FLOAT) AS `num_month`
, TRY_CAST(`text_day` AS FLOAT) AS `num_day`
FROM (
SELECT `start_dates`
, `text_date`
, SUBSTRING(`text_date`, 1, 4) AS `text_year`
, SUBSTRING(`text_date`, 6, 2) AS `text_month`
, SUBSTRING(`text_date`, 9, 2) AS `text_day`
FROM (
SELECT `start_dates`
, TRY_CAST(`start_dates` AS VARCHAR(MAX)) AS `text_date`
FROM `df`
) `q01`
) `q02`
) `q03`
) `q04`
) `q05`
(3) 提取组件,紧凑:
output = df %>%
mutate(num_year = as.numeric(substr(as.character(start_dates), 1, 4)),
num_month = as.numeric(substr(as.character(start_dates), 6, 7)),
num_day = as.numeric(substr(as.character(start_dates), 9, 10)))
来自 show_query(output)
的 SQL 翻译更短:
SELECT `start_dates`
, TRY_CAST(SUBSTRING(TRY_CAST(`start_dates` AS VARCHAR(MAX)), 1, 4) AS FLOAT) AS `num_year`
, TRY_CAST(SUBSTRING(TRY_CAST(`start_dates` AS VARCHAR(MAX)), 6, 2) AS FLOAT) AS `num_month`
, TRY_CAST(SUBSTRING(TRY_CAST(`start_dates` AS VARCHAR(MAX)), 9, 2) AS FLOAT) AS `num_day`
FROM `df`
希望这适用于 dbplyr 可以翻译成的 SQL 的所有风格。由于我无法访问所有 SQL 口味来对其进行测试,因此在特定 SQL 口味上测试过它的人的评论会有所帮助。
一个答案是其中大部分已经成为可能。 (参见答案 here。)
如果 dbplyr
中缺少所需的功能,一种想法是编写拉取请求,将 lubridate
功能的更多翻译添加到 dbplyr
中的后端。
看来翻译不可避免地是后端特定的。如果您查看 PostgreSQL 后端 here,您可以看到一些 lubridate
函数(例如 month
或 quarter
)在那里给出了翻译,但其他的(例如 ymd
) 不是。
dbplyr 将 dplyr 和基本 R 命令转换为 SQL,以便开发人员可以编写 R 代码并在数据库中执行 (tidyverse reference)。在 R 中处理日期时,通常使用 lubridate 包。但是,目前不存在 lubridate 函数的 dbplyr 翻译。因此,使用 dbplyr 的开发人员需要找到处理日期的替代方法。
我以前的方法是在我的 dplyr 命令中使用 SQL 语法片段(参见示例答案:
这让我问:仅当连接到远程数据库时使用 dbplyr 转换来操作日期的最佳方法是什么?
理想的解决方案将:
- 仅使用 dbplyr 翻译,因此无法使用没有 dbplyr 翻译的函数。
- 使用纯 R,没有 SQL 个片段。
- 运行 在数据库上,所以远程 table 而不是本地 table.
我认为至少我们应该能够:
- 提取年月日
- 将年月日组合成一个新日期
从这些您可以手动执行其他操作,例如:
- 递增日期
- 找出两个日期之间的差异
- 查找月末日期
但是 faster/elegant 执行这些更高级操作的方法会更可取。
想到的第一个方法是将日期转换为文本,因为已经有针对不同形式的文本操作的 dbplyr 翻译。此方法依赖于 as.character
将日期转换为字符,并依赖于 substr
将年、月或日提取为文本。然后可以将其转换为数字并进一步操作。
(1) 设置模拟数据库连接来测试翻译(选择你喜欢的 SQL 风格):
library(dplyr)
library(dbplyr)
df = data.frame(start_dates = c('2020-01-31', '2020-02-28', '2020-03-31'))
# simulate a connection to test translation (pick your preferred flavor)
df = tbl_lazy(df, con = simulate_mssql())
# df = tbl_lazy(df, con = simulate_hive())
# df = tbl_lazy(df, con = simulate_impala())
# df = tbl_lazy(df, con = simulate_oracle())
# df = tbl_lazy(df, con = simulate_postgres())
# df = tbl_lazy(df, con = simulate_mysql())
# df = tbl_lazy(df, con = simulate_sqlite())
(2) 示例 - 提取日期组件、增加年份并重新组合:
output = df %>%
mutate(text_date = as.character(start_dates)) %>%
mutate(text_year = substr(text_date, 1, 4),
text_month = substr(text_date, 6, 7),
text_day = substr(text_date, 9, 10)) %>%
mutate(num_year = as.numeric(text_year),
num_month = as.numeric(text_month),
num_day = as.numeric(text_day)) %>%
select(start_dates, num_year, num_month, num_day) %>%
mutate(next_year = num_year + 1) %>%
mutate(next_year_text_date = paste0(next_year, '-', num_month, '-', num_day)) %>%
mutate(next_year_date = as.Date(next_year_text_date)) %>%
select(start_dates, next_year_date)
调用 show_query(output)
然后给出以下翻译,但格式不那么好。我知道嵌套查询不被认为是好的 SQL 做法,但这就是 dbplyr 翻译的工作方式。
SELECT `start_dates`
, TRY_CAST(`next_year_text_date` AS DATE) AS `next_year_date`
FROM (
SELECT `start_dates`
, `num_year`
, `num_month`
, `num_day`
, `next_year`
, `next_year` + '-' + `num_month` + '-' + `num_day` AS `next_year_text_date`
FROM (
SELECT `start_dates`
, `num_year`
, `num_month`
, `num_day`
, `num_year` + 1.0 AS `next_year`
FROM (
SELECT `start_dates`
, TRY_CAST(`text_year` AS FLOAT) AS `num_year`
, TRY_CAST(`text_month` AS FLOAT) AS `num_month`
, TRY_CAST(`text_day` AS FLOAT) AS `num_day`
FROM (
SELECT `start_dates`
, `text_date`
, SUBSTRING(`text_date`, 1, 4) AS `text_year`
, SUBSTRING(`text_date`, 6, 2) AS `text_month`
, SUBSTRING(`text_date`, 9, 2) AS `text_day`
FROM (
SELECT `start_dates`
, TRY_CAST(`start_dates` AS VARCHAR(MAX)) AS `text_date`
FROM `df`
) `q01`
) `q02`
) `q03`
) `q04`
) `q05`
(3) 提取组件,紧凑:
output = df %>%
mutate(num_year = as.numeric(substr(as.character(start_dates), 1, 4)),
num_month = as.numeric(substr(as.character(start_dates), 6, 7)),
num_day = as.numeric(substr(as.character(start_dates), 9, 10)))
来自 show_query(output)
的 SQL 翻译更短:
SELECT `start_dates`
, TRY_CAST(SUBSTRING(TRY_CAST(`start_dates` AS VARCHAR(MAX)), 1, 4) AS FLOAT) AS `num_year`
, TRY_CAST(SUBSTRING(TRY_CAST(`start_dates` AS VARCHAR(MAX)), 6, 2) AS FLOAT) AS `num_month`
, TRY_CAST(SUBSTRING(TRY_CAST(`start_dates` AS VARCHAR(MAX)), 9, 2) AS FLOAT) AS `num_day`
FROM `df`
希望这适用于 dbplyr 可以翻译成的 SQL 的所有风格。由于我无法访问所有 SQL 口味来对其进行测试,因此在特定 SQL 口味上测试过它的人的评论会有所帮助。
一个答案是其中大部分已经成为可能。 (参见答案 here。)
如果 dbplyr
中缺少所需的功能,一种想法是编写拉取请求,将 lubridate
功能的更多翻译添加到 dbplyr
中的后端。
看来翻译不可避免地是后端特定的。如果您查看 PostgreSQL 后端 here,您可以看到一些 lubridate
函数(例如 month
或 quarter
)在那里给出了翻译,但其他的(例如 ymd
) 不是。