Сравнение фраз в PostgreSQL - PullRequest
       1

Сравнение фраз в PostgreSQL

0 голосов
/ 07 декабря 2018

Как мне искать в столбце varchar в таблице Postgres строки, содержащие одну и ту же фразу из трех слов?

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

Пример:

SELECT * 
FROM types t1 
WHERE EXISTS (SELECT * 
              FROM types t2 
              WHERE t1.name phrase_matches t2.name 
                AND t1.id > t2.id)

Здесь phrase_matches является созданнымОперация up, при которой

'my foo bar baz' phrase_matches 'foo bar baz whatever' возвращает true

и

'my foo bar baz' phrase_matches 'foo baz whatever bar' возвращает false

Редактировать: обновление для всех, кто приходит из Google- решение без временной таблицы с использованием объединения заняло более часа на таблице с 18 тыс. строк.Версия временной таблицы работала всего за несколько секунд.

Ответы [ 3 ]

0 голосов
/ 07 декабря 2018

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

select n.name from(
select x.name as xname,count(*) from 
(
  (
    select name,unnest(string_to_array(name2,' '))  as name2
                              from new
  )as x
    inner join
    (
        select name,unnest(string_to_array(name,' ')) as name1
         from new
    )as y
    on x.name2=y.name1 and y.id>x.id
) group by x.name having count(*)>=3)r inner join new n on r.xname=n.name

Вот такая же скрипка: https://www.db -fiddle.com / f / phLirNij577PwEpd8UERef / 0

Обратите внимание, что я не включил идентификатор в мою скрипку, но вы можете сделать это самостоятельно.

0 голосов
/ 07 декабря 2018

демо: дБ <> скрипка

WITH words AS (
    SELECT phrase, unnest, row_number() OVER ()
    FROM (
        SELECT phrase, unnest(string_to_array(phrase, ' '))
        FROM phrases
    )s
), phrase_parts AS (

    SELECT 
        phrase, array_to_string(array_agg, ' ') as check_phrase
    FROM (
        SELECT
            w1.phrase, array_agg(w2.unnest) OVER (PARTITION BY w1.row_number ORDER BY w2.row_number)
        FROM words w1
        JOIN words w2
        ON w1.phrase = w2.phrase and w1.row_number <= w2.row_number

        ORDER BY w1.row_number, w2.row_number
    ) s
    WHERE array_length(array_agg, 1) = 3
)
SELECT p.phrase as a, pp.phrase as b, pp.check_phrase 
FROM 
    phrases p 
JOIN 
    phrase_parts pp 
ON p.phrase LIKE '%' || pp.check_phrase || '%' and p.phrase <> pp.phrase

Расширенный набор данных:

phrase
my foo bar baz
foo baz whatever bar
foo bar baz whatever
blah my foo bar blah
blah my foo baz blah

Результат:

a                      b                      check_phrase
blah my foo bar blah   my foo bar baz         my foo bar
foo bar baz whatever   my foo bar baz         foo bar baz
my foo bar baz         foo bar baz whatever   foo bar baz
blah my foo baz blah   blah my foo bar blah   blah my foo
my foo bar baz         blah my foo bar blah   my foo bar
blah my foo bar blah   blah my foo baz blah   blah my foo
  1. CTE words создает список всех слов всех фраз.Все слова получают индекс для обеспечения оригинального порядка в их фразах.

  2. CTE phrase_parts создает все возможные фразы из 3 слов: для каждой исходной фразы объединяются все слова.

После объединения результат выглядит следующим образом:

