Рекурсивные отношения в таблице словаря - PullRequest
2 голосов
/ 20 мая 2019

Я работаю над плохим, но хорошо для нас , полнотекстовый поиск с использованием только PSQL в Firebird. Я постараюсь максимально упростить, сосредоточившись на моей проблеме:

Подводя итог, это словарь таблицы:

SELECT * FROM FTS_KEYWORDS

 ID | KEYWORD
----+-----------
  1 | 'FORD'
  1 | 'MUSTANG'
  1 | '2010'
  2 | 'FORD'
  2 | 'FUSION'
  2 | 'TURBO'
  2 | '2010'
  3 | 'FORD'
  3 | 'RANGER'
  3 | 'TURBO'
  3 | '2010'
  3 | 'BLACK'

Существует слишком FTS_TOKENIZE() процедура для получения слов из целых строк


Случай 1: поиск пользователя по 1 ключевому слову

SELECT TOKENS FROM FTS_TOKENIZE('FORD')

 TOKENS
-------------
  'FORD'

Тогда это будет SQL, необходимый для получения правильных результатов:

:TOKEN_1 = 'FORD'

SELECT DISTINCT ID
FROM FTS_KEYWORDS
WHERE (KEYWORD STARTING :TOKEN_1)

 ID 
-----
  1
  2 
  3 

Случай 2: поиск пользователя по 3 ключевым словам

SELECT TOKENS FROM FTS_TOKENIZE('FORD 2010 BLACK')

 TOKENS
-------------
 'FORD'
 '2010'
 'BLACK'

Итак, SQL для получения правильных значений:

:TOKEN_1 = 'FORD'
:TOKEN_2 = '2010'
:TOKEN_3 = 'BLACK'

SELECT DISTINCT K1.ID
FROM FTS_KEYWORDS K1
WHERE (K1.KEYWORD STARTING :TOKEN_1)
  AND (K1.ID IN (SELECT DISTINCT K2.ID
                 FROM FTS_KEYWORDS K2
                 WHERE (K2.KEYWORD STARTING :TOKEN_2)))
                   AND (K2.ID IN (SELECT DISTINCT K3.ID
                                  FROM FTS_KEYWORDS K3
                                  WHERE (K3.KEYWORD STARTING :TOKEN_3)))

 ID 
-----
  3 

ID 3 является единственным ID, в котором все ключевые слова соответствуют запросу.

SQL для получения значений является рекурсивным, вложенным поисковым запросом по количеству токенов.

В настоящее время в процедуре FTS_SEARCH() я строю строку SQL и затем использую EXECUTE STATEMENT, но я не думаю, что это идеально.

Я думаю, что это можно сделать с помощью рекурсивных выражений общих таблиц («WITH ... AS ... SELECT»), но я не смог этого сделать, поскольку, основываясь на текущих примерах доступно, требует таблицу с Parent_ID и не принимает входные параметры, что не в моем случае.

У меня вопрос: есть ли способ сделать этот поиск рекурсивным способом, используя CTE или другой трюк SQL?

Ответы [ 4 ]

0 голосов
/ 23 мая 2019

Я думаю, что это простой случай двойного отрицания (я перефразирую ваш вопрос о том, что не должно быть токена, который не является началом ключевого слова), нет необходимости в cte:

SELECT DISTINCT K.ID
FROM FTS_TOKENIZE ('FORD 2010 BLACK') FT
JOIN FTS_KEYWORDS K ON K.KEYWORD STARTING FT.TOKENS
WHERE NOT EXISTS(SELECT *
                 FROM FTS_TOKENIZE('FORD 2010 BLACK') FT2
                 WHERE NOT EXISTS(SELECT *
                                  FROM FTS_KEYWORDS K2
                                  WHERE K2.KEYWORD STARTING FT2.TOKENS
                                    AND K.ID = K2.ID))

HTH, Set

0 голосов
/ 21 мая 2019

