У меня есть две таблицы.
Первый - это таблица с префиксами
code name price
343 ek1 10
3435 nt 4
3432 ek2 2
Во-вторых, записи звонков с номерами телефонов
number time
834353212 10
834321242 20
834312345 30
Мне нужно написать скрипт, который находит самый длинный префикс из префиксов для каждой записи, и записать все эти данные в третью таблицу, например так:
number code ....
834353212 3435
834321242 3432
834312345 343
Для числа 834353212 мы должны обрезать '8', а затем найти самый длинный код из таблицы префиксов, его 3435.
Мы всегда должны сначала сбрасывать '8', а префикс должен быть в начале.
Я решил эту задачу очень давно, очень плохо. Это был ужасный Perl-скрипт, который делает много запросов для каждой записи. Этот скрипт:
Возьмите номер из таблицы звонков, сделайте подстроку от длины (число) до префикса 1 => $ в цикле
Выполните запрос: выберите количество (*) из префиксов, где код, например, $ prefix
- Если count> 0, тогда возьмите первые префиксы и запишите в таблицу
Первая проблема - это количество запросов call_records * length(number)
. Вторая проблема - LIKE
выражения. Я боюсь, что это медленно.
Я попытался решить вторую проблему:
CREATE EXTENSION pg_trgm;
CREATE INDEX prefix_idx ON prefix USING gist (code gist_trgm_ops);
Это ускоряет каждый запрос, но не решает проблему в целом.
У меня есть 20k префиксов и 170K номер сейчас, и мое старое решение плохо. Похоже, мне нужно новое решение без петель.
Только один запрос для каждой записи о вызове или что-то вроде этого.
источник
code
в первой таблице совпадает с префиксом позже. Не могли бы вы уточнить это? Также будет приветствоваться некоторая фиксация данных примера и желаемого результата (чтобы легче было следить за вашей проблемой).Ответы:
Я предполагаю тип данных
text
для соответствующих столбцов.«Простое» решение
Ключевые элементы:
DISTINCT ON
является расширением Postgres стандарта SQLDISTINCT
. Найдите подробное объяснение используемой техники запросов в этом связанном ответе на SO .ORDER BY p.code DESC
выбирает самое длинное совпадение, потому что'1234'
сортирует после'123'
(в порядке возрастания).Простая SQL Fiddle .
Без индекса запрос будет выполняться очень долго (не дожидаясь его завершения). Чтобы сделать это быстро, вам нужна поддержка индекса. Упомянутые вами индексы триграмм, предоставляемые дополнительным модулем,
pg_trgm
являются хорошим кандидатом. Вы должны выбрать между GIN и GiST index. Первый символ чисел является просто шумом и может быть исключен из индекса, что делает его дополнительно функциональным индексом.В моих тестах функциональный индекс GIN триграммы выиграл гонку за индекс GiST триграммы (как и ожидалось):
Продвинутый dbfiddle здесь .
Все результаты теста получены из локальной тестовой установки Postgres 9.1 с сокращенной настройкой: номера 17k и коды 2k:
Еще быстрее
Неудачная попытка с
text_pattern_ops
Как только мы игнорируем отвлекающий первый шумовой символ, он сводится к базовому левому привязанному образцу. Поэтому я попробовал функциональный индекс B-дерева с классом оператора
text_pattern_ops
(предполагая тип столбцаtext
).Это отлично работает для прямых запросов с одним поисковым термином и делает индекс триграммы плохим в сравнении:
Однако планировщик запросов не будет учитывать этот индекс для объединения двух таблиц. Я видел это ограничение раньше. У меня пока нет значимого объяснения этому.
Частичные / функциональные индексы B-дерева
Альтернатива - использовать проверки на равенство для частичных строк с частичными индексами. Это может быть использовано в
JOIN
.Поскольку у нас обычно ограниченное количество
different lengths
префиксов, мы можем построить решение, подобное представленному здесь, с частичными индексами.Скажем, у нас есть префиксы от 1 до 5 символов. Создайте несколько частичных функциональных индексов, один для каждой отдельной длины префикса:
Поскольку это частичные индексы, все они вместе чуть больше одного полного индекса.
Добавьте соответствующие индексы для чисел (с учетом начального шумового символа):
Хотя эти индексы содержат только подстроку и являются частичными, каждый из них охватывает большую часть или всю таблицу. Таким образом, они намного больше вместе, чем один общий индекс - за исключением длинных чисел. И они налагают больше работы для операций записи. Это цена за удивительную скорость.
Если эта стоимость слишком высока для вас (важна производительность записи / слишком много операций записи / дискового пространства), вы можете пропустить эти индексы. Остальное все еще быстрее, если не так быстро, как могло бы быть ...
Если числа никогда не короче
n
символов, отбросьте лишниеWHERE
предложения из некоторых или всех, а также отбросьте соответствующееWHERE
предложение из всех последующих запросов.Рекурсивный CTE
После всех настроек я надеялся на очень элегантное решение с рекурсивным CTE :
Однако, хотя этот запрос не плохой - он работает примерно так же хорошо, как простая версия с индексом GIN триграммы - он не дает того, к чему я стремился. Рекурсивный термин планируется только один раз, поэтому он не может использовать лучшие индексы. Только нерекурсивный термин может.
СОЮЗ ВСЕХ
Поскольку мы имеем дело с небольшим количеством рекурсий, мы можем просто итеративно их разобрать. Это позволяет оптимизировать планы для каждого из них. (Тем не менее, мы теряем рекурсивное исключение уже успешных чисел. Таким образом, есть еще возможности для улучшения, особенно для более широкого диапазона длин префиксов)):
Прорыв, наконец-то!
Функция SQL
Включение этого в функцию SQL устраняет накладные расходы на планирование запросов для повторного использования:
Вызов:
Функция PL / pgSQL с динамическим SQL
Эта функция plpgsql очень похожа на рекурсивный CTE, описанный выше, но динамический SQL
EXECUTE
заставляет запрос перепланироваться для каждой итерации. Теперь он использует все индивидуальные индексы.Кроме того, это работает для любого диапазона длин префикса. Функция принимает два параметра для диапазона, но я подготовил его со
DEFAULT
значениями, поэтому он работает и без явных параметров:Последний шаг не может быть легко включен в одну функцию. Либо просто назовите это так:
Или используйте другую функцию SQL в качестве оболочки:
Вызов:
Немного медленнее из-за необходимости планирования. Но более универсальный, чем SQL и более короткий для более длинных префиксов.
источник
Строка S является префиксом строки T, если T находится между S и SZ, где Z лексикографически больше, чем любая другая строка (например, 99999999 с достаточным количеством 9, чтобы превысить максимально длинный номер телефона в наборе данных, или иногда 0xFF будет работать).
Самый длинный общий префикс для любого данного T также лексикографически максимален, поэтому простая группа по max и найдет его.
Если это происходит медленно, это, вероятно, связано с вычисленными выражениями, поэтому вы также можете попробовать материализовать p.code || '999999' в столбец таблицы кодов с собственным индексом и т. Д.
источник