Postgres использует индекс с `split_part` - PullRequest
2 голосов
/ 24 сентября 2019

Контекст:

У меня есть таблица test:

=> \d+ test 
                                       Table "public.test"
Column     |          Type          | Collation | Nullable | Default | Storage  | Stats target | Description 
---------------+------------------------+-----------+----------+---------+----------+-------- 
------+-------------
 id            | character varying(255) |           |          |         | extended |              
| 
 configuration | jsonb                  |           |          |         | extended |              
| 

Столбец configuration содержит «четко определенный» json, который имеетключ называется source_url (пропуск других не относящихся ключей).Пример значения для столбца configuration:

{
"source_url": "https://<resource-address>?Signature=R1UzTGphWEhrTTFFZnc0Q4qkGRxkA5%2BHFZSfx3vNEvRsrlDcHdntArfHwkWiT7Qxi%2BWVJ4DbHJeFp3GpbS%2Bcb1H3r1PXPkfKB7Fjr6tFRCetDWAOtwrDrVOkR9G1m7iOePdi1RW%2Fn1LKE7MzQUImpkcZXkpHTUgzXpE3TPgoeVtVOXXt3qQBARpdSixzDU8dW%2FcftEkMDVuj4B%2Bwiecf6st21MjBPjzD4GNVA%2F6bgvKA6ExrdYmM5S6TYm1lz2e6juk81%2Fk4eDecUtjfOj9ekZiGJVMyrD5Tyw%2FTWOrfUB2VM1uw1PFT2Gqet87jNRDAtiIrJiw1lfB7Od1AwNxIk0Rqkrju8jWxmQhvb1BJLV%2BoRH56OHdm5nHXFmQdldVpyagQ8bQXoKmYmZPuxQb6t9FAyovGMav3aMsxWqIuKTxLzjB89XmgwBTxZSv5E9bkWUbom2%2BWq4O3%2BCrVxYwsqg%3D%3D&Expires-At=1569340020&Issued-At=1568293200"
    .
    .
}

URL содержит параметр запроса Expires-At


Проблема:

Существует запланированная работа, которая выполняется каждые 24 часа.Эта работа должна найти все такие записи, срок действия которых истек или вот-вот истечет (а затем что-то с этим сделать).

Решение:

У меня есть этот запрос, чтобы получить мойзадание выполнено:

select * from test where to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint) <= now() + interval '24 hours';

Объяснение:

  • Запрос сначала разбивает source_url на Expires-At= и выбирает присутствующую деталь справазатем он разделяет результирующую строку на & и выбирает левую ее часть, получая точное время эпохи, необходимое для text
  • Тот же запрос также работает для углового случая, когда Expires-Atявляется последним параметром запроса в source_url
  • . Как только он извлекает время эпохи как text, он сначала преобразует его в bigint, а затем преобразует его в метку времени Postgres, а затем эта метка времени сравнивается, еслионо будет меньше или равно времени в 24 часах от now()
  • Все строки, проходящие вышеупомянутое условие, выбраны

Таким образом, в конце, в каждомrun, scheduler обновляет все URL, срок действия которых истекает в следующие 24часов (включая те, которые уже истекли)


Вопросы:
  1. Хотя это решает мою проблему, мне действительно не нравится это решение.Здесь много манипуляций со строками, которые я нахожу не чистыми.Есть ли более чистый способ сделать это?
  2. Если мы «должны» перейти к описанному выше решению, можем ли мы даже использовать индексы для такого рода запросов?Я знаю, что функции lower(), upper() extra могут быть проиндексированы, но я действительно не могу придумать, как можно проиндексировать этот запрос.

Альтернативы:

Если не будет действительно чистого решения, я пойду с этим:

  • Я бы ввел новый ключ внутри configuration json под названием expires_at, убедившись, чтоэто заполнено правильным значением, каждый раз, когда вставляется строка.
  • И затем непосредственно запрашивает это вновь добавленное поле (индекс имеет столбец configuration).

Iпризнаю, что таким образом я повторяю информацию Expires-At, но из всех возможных решений, которые я мог придумать, это то, что я считаю наиболее чистым.

Есть ли лучший способ, чем этот, которыйвы, ребята, можете думать о?



РЕДАКТИРОВАТЬ:

Обновлен запрос для использования substring() с регулярным выражением вместо внутреннего split_part():

select * from test where to_timestamp(split_part(substring(configuration->>'source_url' from 'Expires-At=\d+'), '=', 2)::bigint) <= now() + interval '24 hours';

Ответы [ 2 ]

2 голосов
/ 24 сентября 2019

Учитывая вашу текущую модель данных, я не считаю ваше условие WHERE плохим.

Вы можете индексировать его с помощью

CREATE INDEX ON test ( 
   to_timestamp(
      split_part(
         split_part(
            configuration->>'source_url',
            'Expires-At=',
            2
         ),
         '&',
         1
      )::bigint
   )
);

По сути, вам нужно проиндексировать все выражениена левой стороне =.Вы можете сделать это только в том случае, если задействованы все функции и операторы IMMUTABLE, что, как мне кажется, в вашем случае.

Хотя я бы изменил модель данных.Во-первых, я не вижу значения наличия столбца jsonb с одним значением.Почему бы не указывать URL-адрес в виде столбца text?

Вы можете пойти дальше и разбить URL-адрес на отдельные части, которые хранятся в столбцах.

Если все это хорошая идея, зависито том, как вы используете значение в базе данных: часто это хорошая идея, чтобы отделить те части данных, которые вы используете в условиях WHERE и т. п., и оставить все остальное «в кучу».Это в некоторой степени дело вкуса.

1 голос
/ 24 сентября 2019

Вы можете использовать модуль разбора URI, если это та часть, которую вы считаете нечистой.Вы можете использовать plperl или plpythonu с любой библиотекой парсера URI, которую вы предпочитаете.Но если ваш JSON действительно "хорошо определен", я не вижу особого смысла.Если вы уже не используете plperl или plpythonu, добавление этих зависимостей, вероятно, добавляет больше «грязи», чем удаляет.

Вы можете создать индекс:

create index on test (to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint));
set enable_seqscan TO off;
explain select * from test where to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint) <= now() + interval '24 hours';
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using test_to_timestamp_idx1 on test  (cost=0.13..8.15 rows=1 width=36)
   Index Cond: (to_timestamp(((split_part(split_part((configuration ->> 'source_url'::text), 'Expires-At='::text, 2), '&'::text, 1))::bigint)::double precision) <= (now() + '24:00:00'::interval))

Я бы представилновый ключ в конфигурации json, называемый expires_at, поэтому он должен заполняться правильным значением каждый раз, когда вставляется строка.

Разве это не просто переупорядочивает грязь?Это делает запрос более привлекательным за счет того, что вставка выглядит более уродливой.Возможно, вы могли бы поместить его в триггер INSERT OR UPATE.

...