我有一个带有PostgreSQL数据库的Ruby on Rails应用程序;几个表有created_at和updated_at时间戳属性。显示时,这些日期将以用户的语言环境格式化;例如,时间戳2009-10-15 16:30:00.435
变为字符串15.10.2009 - 16:30
(此示例的日期格式为dd.mm.yyyy - hh.mm
)。
要求是用户必须能够按日期搜索记录,就好像它们是在当前区域设置中格式化的字符串一样。例如,搜索15.10.2009
将在2009年10月15日返回包含日期的记录,搜索15.10
将返回任何年份10月15日的日期记录,搜索15
将返回所有日期匹配15(无论是日,月或年)。由于用户可以使用日期的任何部分作为搜索词,因此无法将其转换为日期/时间戳进行比较。
一种(慢)方式是检索所有记录,格式化日期并对其执行搜索。这可以通过首先仅检索id和日期,执行搜索,然后获取匹配记录的数据来加速;但对于大量的行来说,它仍然可能很慢。
另一种(不是数据库不可知)方式是使用PostgreSQL函数或运算符在数据库中将日期转换/格式化为正确的格式,并让数据库进行匹配(使用PostgreSQL正则表达式运算符或诸如此类的东西)。 / p>
有没有办法以数据库无关的方式有效地执行此操作(无需获取所有行)?或者你认为我的方向是错误的,应该以不同的方式解决问题吗?
答案 0 :(得分:2)
根据Carlos的回答,如果所有日期和日期部分字段都有索引,则应该允许所有搜索没有全表扫描。基于函数的索引对于日期部分列更好,但我没有使用它们,因为这不应该是特定于数据库的。
CREATE TABLE mytable (
col1 varchar(10),
-- ...
inserted_at timestamp,
updated_at timestamp);
INSERT INTO mytable
VALUES
('a', '2010-01-02', NULL),
('b', '2009-01-02', '2010-01-03'),
('c', '2009-11-12', NULL),
('d', '2008-03-31', '2009-04-18');
ALTER TABLE mytable
ADD inserted_at_month integer,
ADD inserted_at_day integer,
ADD updated_at_month integer,
ADD updated_at_day integer;
-- you will have to find your own way to maintain these values...
UPDATE mytable
SET
inserted_at_month = date_part('month', inserted_at),
inserted_at_day = date_part('day', inserted_at),
updated_at_month = date_part('month', updated_at),
updated_at_day = date_part('day', updated_at);
如果用户仅输入年份,请使用WHERE Date BETWEEN'YYYY-01-01'和'YYYY-12-31'
SELECT *
FROM mytable
WHERE
inserted_at BETWEEN '2010-01-01' AND '2010-12-31'
OR updated_at BETWEEN '2010-01-01' AND '2010-12-31';
如果用户输入年份和月份,请使用WHERE Date BETWEEN'YYYY-MM-01'和'YYYY-MM-31'(可能需要调整30/29/28)
SELECT *
FROM mytable
WHERE
inserted_at BETWEEN '2010-01-01' AND '2010-01-31'
OR updated_at BETWEEN '2010-01-01' AND '2010-01-31';
如果用户输入三个值,请使用SELECT .... WHERE Date ='YYYY-MM-DD'
SELECT *
FROM mytable
WHERE
inserted_at = '2009-11-12'
OR updated_at = '2009-11-12';
如果用户输入月和日
SELECT *
FROM mytable
WHERE
inserted_at_month = 3
OR inserted_at_day = 31
OR updated_at_month = 3
OR updated_at_day = 31;
如果用户输入月份或日期(您可以优化以不检查值> 12个月)
SELECT *
FROM mytable
WHERE
inserted_at_month = 12
OR inserted_at_day = 12
OR updated_at_month = 12
OR updated_at_day = 12;
答案 1 :(得分:1)
用户输入的Watever,您应该使用他的区域设置作为指南,提取三个值:Year
,Month
和Day
。某些值可能为空。
Year
使用WHERE Date BETWEEN 'YYYY-01-01' AND 'YYYY-12-31'
Year
而Month
使用WHERE Date BETWEEN 'YYYY-MM-01' AND 'YYYY-MM-31'
(可能需要调整30/29/28)SELECT .... WHERE Date = 'YYYY-MM-DD'
Month
和Day
,则必须使用'慢'方式答案 2 :(得分:1)
“数据库无关的方式”通常是“慢速方式”的同义词,因此解决方案不太可能有效。
在任何情况下,解析客户端上的所有记录都是效率最低的解决方案。
您可以在客户端处理您的语言环境字符串,并为LIKE
,RLIKE
或REGEXP_SUBSRT
运算符形成正确的条件。客户端当然应该知道系统使用的数据库。
然后你应该将操作符应用于根据语言环境形成的字符串以及特定于数据库的格式化函数,如下所示(在Oracle
中):
SELECT *
FROM mytable
WHERE TO_CHAR(mydate, 'dd.mm.yyyy - hh24.mi') LIKE '15\.10'
更有效的方式(仅适用于PostgreSQL
)将在各个日期部分创建GIN
索引:
CREATE INDEX ix_dates_parts
ON dates
USING GIN
(
(ARRAY
[
DATE_PART('year', date)::INTEGER,
DATE_PART('month', date)::INTEGER,
DATE_PART('day', date)::INTEGER,
DATE_PART('hour', date)::INTEGER,
DATE_PART('minute', date)::INTEGER,
DATE_PART('second', date)::INTEGER
]
)
)
并在查询中使用它:
SELECT *
FROM dates
WHERE ARRAY[11, 19, 2010] <@ (ARRAY
[
DATE_PART('year', date)::INTEGER,
DATE_PART('month', date)::INTEGER,
DATE_PART('day', date)::INTEGER,
DATE_PART('hour', date)::INTEGER,
DATE_PART('minute', date)::INTEGER,
DATE_PART('second', date)::INTEGER
]
)
LIMIT 10
这将选择记录,在任何日期部分中包含所有三个数字(1
,2
和2010
):例如,Novemer 19 2010
的所有记录加上所有记录19:11
中的2010
等等。
答案 3 :(得分:0)
恕我直言,简短回答 否。但绝对避免加载所有行。
很少注意到:
DATE (YYYY-MM-DD, ex: 2010-02-01)
作为SQL WHERE
或DATETIME。但是,由于您似乎需要查询“10月15日的所有年份”,因此无论如何都需要自定义查询。WHEREs
子句的一部分。我确信你最终会有不到十几个案例,所以你可以为每个案例提供最佳year
。这样您就可以避免加载所有记录。
COMPUTED COLUMN
有大量查询,则可以创建YEAR
,其中只包含{{1}}并且上面有索引。