字典表上的递归关系

时间:2019-05-20 12:09:37

标签: sql firebird firebird-3.0

我正在研究一个穷人,但是对我们来说可以,在Firebird中仅使用PSQL进行全文搜索。我将着重解决我的问题,以尽可能简化:

总结一下,这是一个字典表:

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的方式使用它,但是我认为这不是理想的选择。

我认为可以使用recursive Common Table Expressions(“ WITH ... AS ... SELECT”)完成此操作,但是我无法执行此操作,因为根据现有示例,它需要一个带有Parent_ID的表,并且不接受输入参数,这不是我的情况。

我的问题是:是否可以使用CTE或其他SQL技巧以递归方式进行此搜索?

4 个答案:

答案 0 :(得分:0)

您可以不使用递归CTE,而是将令牌列表放入表(CRITERIA)中,并与FTS_KEYWORDS上的KEYWORD将该表连接起来,并按{{1}分组},并计算每个ID的关键字数,并应用HAVING子句以仅选择计数等于ID表中的行数的ID值。

答案 1 :(得分:0)

与其提出使用递归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(*)

这将跟踪匹配的令牌(不是关键字!)的数量,并且仅输出匹配的令牌数量等于期望的令牌数量的id。

由于需要使用STARTINGSTARTING WITH),因此跟踪令牌而不是关键字的数量很重要,因为这可能会将多个关键字与单个令牌匹配,而该令牌只能计数一次。

请注意,此解决方案确实假定fts_tokenize仅输出一次令牌,否则您需要将tokens CTE修改为

WITH tokens AS (
    SELECT COUNT(*) OVER () tokencount, token
    FROM (
        SELECT DISTINCT token
        FROM fts_tokenize('FORD 2010 BLACK')
    ) a
),

答案 2 :(得分:0)

您可以通过构建前缀列表来实现。 作为前缀,我使用了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' 

答案 3 :(得分:0)

我认为这是双重否定的简单情况(我将您的问题改写为应该没有不是关键字开头的令牌),不需要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, 设置