Можно ли искать даты в виде строк независимо от базы данных?

У меня есть приложение Ruby on Rails с базой данных PostgreSQL; несколько таблиц имеют атрибуты временной метки created_at и updated_at. При отображении эти даты форматируются в языковом стандарте пользователя; например, отметка времени 2009-10-15 16:30:00.435 становится строкой 15.10.2009 - 16:30 (формат даты для этого примера — dd.mm.yyyy - hh.mm).

Требование состоит в том, что пользователь должен иметь возможность искать записи по дате, как если бы они были строками, отформатированными в текущей локали. Например, поиск 15.10.2009 вернет записи с датами 15 октября 2009 года, поиск 15.10 вернет записи с датами 15 октября любого года, поиск 15 вернет все даты, соответствующие 15 (будь то день, месяц или год). . Поскольку пользователь может использовать любую часть даты в качестве условия поиска, ее нельзя преобразовать в отметку даты/времени для сравнения.

Один (медленный) способ - получить все записи, отформатировать даты и выполнить поиск по ним. Это можно ускорить, если сначала получить только идентификатор и даты, выполнить поиск, а затем получить данные для соответствующих записей; но это все еще может быть медленным для большого количества строк.

Другим (не независимым от базы данных) способом было бы привести/отформатировать даты в правильном формате в базе данных с помощью функций или операторов PostgreSQL и заставить базу данных выполнить сопоставление (с операторами регулярных выражений PostgreSQL или еще чем-то).

Есть ли способ сделать это эффективно (без извлечения всех строк) независимо от базы данных? Или вы думаете, что я иду в неправильном направлении и должен подойти к проблеме по-другому?


person Alpha Hydrae    schedule 01.02.2010    source источник
comment
Должен ли поиск года 98 возвращать как 1988, так и 1998?   -  person Quassnoi    schedule 01.02.2010
comment
Означает ли отсутствие выборки всех строк не получение их в код приложения или полное сканирование таблицы?   -  person cope360    schedule 01.02.2010
comment
Quassnoi: в идеале да, но это не обязательное требование. copy360: Я имел в виду, не загружая их в код моего приложения.   -  person Alpha Hydrae    schedule 02.02.2010


Ответы (4)


Основываясь на ответе Карлоса, это должно разрешить все ваши поиски без полного сканирования таблицы, если у вас есть индексы для всех полей даты и даты. Индексы на основе функций были бы лучше для столбцов части даты, но я не использую их, так как это не должно быть специфичным для базы данных.

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);

Если пользователь вводит только год, используйте ГДЕ Дата МЕЖДУ «ГГГГ-01-01» И «ГГГГ-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';

Если пользователь вводит год и месяц, используйте ГДЕ дата МЕЖДУ «ГГГГ-ММ-01» И «ГГГГ-ММ-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;
person cope360    schedule 01.02.2010
comment
Я думаю, что я пойду с этим решением сейчас. Вычисляемые столбцы (поддерживаемые приложением) должны соответствовать требованиям без использования каких-либо функций, специфичных для базы данных. Спасибо за примеры. - person Alpha Hydrae; 02.02.2010

«Путь, не зависящий от базы данных», обычно является синонимом «медленного пути», поэтому решения вряд ли будут эффективными.

В любом случае разбор всех записей на стороне клиента будет наименее эффективным решением.

Вы можете обработать строку локали на стороне клиента и сформировать правильное условие для оператора 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 и т. д.

person Quassnoi    schedule 01.02.2010
comment
Это кажется очень эффективным способом. Я мог бы не использовать его для этого проекта, потому что у меня, вероятно, нет времени, чтобы изучить все особенности базы данных. Но я рассмотрю индексы GIN и тому подобное. Спасибо. - person Alpha Hydrae; 02.02.2010

Независимо от того, что вводит пользователь, вы должны извлечь три значения: 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, вам придется использовать "медленный" способ
person Carlos Gutiérrez    schedule 01.02.2010

ИМХО, короткий ответНет. Но обязательно избегайте загрузки всех строк.

Несколько заметок:

  • если у вас есть только простые запросы для точных дат или диапазонов, я бы рекомендовал использовать формат ISO для DATE (YYYY-MM-DD, ex: 2010-02-01) или ДАТАВРЕМЯ. Но поскольку вам, кажется, нужны такие запросы, как «все годы на 15 октября», вам все равно нужны пользовательские запросы.
  • I suggest you create a "parser" that takes your date query and gives you the part of the SQL WHERE clause. I am certain that you will end up having less then a dozen of cases, so you can have optimal WHEREs for each of them. This way you will avoid loading all records.
    • you definitely do not want to do anything locale specific in the SQL. Therefore convert local to some standard in the non-SQL code, then use it to perform your query (basically separate localization/globalization and the query execution)
    • Потом можно оптимизировать. Если вы видите, что у вас есть много запросов только для year, вы можете создать COMPUTED COLUMN, который будет содержать только YEAR и иметь для него индекс.
person van    schedule 01.02.2010
comment
Это звучит как хороший компромисс, если я не хочу добавлять (много) вычисляемых столбцов в базу данных. Я посмотрю на это. Спасибо. - person Alpha Hydrae; 02.02.2010