Получите общее количество записей по дням, месяцам, годам и продолжительности жизни за один запрос с оптимизацией - PullRequest
0 голосов
/ 25 мая 2011

У меня есть Postgres DB, работающий 7.4 (Да, мы находимся в процессе обновления)

У меня есть четыре отдельных запроса, чтобы получить количество записей Daily, Monthly, Yearly и Lifetime

SELECT COUNT(field)
FROM database
WHERE date_field
    BETWEEN DATE_TRUNC('DAY' LOCALTIMESTAMP) 
    AND DATE_TRUNC('DAY' LOCALTIMESTAMP) + INTERVAL '1 DAY'

Для Месяца просто замените слово DAY на MONTH в запросе и т. Д. Для каждого периода времени.

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

Заранее спасибо!

ПРИМЕЧАНИЕ: поле_ даты имеет метку времени без часового пояса

ОБНОВЛЕНИЕ:

Извините, я отфильтровываю записи с дополнительнымиограничения запроса, просто хотел дать суть сравнений date_field.Извините за путаницу

Ответы [ 3 ]

1 голос
/ 25 мая 2011

У меня есть идея использовать подготовленные операторы и таблицу простой статистики (record_count_t) для этого:

-- DROP TABLE IF EXISTS record_count_t;
-- DEALLOCATE record_count;
-- DROP FUNCTION updateRecordCounts();

CREATE TABLE record_count_t (type char, count bigint);
INSERT INTO record_count_t (type) VALUES ('d'), ('m'), ('y'), ('l');

PREPARE record_count (text) AS
UPDATE record_count_t SET count =
(SELECT COUNT(field)
FROM database
WHERE
CASE WHEN $1 <> 'l' THEN
    DATE_TRUNC($1, date_field) = DATE_TRUNC($1, LOCALTIMESTAMP)
ELSE TRUE END)
WHERE type = $1;

CREATE FUNCTION updateRecordCounts() RETURNS void AS
$$
    EXECUTE record_count('d');
    EXECUTE record_count('m');
    EXECUTE record_count('y');
    EXECUTE record_count('l');
$$
LANGUAGE SQL;

SELECT updateRecordCounts();
SELECT type,count FROM record_count_t;

Используйте функцию updateRecordCounts () каждый раз, когда вам нужно обновить статистику.

0 голосов
/ 25 мая 2011

Я думаю, что это невозможно оптимизировать дальше, чем это уже есть.

Если вы собираете ежедневную / ежемесячную / годовую статистику, как я предполагаю, вы делаете, один из вариантов (после обновления, конечно) - это с утверждением и соответствующими объединениями, например:

with daily_stats as (
(what you posted)
),
monthly_stats as (
(what you posted monthly)
),
etc.
select daily_stats.stats,
       monthly_stats.stats,
       etc.
stats
left join yearly_stats on ...
left join monthly_stats on ...
left join daily_stats on ...

Однако, это на самом деле будет работать хуже, чем выполнение каждого запроса отдельно в производственной среде, потому что вы введете левые соединения в БД, которые также могут выполняться в промежуточном программном обеспечении (т.е. показывать ежедневно, затем ежемесячно, затем годовая и, наконец, пожизненная статистика). (Если не лучше, так как вы будете избегать полных сканирований таблицы.)

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

0 голосов
/ 25 мая 2011

Хлоп! Не делай этого !!! Не потому, что вы не можете делать то, что вы просите, а потому, что вам, вероятно, не следует делать то, что вы просите, таким образом. Я предполагаю, что причина того, что у вас есть date_field в вашем примере, заключается в том, что у вас есть date_field, привязанный к пользователю или другим метаданным.

Подумайте об этом: вы просите PostgreSQL сканировать 100% записей, относящихся к данному пользователю. Если это не разовая операция, вы почти наверняка не хотите этого делать. Если это однократная операция, и вы планируете кэшировать это значение в качестве метаданных, то кого волнует оптимизация? Пространство дешево и сэкономит вам кучу времени на выполнение в будущем.

Вы должны добавить 4x поля метаданных для каждого пользователя (или что бы то ни было), которые помогают суммировать данные. У вас есть два варианта, я позволю вам выяснить, как использовать это, чтобы сохранить исторические подсчеты, но вот простая версия:

CREATE TABLE user_counts_only_keep_current (
  user_id , -- Your user_id
  lifetime INT DEFAULT 0,
  yearly INT DEFAULT 0,
  monthly INT DEFAULT 0,
  daily INT DEFAULT 0,
  last_update_utc TIMESTAMP WITH  TIME ZONE,
  FOREIGN KEY(user_id) REFERENCES "user"(id)
);
CREATE UNIQUE INDEX this_tbl_user_id_udx ON user_counts_only_keep_current(user_id);

Установите некоторые хранимые процедуры, которые обнуляют отдельные столбцы, если last_update_utc не соответствует текущему дню в соответствии с NOW(). Вы можете проявить творческий подход отсюда, но увеличение количества таких записей будет правильным способом.

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

...