在 SQL 中获取满足最左条件的第一条记录

Getting first record that satisfies lef-most condition in SQL

我的主要目标是在列中搜索特定值(比如 word)。如果不存在,想找到第一个匹配 word% or wor% or wo%w%.

在 "English" 中,查询内容如下:"look for 'word' and return it if it exists. If not, look for the first word that has the maximum same prefix as 'word'".

我会写

SELECT word FROM words WHERE word = 'word' or word LIKE 'word%' or ... LIMIT 1;

我试图按字母顺序排序,但行不通(wo comes before wor)。另外,不能倒序,否则'wordy'会排在'word'之前。

我目前的想法是调用数据库n次,其中n = length(word)。但是我想知道 SQL 中是否有任何类型的 'short-circuit OR' -- MySQL/MariaDB,准确地说是

例子

数据库有'w'、'word'、'wording',想按'word'搜索,只检索'word'。

数据库有'z'、'zab'、'zac'、'ze'、'zeb'想按'za'搜索得到'zab'

尝试以下查询:

SELECT word 
  FROM words
 WHERE word LIKE 'w%'
ORDER BY word;

听起来您正在寻找 string distance algorithm。字符串距离算法告诉你需要多少变化才能将当前词变成想要的词。这个想法是你给所有的单词一个字符串距离,并根据距离升序排序。完全匹配将为 0,缺少或额外的字母将为 1。

不完全是您问题的答案,但我希望这正是您要找的。您可能也对 word stems 感兴趣,这将与此相得益彰。

编辑

将我的答案扩展为针对您的实际查询的解决方案。 添加功能:

CREATE FUNCTION `WORDRANK`(`a` VARCHAR(150), `b` VARCHAR(150)) RETURNS INT
BEGIN
    DECLARE rank INT DEFAULT 0;

    WHILE rank < LENGTH(a) DO
        IF rank = 0 AND b = a THEN RETURN rank;
        ELSEIF rank = 0 AND b LIKE CONCAT(a, "%") THEN RETURN rank + 1;
        ELSEIF b LIKE CONCAT(LEFT(a, LENGTH(a) - rank), "%") THEN RETURN rank + 2;
        END IF;
        SET rank = rank + 1;
    END WHILE;

    RETURN rank + 100;
END

然后创建一个存储过程:

CREATE PROCEDURE `getClosestMatch`(IN `q` VARCHAR(150))
BEGIN
    SELECT
        word
    FROM words
    WHERE word LIKE CONCAT(LEFT(q, 1),"%")
    ORDER BY WORDRANK(q, word), word
    LIMIT 1;
END

为了获得所需的结果,我们需要根据您在 WORDRANK 函数中定义的所需算法对每个单词进行排名。存储过程让我们有一种通用的方式来执行查询。

假设:单词是 ASCII,最小长度 = 1,最大值 < 'ZZ'。 假设:VARCHAR 输入,没有尾随空格。 假设:如果 'word' 不存在,但存在 'wording' 和 'wordy', 你想要 'wording' 而不是 'wordy'。 也许不简单,但只有一个 SELECT 语句 ...

set @x = 'w'; /* or whatever word you want to search with */
 select * from words
 where word <= concat(@x,'zz')
 and (word like concat(@x,'%') or @x like concat(word,'%'))
 order by length(word) <> length(@x),
 case when length(word) = length(@x) then 1 else 0 end asc,
 case when length(word) > length(@x) then word else 'zz' end asc,
 word desc limit 1;