2017-02-08 4 views
2

РедактироватьЛенивый заказ на /, где оценка

Кажется, что чистый материализации могут быть сохранены в качестве колонки на столе и индексироваться; Однако, мой конкретный случай использования (semver.satisfies) требует более общего решения:

create table Submissions (
    version text 
    created_at timestamp 
) 

create index Submissions_1 on Submissions (created_at) 

Моего запрос будет выглядеть так:

select * from Submissions 
where 
    created_at <= '2016-07-12' and 
    satisfies(version, '>=1.2.3 <4.5.6') 
order by created_at desc 
limit 1; 

Где я не был бы в состоянии практически использовать ту же технику, запоминание ,

Оригинал

У меня есть таблица хранения текстовых данных и дат, на которых они были созданы:

create table Submissions (
    content text, 
    created_at timestamp 
); 

create index Submissions_1 on Submissions (created_at); 

Учитывая контрольную сумму и ссылочную дату, я хочу, чтобы получить последнюю Submission где content поля соответствует этой контрольной сумме:

select * from Submissions 
where 
    created_at <= '2016-07-12' and 
    expensive_chksm(content) = '77ac76dc0d4622ba9aa795acafc05f1e' 
order by created_at desc 
limit 1; 

Это работает, но я t очень медленно. Что Postgres заканчивает тем, что делает, принимая контрольную сумму каждой строки, а затем выполняя order by:

Limit (cost=270834.18..270834.18 rows=1 width=32) (actual time=1132.898..1132.898 rows=1 loops=1) 
    -> Sort (cost=270834.18..271561.27 rows=290836 width=32) (actual time=1132.898..1132.898 rows=1 loops=1) 
     Sort Key: created_at DESC 
     Sort Method: top-N heapsort Memory: 25kB 
     -> Seq Scan on installation (cost=0.00..269380.00 rows=290836 width=32) (actual time=0.118..1129.961 rows=17305 loops=1) 
       Filter: created_at <= '2016-07-12' AND expensive_chksm(content) = '77ac76dc0d4622ba9aa795acafc05f1e' 
       Rows Removed by Filter: 982695 
Planning time: 0.066 ms 
Execution time: 1246.941 ms 

Без order by, это операция суб-миллисекунды, потому что Postgres знает, что я хочу только первый результат. Единственное различие заключается в том, что я хочу, чтобы Postgres начал поиск с последней даты вниз.

В идеале Postgres бы:

  1. фильтр created_at
  2. сортировать по created_at, по убыванию
  3. возвращение первой строки, где контрольная сумма соответствует

Я пытался писать запросы с встроенными представлениями, но explain analyze показывает, что он будет просто переписан в то, что я уже имел выше.

+0

Вы можете использовать нормальное сравнение и нормальный индекс, если вы храните его немного больше разумно, например, bigint '1000002000003' вместо' 1.2.3' и '4000005000006' вместо 4.5.6 (основной * 10^12 + минор * 10^6 + rel легкость). – Tometzky

+0

Это хорошая идея, но semver действительно сложный. Я экспериментировал с несколькими подходами, и кажется, что то, что я сейчас пытаюсь сделать, дает лучший баланс точности результатов. Я уточнил вопрос, чтобы уточнить. – Synchronous

+0

Может быть, чем использовать int [] -> [1,2,3]? Это также хороший показатель и лучше сравнивать? –

ответ

0

Если вы всегда будет запрашивать на checksum, то альтернативой будет иметь другой столбец контрольной суммы в таблице, например:

create table Submissions (
    content text, 
    created_at timestamp, 
    checksum varchar 
); 

Вы можете затем insert/update контрольная сумма всякий раз, когда строка получает inserted/updated (или напишите trigger), который делает это для вас и запрашивает на колонке checksum непосредственно для быстрого результата.

+0

Спасибо за ответ! Ваше решение работает для простых вещей, таких как проверка контрольных сумм. В моем проекте я использую расширение 'semver.satisfies' для проверки поля' version', заданного диапазоном, поэтому, к сожалению, я не могу использовать одно и то же решение. Если у вас есть лучшая аналогия, я буду рад обновить вопрос! – Synchronous

+0

Поле версии также является частью таблицы 'Submissions'? –

+0

Моя фактическая таблица выглядит так: '(текст версии, created_at timestamp)' и мое предложение where выглядит следующим образом: 'created_at <= '2016-07-12' и удовлетворяет (version, '> = 4.0.0 <5.0.0 ') ' – Synchronous

0

Попробуйте

select * 
from Submissions 
where created_at = (
    select max(created_at) 
    from Submissions 
    where expensive_chksm(content) = '77ac76dc0d4622ba9aa795acafc05f1e') 
+2

все равно нужно рассчитать chksm для всех строк, чтобы найти max created_at – spiderman

1

Вы можете использовать вспомогательный запрос для метки времени и упорядочения части, а затем запустить ChkSum снаружи:

select * from (
    select * from submissions where 
    created_at <= '2016-07-12' and 
    order by created_at desc) as S 
where expensive_chksm(content) = '77ac76dc0d4622ba9aa795acafc05f1e' 
LIMIT 1 
+0

Спасибо за ответ! К сожалению, это просто переписывается планировщиком запросов на то, что у меня было до этого. – Synchronous

+0

Кроме того, если есть способ предотвратить перезапись, у меня будет то, что мне нужно! – Synchronous

2

Вы можете создать индекс для обоих полей вместе:

create index Submissions_1 on Submissions (created_at DESC, expensive_chksm(content)); 

                     QUERY PLAN                   
