3

我有一个带有 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 正则表达式运算符或诸如此类)。

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

4

4 回答 4

2

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

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

LIKE您可以在客户端处理您的语言环境字符串并为, RLIKEor运算符形成正确的条件REGEXP_SUBSRT。客户端当然应该知道系统使用的数据库。

然后,您应该将运算符应用于根据具有特定于数据库的格式化函数的语言环境形成的字符串,如下所示(in 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:11in的所有记录2010,等等。

于 2010-02-01T10:49:24.033 回答
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' AND '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' AND '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;
于 2010-02-01T23:01:16.883 回答
1

无论用户输入什么,您都应该提取三个值:和Year,使用他的语言环境作为指导。某些值可能为空。MonthDay

  • 如果用户输入仅Year使用WHERE Date BETWEEN 'YYYY-01-01' AND 'YYYY-12-31'
  • 如果用户进入使用Year(可能需要调整为30/29/28)MonthWHERE Date BETWEEN 'YYYY-MM-01' AND 'YYYY-MM-31'
  • 如果用户输入三个值,请使用SELECT .... WHERE Date = 'YYYY-MM-DD'
  • 如果用户输入Monthand Day,您将不得不使用“慢”方式
于 2010-02-01T10:17:22.393 回答
0

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

几点注意事项:

  • 如果您只需要简单查询确切的日期或范围,我建议您使用ISO格式DATE (YYYY-MM-DD, ex: 2010-02-01)或 DATETIME。但是,由于您似乎需要像“10 月 15 日的所有年份”这样的查询,所以无论如何您都需要自定义查询。
  • 我建议您创建一个“解析器”来获取您的日期查询并为您提供SQL WHERE子句的一部分。我敢肯定,您最终将拥有少于十几个案例,因此您可以WHEREs为每个案例提供最佳选择。这样您将避免加载所有记录。
    • 您绝对不想在 SQL 中执行任何特定于语言环境的操作。因此,将本地转换为非 SQL 代码中的某个标准,然后使用它来执行您的查询(基本上将本地化/全球化和查询执行分开)
    • 然后你可以优化。如果你发现你有很多查询year,你可能会创建一个COMPUTED COLUMN只包含YEAR并且有索引的查询。
于 2010-02-01T10:20:20.253 回答