是否有可能以数据库无关的方式搜索日期作为字符串?

时间:2010-02-01 10:03:20

标签: ruby-on-rails postgresql search database-agnostic

我有一个带有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>

有没有办法以数据库无关的方式有效地执行此操作(无需获取所有行)?或者你认为我的方向是错误的,应该以不同的方式解决问题吗?

4 个答案:

答案 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,您应该使用他的区域设置作为指南,提取三个值:YearMonthDay。某些值可能为空。

  • 如果用户仅输入Year使用WHERE Date BETWEEN 'YYYY-01-01' AND 'YYYY-12-31'
  • 如果用户输入YearMonth使用WHERE Date BETWEEN 'YYYY-MM-01' AND 'YYYY-MM-31'(可能需要调整30/29/28)
  • 如果用户输入三个值,请使用SELECT .... WHERE Date = 'YYYY-MM-DD'
  • 如果用户输入MonthDay,则必须使用'慢'方式

答案 2 :(得分:1)

“数据库无关的方式”通常是“慢速方式”的同义词,因此解决方案不太可能有效。

在任何情况下,解析客户端上的所有记录都是效率最低的解决方案。

您可以在客户端处理您的语言环境字符串,并为LIKERLIKEREGEXP_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

这将选择记录,在任何日期部分中包含所有三个数字(122010):例如,Novemer 19 2010的所有记录加上所有记录19:11中的2010等等。

答案 3 :(得分:0)

恕我直言,简短回答 。但绝对避免加载所有行

很少注意到:

  • 如果您只对简单日期或范围进行简单查询,我建议您使用DATE (YYYY-MM-DD, ex: 2010-02-01)作为SQL WHERE或DATETIME。但是,由于您似乎需要查询“10月15日的所有年份”,因此无论如何都需要自定义查询。
  • 我建议你创建一个“解析器”来获取日期查询,并为你提供WHEREs子句的一部分。我确信你最终会有不到十几个案例,所以你可以为每个案例提供最佳year。这样您就可以避免加载所有记录。
    • 您绝对不希望在SQL中执行任何特定于区域设置的操作。因此,在非SQL代码中将本地转换为某些标准,然后使用它来执行查询(基本上是单独的本地化/全球化和查询执行)
    • 然后你可以优化。如果您发现只有COMPUTED COLUMN有大量查询,则可以创建YEAR,其中只包含{{1}}并且上面有索引。