----------------------------------------------------------------------------------------------------------------------------------------------------------- 
Limit (cost=0.15..8.16 rows=1 width=40) (actual time=0.004..0.004 rows=0 loops=1) 
    -> Index Scan using submissions_1 on submissions (cost=0.15..16.17 rows=2 width=40) (actual time=0.002..0.002 rows=0 loops=1) 
     Index Cond: ((created_at <= '2016-07-12 00:00:00'::timestamp without time zone) AND ((content)::text = '77ac76dc0d4622ba9aa795acafc05f1e'::text)) 
Planning time: 0.414 ms 
Execution time: 0.036 ms 

Важно также использовать DESC в индексе.

ОБНОВЛЕНО:

Для хранения и сравнения версии вы можете использовать INT []

create table Submissions (
    version int[], 
    created_at timestamp 
); 

INSERT INTO Submissions SELECT ARRAY [ (random() * 10)::int2, (random() * 10)::int2, (random() * 10)::int2], '2016-01-01'::timestamp + ('1 hour')::interval * random() * 10000 FROM generate_series(1, 1000000); 

    create index Submissions_1 on Submissions (created_at DESC, version); 

EXPLAIN ANALYZE select * from Submissions 
where 
    created_at <= '2016-07-12' 
    AND version <= ARRAY [5,2,3] 
    AND version > ARRAY [1,2,3] 
order by created_at desc 
limit 1; 

                      QUERY PLAN                    
--------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
Limit (cost=0.42..13.24 rows=1 width=40) (actual time=0.074..0.075 rows=1 loops=1) 
    -> Index Only Scan using submissions_1 on submissions (cost=0.42..21355.76 rows=1667 width=40) (actual time=0.073..0.073 rows=1 loops=1) 
     Index Cond: ((created_at <= '2016-07-12 00:00:00'::timestamp without time zone) AND (version <= '{5,2,3}'::integer[]) AND (version > '{1,2,3}'::integer[])) 
     Heap Fetches: 1 
Planning time: 3.019 ms 
Execution time: 0.100 ms 

Чтобы a_horse_with_no_name комментарий: Порядок условий в где положение не имеет значения для использования индекса. Лучше поставить тот, который может быть использован для выражения равенства сначала в индексе, а затем в выражении диапазона. -

BEGIN; 

create table Submissions (
    content text, 
    created_at timestamp 
); 


CREATE FUNCTION expensive_chksm(varchar) RETURNS varchar AS $$ 
SELECT $1; 
$$ LANGUAGE sql; 

INSERT INTO Submissions SELECT (random() * 1000000000)::text, '2016-01-01'::timestamp + ('1 hour')::interval * random() * 10000 FROM generate_series(1, 1000000); 
INSERT INTO Submissions SELECT '77ac76dc0d4622ba9aa795acafc05f1e', '2016-01-01'::timestamp + ('1 hour')::interval * random() * 10000 FROM generate_series(1, 100000); 

    create index Submissions_1 on Submissions (created_at DESC, expensive_chksm(content)); 
-- create index Submissions_2 on Submissions (expensive_chksm(content), created_at DESC); 

EXPLAIN ANALYZE select * from Submissions 
where 
    created_at <= '2016-07-12' and 
    expensive_chksm(content) = '77ac76dc0d4622ba9aa795acafc05f1e' 
order by created_at desc 
limit 1; 

Использование Submission1:

                 QUERY PLAN                   
----------------------------------------------------------------------------------------------------------------------------------------------------------- 
Limit (cost=0.43..10.98 rows=1 width=40) (actual time=0.018..0.019 rows=1 loops=1) 
    -> Index Scan using submissions_1 on submissions (cost=0.43..19341.43 rows=1833 width=40) (actual time=0.018..0.018 rows=1 loops=1) 
     Index Cond: ((created_at <= '2016-07-12 00:00:00'::timestamp without time zone) AND ((content)::text = '77ac76dc0d4622ba9aa795acafc05f1e'::text)) 
Planning time: 0.257 ms 
Execution time: 0.033 ms 

Использование Submission2:

                   QUERY PLAN                    
----------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
Limit (cost=4482.39..4482.40 rows=1 width=40) (actual time=29.096..29.096 rows=1 loops=1) 
    -> Sort (cost=4482.39..4486.98 rows=1833 width=40) (actual time=29.095..29.095 rows=1 loops=1) 
     Sort Key: created_at DESC 
     Sort Method: top-N heapsort Memory: 25kB 
     -> Bitmap Heap Scan on submissions (cost=67.22..4473.23 rows=1833 width=40) (actual time=15.457..23.683 rows=46419 loops=1) 
       Recheck Cond: (((content)::text = '77ac76dc0d4622ba9aa795acafc05f1e'::text) AND (created_at <= '2016-07-12 00:00:00'::timestamp without time zone)) 
       Heap Blocks: exact=936 
       -> Bitmap Index Scan on submissions_1 (cost=0.00..66.76 rows=1833 width=0) (actual time=15.284..15.284 rows=46419 loops=1) 
        Index Cond: (((content)::text = '77ac76dc0d4622ba9aa795acafc05f1e'::text) AND (created_at <= '2016-07-12 00:00:00'::timestamp without time zone)) 
Planning time: 0.583 ms 
Execution time: 29.134 ms 

PostgreSQL 9.6.1

+0

На самом деле было бы лучше сделать индекс в обратном порядке: 'create index submissions_chksum_created_desc_idx на представлениях (дорогой_chksm (content), created_at desc);' поскольку база данных всегда будет иметь возможность использовать двоичный поиск, чтобы найти строку. – Tometzky

+0

Нет, порядок индекса должен быть таким же, как в запросе. Другим способом - сканирование индексов будет использоваться растровое сканирование индекса + растровое сканирование кучи + сортировка –

+0

Порядок условий в предложении 'where' не имеет значения для использования индекса. Лучше поставить тот, который может быть использован для выражения равенства сначала в индексе, а затем в выражении диапазона. –