1.) Ваш запрос не уловил всех совпадений - это уже было исправлено другими ответами.
2.) Тип данных ваших столбцов starttime
и endtime
равен timestamp
,Таким образом, ваше WHERE
предложение также немного неверно:
BETWEEN '2011-11-02' AND '2011-11-03'
Это будет включать «2011-11-03 00:00».Верхняя граница должна быть исключена .
3.) Удален синтаксис смешанного регистра без двойных кавычек.Идентификаторы без кавычек преобразуются в нижний регистр автоматически.Проще говоря: лучше всего вообще не использовать смешанные идентификаторы в PostgreSQL.
4.) Преобразовать запрос, чтобы использовать явный JOIN, который всегда предпочтителен.На самом деле, я сделал это LEFT [OUTER] JOIN, потому что я хочу подсчитывать вызовы, которые перекрываются и без других вызовов.
5.) Немного упростил синтаксис для получения этого базового запроса:
SELECT t1.sid, count(*) AS ct
FROM calls_nov t1
LEFT JOIN calls_nov t2 ON t1.starttime <= t2.endtime
AND t1.endtime >= t2.starttime
WHERE t1.starttime >= '2011-11-02 0:0'::timestamp
AND t1.starttime < '2011-11-03 0:0'::timestamp
GROUP BY 1
ORDER BY 2 DESC;
Этот запрос является чрезвычайно медленным для большой таблицы, поскольку каждая строка, начинающаяся с '2011-11-02', должна сравниваться с каждой строкой во всей таблице, что приводит к(почти) O (n²) стоимость.
Быстрее
Мы можем резко сократить расходы, предварительно выбрав возможных кандидатов .Выбирайте только те столбцы и строки, которые вам нужны.Я делаю это с двумя CTE.
- Выбор звонков, начиная с рассматриваемого дня.-> CTE
x
- Рассчитать последний конец этих вызовов.(подзапрос в CTE
y
) - Выбор только вызовов, которые перекрываются с общим диапазоном CTE
x
.-> CTE y
- Последний запрос намного быстрее , чем запрос к огромной базовой таблице.
WITH x AS (
SELECT sid, starttime, endtime
FROM calls_nov
WHERE starttime >= '2011-11-02 0:0'
AND starttime < '2011-11-03 0:0'
), y AS (
SELECT starttime, endtime
FROM calls_nov
WHERE endtime >= '2011-11-02 0:0'
AND starttime <= (SELECT max(endtime) As max_endtime FROM x)
)
SELECT x.sid, count(*) AS count_overlaps
FROM x
LEFT JOIN y ON x.starttime <= y.endtime
AND x.endtime >= y.starttime
GROUP BY 1
ORDER BY 2 DESC;
Еще быстрее
У меня есть реальная таблица из 350 000 строк с перекрывающимися отметками времени начала / окончания, аналогичными вашей.Я использовал это для быстрого теста .PostgreSQL 8.4, ограниченные ресурсы, потому что это тестовая БД.Индексы на start
и end
.(Индекс по столбцу ID здесь не имеет значения.) Протестировано с EXPLAIN ANALYZE
, лучшее из 5.
Общее время выполнения: 476994,774 мс
Вариант CTE:
Общее время выполнения: 4199,788 мс -это> коэффициент 100.
После добавления многоколоночного индекса формы:
CREATE INDEX start_end_index on calls_nov (starttime, endtime);
Общее время выполнения: 4159,367 мс
UltimateСкорость
Если этого недостаточно, есть способ ускорить его еще на один порядок.Вместо вышеуказанных CTE материализуйте временные таблицы и - это критический момент - создайте index для второй.Может выглядеть так:
Выполнить как одна транзакция :
CREATE TEMP TABLE x ON COMMIT DROP AS
SELECT sid, starttime, endtime
FROM calls_nov
WHERE starttime >= '2011-11-02 0:0'
AND starttime < '2011-11-03 0:0';
CREATE TEMP TABLE y ON COMMIT DROP AS
SELECT starttime, endtime
FROM calls_nov
WHERE endtime >= '2011-11-02 0:0'
AND starttime <= (SELECT max(endtime) FROM x);
CREATE INDEX y_idx ON y (starttime, endtime); -- this is where the magic happens
SELECT x.sid, count(*) AS ct
FROM x
LEFT JOIN y ON x.starttime <= y.endtime
AND x.endtime >= y.starttime
GROUP BY 1
ORDER BY 2 DESC;
Читать о временных таблицах в руководстве .
Окончательное решение
Создание функции plpgsql , которая заключает в себе магию.
Диагностика типичного размераваши временные таблицы.Создайте их отдельно и измерьте:
SELECT pg_size_pretty(pg_total_relation_size('tmp_tbl'));
Если они больше, чем ваши настройки для temp_buffers , тогда временно установите их достаточно высоко в вашей функции, чтобы удержать ваши временныетаблицы в оперативной памяти.Это значительное ускорение, если вам не нужно переключаться на диск.(Должно быть первое использование временных таблиц в сеансе, чтобы иметь эффект.)
CREATE OR REPLACE FUNCTION f_call_overlaps(date)
RETURNS TABLE (sid varchar, ct integer) AS
$BODY$
DECLARE
_from timestamp := $1::timestamp;
_to timestamp := ($1 +1)::timestamp;
BEGIN
SET temp_buffers = 64MB'; -- example value; more RAM for temp tables;
CREATE TEMP TABLE x ON COMMIT DROP AS
SELECT c.sid, starttime, endtime -- avoid naming conflict with OUT param
FROM calls_nov c
WHERE starttime >= _from
AND starttime < _to;
CREATE TEMP TABLE y ON COMMIT DROP AS
SELECT starttime, endtime
FROM calls_nov
WHERE endtime >= _from
AND starttime <= (SELECT max(endtime) FROM x);
CREATE INDEX y_idx ON y (starttime, endtime);
RETURN QUERY
SELECT x.sid, count(*)::int -- AS ct
FROM x
LEFT JOIN y ON x.starttime <= y.endtime AND x.endtime >= y.starttime
GROUP BY 1
ORDER BY 2 DESC;
END;
$BODY$ LANGUAGE plpgsql;
Вызов:
SELECT * FROM f_call_overlaps('2011-11-02') -- just name your date
Общее время выполнения: 138,169 мс - это фактор 3000
Что еще можно сделать, чтобы ускорить его?
Общая оптимизация производительности .
CLUSTER calls_nov USING starttime_index; -- this also vacuums the table fully
ANALYZE calls_nov;