我正在研究一个穷人,但是对我们来说可以,在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技巧以递归方式进行此搜索?
答案 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。
由于需要使用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
),
答案 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, 设置