phrase                 unnest   row_number   phrase                 unnest     row_number
my foo bar baz         my       1            my foo bar baz         my         1
my foo bar baz         my       1            my foo bar baz         foo        2
my foo bar baz         my       1            my foo bar baz         bar        3
my foo bar baz         my       1            my foo bar baz         baz        4
my foo bar baz         foo      2            my foo bar baz         foo        2
my foo bar baz         foo      2            my foo bar baz         bar        3
my foo bar baz         foo      2            my foo bar baz         baz        4
my foo bar baz         bar      3            my foo bar baz         bar        3
my foo bar baz         bar      3            my foo bar baz         baz        4
my foo bar baz         baz      4            my foo bar baz         baz        4
foo baz whatever bar   foo      5            foo baz whatever bar   foo        5
foo baz whatever bar   foo      5            foo baz whatever bar   baz        6
foo baz whatever bar   foo      5            foo baz whatever bar   whatever   7
foo baz whatever bar   foo      5            foo baz whatever bar   bar        8
foo baz whatever bar   baz      6            foo baz whatever bar   baz        6
...

С оконной функцией array_agg() Я могу объединить второй столбец unnestтаким образом:

array_agg
{my}
{my,foo}
{my,foo,bar}
{my,foo,bar,baz}
{foo}
{foo,bar}
{foo,bar,baz}
{bar}
{bar,baz}
{baz}
{foo}
{foo,baz}
{foo,baz,whatever}
{foo,baz,whatever,bar}
...

Это фильтруется для array length = 3 и преобразовывается в строку.Результатом являются 3 словосочетания:

Последний шаг - проверить все фразы в таблице на наличие любой из трех фраз (и не совпадающих с их исходными фразами)
0 голосов
/ 07 декабря 2018

Составьте таблицу с идентификаторами триграмм и строк, а затем самостоятельно присоединитесь к столбцу триграмм.Тратит много места, но выручает самый простой способ сделать это.С некоторой помощью из ответа Клина на Как извлечь n-граммовые последовательности слов из текста в Postgres :

-- your table
CREATE TABLE phrases (
  id INT,
  phrase TEXT
);

-- your data
INSERT INTO phrases (id, phrase) VALUES
(1, 'my foo bar baz'),
(2, 'foo bar baz whatever'),
(3, 'foo baz whatever bar');

-- function to extract word n-grams
-- from https://stackoverflow.com/a/51571001/240443
CREATE OR REPLACE FUNCTION word_ngrams(str TEXT, n INT)
RETURNS SETOF TEXT LANGUAGE plpgsql AS $$
DECLARE
    i INT;
    arr TEXT[];
BEGIN
    str := regexp_replace(str, '[^[:alnum:]|\s]', '', 'g');
    arr := string_to_array(str, ' ');
    FOR i in 1 .. cardinality(arr) - n + 1 LOOP
        RETURN NEXT array_to_string(arr[i : i+n-1], ' ');
    END LOOP;
END $$;

-- table of all trigrams (my foo bar, foo bar baz, bar baz whatever...)
-- and rows they belong to
CREATE TEMPORARY TABLE trigrams (
  id INT,
  trigram TEXT
);

-- make sure JOIN doesn't take forever
CREATE INDEX ON trigrams (trigram, id);

-- extract the trigrams into their stylish new - yet temporary - home
INSERT INTO trigrams SELECT id, word_ngrams(phrase, 3) FROM phrases;

-- see which original rows have common trigrams
SELECT DISTINCT T1.id AS id1, T2.id AS id2
FROM trigrams T1 JOIN trigrams T2
  ON T1.trigram = T2.trigram
  AND T1 < T2;

-- | id1 | id2
---+-----+----
-- |   1 |   2

Вы также можете использовать функцию word_ngrams напрямую, без временной таблицы, но это будет намного медленнее.Время или пространство, выберите только один: P Это заменяет все в предыдущем фрагменте кода с CREATE TEMPORARY TABLE и далее (но все еще использует замечательную функцию Клина).

SELECT DISTINCT T1.id AS id1, T2.id AS id2
FROM phrases T1 JOIN phrases T2
  ON EXISTS (
    SELECT word_ngrams(T1.phrase, 3)
    INTERSECT
    SELECT word_ngrams(T2.phrase, 3)
  )
  AND T1.id < T2.id;

-- | id1 | id2
---+-----+----
-- |   1 |   2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...