Можно ли искать даты в виде строк независимо от базы данных? - PullRequest
2 голосов
/ 01 февраля 2010

У меня есть приложение Ruby on Rails с базой данных PostgreSQL; несколько таблиц имеют атрибуты меток времени create_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 или чего-либо еще). 1013 *

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

Ответы [ 4 ]

2 голосов
/ 02 февраля 2010

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

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;
1 голос
/ 01 февраля 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 и т. д.

1 голос
/ 01 февраля 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, вам придется использовать «медленный» способ
0 голосов
/ 01 февраля 2010

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

Несколько нот:

  • если бы у вас были только простые запросы на точные даты или диапазоны, я бы рекомендовал использовать формат ISO для DATE (YYYY-MM-DD, ex: 2010-02-01) или DATETIME. Но поскольку вам, похоже, нужны запросы типа «все годы на 15 октября», вам все равно нужны пользовательские запросы.
  • Я предлагаю вам создать «парсер», который принимает ваш запрос даты и дает вам часть предложения SQL WHERE. Я уверен, что в итоге у вас будет менее десятка случаев, поэтому вы можете выбрать оптимальный WHEREs для каждого из них. Таким образом вы избежите загрузки всех записей.
    • вы определенно не хотите делать что-то конкретное в SQL. Поэтому преобразуйте local в некоторый стандарт в коде, отличном от SQL, а затем используйте его для выполнения запроса (в основном, для разделения локализации / глобализации и выполнения запроса)
    • Тогда вы можете оптимизировать. Если вы видите, что у вас много запросов только для year, вы можете создать COMPUTED COLUMN, который будет содержать только YEAR и иметь индекс для него.
...