SQL: как проверить, является ли строка подстрокой любой другой строки в той же таблице
У меня есть таблица, полная строк (текста), и мне нравится получать все строки, которые являются подстроками любой другой строки в той же таблице. Например, если бы у меня были эти три строки в моей таблице:
WORD WORD_ID
cup 0
cake 1
cupcake 2
В результате моего запроса я хотел бы получить что-то вроде этого:
WORD WORD_ID SUBSTRING SUBSTRING_ID
cupcake 2 cup 0
cupcake 2 cake 1
Я знаю, что могу сделать это с помощью двух циклов (с помощью Python или JS), прокручивая каждое слово в моей таблице и сопоставляя его с каждым словом в той же таблице, но я не уверен, как это можно сделать с помощью SQL (PostgreSQL, если уж на то пошло).
Я был бы очень счастлив, если бы кто-нибудь здесь мог помочь мне в этом.
3 ответа:
Используйте self-join:
select w1.word, w1.word_id, w2.word, w2.word_id from words w1 join words w2 on w1.word <> w2.word and w1.word like format('%%%s%%', w2.word); word | word_id | word | word_id ---------+---------+------+--------- cupcake | 2 | cup | 0 cupcake | 2 | cake | 1 (2 rows)
Задача
Задача потенциально может остановить ваш сервер баз данных для таблиц нетривиального размера, так как этоO(N2) проблема, пока вы не можете использовать для нее индекс.
При последовательном сканировании вы должны проверить каждую возможную комбинацию из двух строк, то есть комбинации
В любом случае производительность быстро ухудшается с увеличением числа строк в таблице. Проверьте каждый запрос с помощьюn * (n-1) / 2
- Postgres будет выполнять тестыn * n-1
, так как не так просто исключить обратные повторяющиеся комбинации. Если вас устраивает первый матч, то он становится дешевле - сколько зависит от того, как вы его проведете. распределение данных. Для многих матчей Postgres найдет совпадение для строки раньше и может пропустить тестирование остальных. Для некоторых матчей большинство проверок все равно приходится выполнять.EXPLAIN ANALYZE
и 10, 100, 1000 и т. д. строки в таблице, чтобы увидеть сами.Решение
Создайте индекс триграммы на
word
- предпочтительно Джин .CREATE INDEX tbl_word_trgm_gin_idx ON tbl USING gin (word gin_trgm_ops);
Подробности:
Запросы в обоих ответах до сих пор не использовали бы индекс, даже если бы он у вас был. Используйте запрос, который действительно может работать с этим индексом:
Перечислить все совпадения (в соответствии с телом вопроса):
Используйте aLATERAL CROSS JOIN
:SELECT t2.word_id, t2.word, t1.word_id, t1.word FROM tbl t1 , LATERAL ( SELECT word_id, word FROM tbl WHERE word_id <> t1.word_id AND word like format('%%%s%%', t1.word) ) t2;
Чтобы просто получить строки, которые имеют любой матч (в соответствии с вашим названием): Использовать
EXISTS
полу-соединение:SELECT t1.word_id, t1.word FROM tbl t1 WHERE EXISTS ( SELECT 1 FROM tbl WHERE word_id <> t1.word_id AND word like format('%%%s%%', t1.word) );
Я бы подошел к этому так:
select w1.word_id, w1.word, w2.word_id as substring_id w2.word as substring from words w1 join words w2 on w1.word like '%' || w2.word || '%' and w1.word <> w2.word;
Примечание: это, вероятно, немного быстрее, чем делать цикл в приложении. Однако этот запрос будет реализован как вложенный цикл в Postgres, поэтому он не будет слишком быстрым.