关于r:sqldf:按日期范围查询数据

sqldf: query data by range of dates

我正在读取具有'%d/%m/%Y'日期格式的巨大文本文件。 我想使用sqldf的read.csv.sql来按日期同时读取和过滤数据。 这是通过跳过许多我不感兴趣的日期来节省内存使用和运行时间的。我知道如何在dplyrlubridate的帮助下执行此操作,但是我只想尝试使用sqldf 上述原因。 即使我对SQL语法非常熟悉,它在大多数情况下仍然会让我受益,sqldf也不例外。

像下面这样的运行命令返回了一个带有0行的data.frame:

1
2
3
4
5
6
7
8
first_date <-"2001-11-1"
second_date <-"2003-11-1"
query <-"select * from file WHERE strftime('%d/%m/%Y', Date, 'unixepoch', 'localtime') between
                    '$first_date' AND '$second_date'"
df <- read.csv.sql(data_file,
                       sql= query,
                       stringsAsFactors=FALSE,
                       sep =";", header = TRUE)

因此,为了进行仿真,我尝试使用sqldf函数,如下所示:

1
2
3
4
5
6
7
8
9
10
first_date <-"2001-11-1"
second_date <-"2003-11-1"
df2 <- data.frame( Date = paste(rep(1:3, each = 4), 11:12, 2001:2012, sep ="/"))
sqldf("SELECT * FROM df2 WHERE strftime('%d/%m/%Y', Date, 'unixepoch') BETWEEN '$first-date' AND '$second_date'")

# Expect:
# Date
# 1  1-11-2001
# 2  1-12-2002
# 3  1-11-2003


具有百分比代码的strftime strftime用于将已被sqlite视为日期时间的对象转换为其他对象,但是您需要相反的操作,因此问题中的方法行不通。例如,这里我们将当前时间转换为dd-mm-yyyy字符串:

1
2
3
4
library(sqldf)
sqldf("select strftime('%d-%m-%Y', 'now') now")
##          now
## 1 07-09-2014

讨论由于SQlite缺少日期类型,因此处理它会比较麻烦,尤其是使用1或2位非标准日期格式时,但是如果您真的想使用SQLite,我们可以通过繁琐地解析日期字符串来做到这一点。使用gsubfn包中的fn$进行字符串插值会稍微减轻一点。

zero2d以下的代码输出SQL代码,以在其输入前添加零字符(如果该数字为一位)。 rmSlash输出SQL代码以删除其参数中的任何斜杠。 YearMonthDay各自输出SQL代码以采用表示所讨论格式的日期的字符串,并提取指示的组件,在Month的情况下将其重新格式化为2位零填充字符串。和DayfmtDate接受问题中所示的形式的first_stringsecond_string字符串,并输出yyyy-mm-dd字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
library(sqldf)
library(gsubfn)

zero2d <- function(x) sprintf("substr('0' || %s, -2)", x)

rmSlash <- function(x) sprintf("replace(%s, '/', '')", x)

Year <- function(x) sprintf("substr(%s, -4)", x)

Month <- function(x) {
   y <- sprintf("substr(%s, instr(%s, '/') + 1, 2)", x, x)
   zero2d(rmSlash(y))
}

Day <- function(x) {
   y <- sprintf("substr(%s, 1, 2)", x)
   zero2d(rmSlash(y))
}

fmtDate <- function(x) format(as.Date(x))

sql <-"select * from df2 where
  `Year('Date')` || '-' ||
  `Month('Date')` || '-' ||
  `Day('Date')`
  between '`fmtDate(first_date)`' and '`fmtDate(second_date)`'"
fn$sqldf(sql)

给予:

1
2
3
4
       Date
1 1/11/2001
2 1/12/2002
3 1/11/2003

笔记

1)使用instrreplacesubstr的SQLite函数是核心sqlite函数

2)SQL fn$执行替换之后执行的实际SQL语句如下(略微重新格式化以适合):

1
2
3
4
5
6
7
8
9
> cat( fn$identity(sql),"
")
select * from df2 where
  substr(Date, -4)
  || '-' ||
  substr('0' || replace(substr(Date, instr(Date, '/') + 1, 2), '/', ''), -2)
  || '-' ||
  substr('0' || replace(substr(Date, 1, 2), '/', ''), -2)
  between '2001-11-01' and '2003-11-01'

3)并发症的来源主要并发症是非标准的1位或2位数字的日和月。如果它们始终为两位数,则应减少为:

1
2
3
4
5
6
7
8
first_date <-"2001-11-01"
second_date <-""2003-11-01"

fn$sqldf("select Date from df2
   where substr(Date, -4) || '-' ||
         substr(Date, 4, 2) || '-' ||
         substr(Date, 1, 2)
   between '`first_date`' and '`second_date`'")

4)H2这是H2解决方案。 H2确实具有日期时间类型,从而大大简化了SQLite上的解决方案。我们假定数据在名为mydata.dat的文件中。请注意,read.csv.sql不支持H2,因为H2已经具有内部的csvread SQL函数来执行此操作:

1
2
3
4
5
6
7
8
9
10
library(RH2)
library(sqldf)

first_date <-"2001-11-01"
second_date <-"2003-11-01"

fn$sqldf(c("CREATE TABLE t(DATE TIMESTAMP) AS
  SELECT parsedatetime(DATE, 'd/M/y') as DATE
  FROM CSVREAD('mydata.dat')",
 "SELECT DATE FROM t WHERE DATE between '`first_date`' and '`second_date`'"))

请注意,第一个RH2查询在会话中会很慢,因为它会加载Java。之后,您可以尝试一下性能是否足够。