Задача
72 дочерних таблицы, каждая из которых имеет годовой индекс и индекс станции, определяются следующим образом:
CREATE TABLE climate.measurement_12_013
(
-- Inherited from table climate.measurement_12_013: id bigint NOT NULL DEFAULT nextval('climate.measurement_id_seq'::regclass),
-- Inherited from table climate.measurement_12_013: station_id integer NOT NULL,
-- Inherited from table climate.measurement_12_013: taken date NOT NULL,
-- Inherited from table climate.measurement_12_013: amount numeric(8,2) NOT NULL,
-- Inherited from table climate.measurement_12_013: category_id smallint NOT NULL,
-- Inherited from table climate.measurement_12_013: flag character varying(1) NOT NULL DEFAULT ' '::character varying,
CONSTRAINT measurement_12_013_category_id_check CHECK (category_id = 7),
CONSTRAINT measurement_12_013_taken_check CHECK (date_part('month'::text, taken)::integer = 12)
)
INHERITS (climate.measurement)
CREATE INDEX measurement_12_013_s_idx
ON climate.measurement_12_013
USING btree
(station_id);
CREATE INDEX measurement_12_013_y_idx
ON climate.measurement_12_013
USING btree
(date_part('year'::text, taken));
(ограничения внешнего ключа будут добавлены позже.)
Следующий запрос выполняется крайне медленно из-за полного сканирования таблицы:
SELECT
count(1) AS measurements,
avg(m.amount) AS amount
FROM
climate.measurement m
WHERE
m.station_id IN (
SELECT
s.id
FROM
climate.station s,
climate.city c
WHERE
/* For one city... */
c.id = 5182 AND
/* Where stations are within an elevation range... */
s.elevation BETWEEN 0 AND 3000 AND
/* and within a specific radius... */
6371.009 * SQRT(
POW(RADIANS(c.latitude_decimal - s.latitude_decimal), 2) +
(COS(RADIANS(c.latitude_decimal + s.latitude_decimal) / 2) *
POW(RADIANS(c.longitude_decimal - s.longitude_decimal), 2))
) <= 50
) AND
/* Data before 1900 is shaky; insufficient after 2009. */
extract( YEAR FROM m.taken ) BETWEEN 1900 AND 2009 AND
/* Whittled down by category... */
m.category_id = 1 AND
/* Between the selected days and years... */
m.taken BETWEEN
/* Start date. */
(extract( YEAR FROM m.taken )||'-01-01')::date AND
/* End date. Calculated by checking to see if the end date wraps
into the next year. If it does, then add 1 to the current year.
*/
(cast(extract( YEAR FROM m.taken ) + greatest( -1 *
sign(
(extract( YEAR FROM m.taken )||'-12-31')::date -
(extract( YEAR FROM m.taken )||'-01-01')::date ), 0
) AS text)||'-12-31')::date
GROUP BY
extract( YEAR FROM m.taken )
Вялость проистекает из этой части запроса:
m.taken BETWEEN
/* Start date. */
(extract( YEAR FROM m.taken )||'-01-01')::date AND
/* End date. Calculated by checking to see if the end date wraps
into the next year. If it does, then add 1 to the current year.
*/
(cast(extract( YEAR FROM m.taken ) + greatest( -1 *
sign(
(extract( YEAR FROM m.taken )||'-12-31')::date -
(extract( YEAR FROM m.taken )||'-01-01')::date ), 0
) AS text)||'-12-31')::date
Эта часть запроса соответствует выбору дней. Например, если пользователь хочет просматривать данные в период с 1 июня по 1 июля за все годы, для которых имеются данные, приведенное выше предложение сопоставляется только с этими днями. Если пользователь хочет просмотреть данные в период с 22 декабря по 22 марта, опять же для всех лет, для которых имеются данные, вышеприведенное предложение рассчитывает, что 22 марта наступит в следующем году 22 декабря, и, соответственно, соответствует дате:
В настоящее время даты установлены с 1 января по 31 декабря, но будут параметризованы, как показано выше.
HashAggregate от плана показывает стоимость 10006220141.11, что, я подозреваю, на астрономически огромной стороне.
В таблице измерений выполняется полное сканирование таблицы (в которой нет ни данных, ни индексов). Таблица объединяет 273 миллиона строк из своих дочерних таблиц.
Вопрос
Как правильно индексировать даты, чтобы избежать полного сканирования таблицы?
Опции, которые я рассмотрел:
- GIN
- GiST
- Перепишите предложение WHERE
- Разделение столбцов year_taken, month_taken и day_taken в таблицах
Что ты думаешь?
Спасибо!