Вместо того, чтобы прибегать к использованию рекурсивного CTE (и я не знаю, действительно ли использование рекурсивного CTE решит вашу проблему, и не будет ли оно выполнено), я предлагаю следующее решение:

WITH tokens AS (
    SELECT COUNT(*) OVER () tokencount, token 
    FROM fts_tokenize('FORD 2010 BLACK')
)
SELECT id
FROM (
    SELECT DISTINCT tokencount, token, id
    FROM tokens t
    INNER JOIN fts_keywords k
        ON k.KEYWORD STARTING WITH t.token
)
GROUP BY id
HAVING MAX(tokencount) = count(*)

Это будет отслеживать количество совпавших токенов (не ключевых слов!) И выводить только те идентификаторы, в которых количество совпавших токенов равно количеству ожидаемых токенов.

Отслеживание количества токенов, а не ключевых слов, важно, поскольку вам необходимо использовать STARTING (STARTING WITH), поскольку это может сопоставить несколько ключевых слов с одним токеном, который должен учитываться только один раз.

Имейте в виду, что это решение предполагает, что fts_tokenize будет выводить токен только один раз, в противном случае вам придется изменить tokens CTE на

WITH tokens AS (
    SELECT COUNT(*) OVER () tokencount, token
    FROM (
        SELECT DISTINCT token
        FROM fts_tokenize('FORD 2010 BLACK')
    ) a
),
0 голосов
/ 23 мая 2019

Вы можете сделать это, создав список префиксов.В качестве префикса я использовал ASCII_CHAR(5)

SELECT 
  K.ID, COUNT(*) 
FROM FTS_KEYWORDS K
WHERE
  (SELECT ASCII_CHAR(5) || LIST(T.TOKEN, ASCII_CHAR(5)) || ASCII_CHAR(5) FROM FTS_TOKENIZE('FORD 2010 BLACK') T)
  LIKE '%' || ASCII_CHAR(5) || K.KEYWORD || ASCII_CHAR(5) || '%'
GROUP BY K.ID
HAVING COUNT(*)=(SELECT COUNT(*) FROM FTS_TOKENIZE('FORD 2010 BLACK') TX)

, это должно быть быстрее (более низкие выборки), но вы должны проверить это в своей среде.

Вы также можете ускорить это, удалив FTS_TOKENIZE вообще, и вместо 'FORD 2010 BLACK' вы просто делаете

SELECT 
  K.ID, COUNT(*) 
FROM FTS_KEYWORDS K
WHERE
  ASCII_CHAR(5) || 'FORD' || ASCII_CHAR(5) || '2010' || ASCII_CHAR(5) || 'BLACK' || ASCII_CHAR(5) 
  LIKE '%' || ASCII_CHAR(5) || K.KEYWORD || ASCII_CHAR(5) || '%'
GROUP BY K.ID
HAVING COUNT(*)=3

, но я не знаю вашего реального случая, особенно как эта строкасборка для передачи в FTS_TOKENIZE

UPDATE1 Не ответ на ваш вопрос, но вы можете оптимизировать свой текущий запрос с помощью:

SELECT
    DISTINCT K1.ID
FROM
    FTS_KEYWORDS K1
    INNER JOIN FTS_KEYWORDS K2 ON K2.ID = K1.ID AND K2.KEYWORD STARTING 'FORD'
    INNER JOIN FTS_KEYWORDS K3 ON K3.ID = K2.ID AND K3.KEYWORD STARTING '2010'
WHERE
    K1.KEYWORD STARTING 'BLACK' 
0 голосов
/ 20 мая 2019

Вместо использования рекурсивного CTE вы можете поместить свой список токенов в таблицу (CRITERIA), объединить эту таблицу с FTS_KEYWORDS на KEYWORD, сгруппировать по ID и сосчитать количество ключевых слов наID и примените предложение HAVING, чтобы выбрать только те значения ID с числом, равным количеству строк в таблице CRITERIA.

...