mysql - 如何提高 MySQL 中 REGEXP 字符串匹配的性能?

标签 mysql regex algorithm sqlperformance

前言:

我对此进行了大量(重新)搜索,并找到了以下 SO 帖子/答案:https://stackoverflow.com/a/5361490/6095216这非常接近我正在寻找的东西。相同的代码,但带有更多有用的注释,出现在这里:http://thenoyes.com/littlenoise/?p=136 .

问题描述:

我需要将 1 列 MySQL TEXT 数据拆分为多列,其中原始数据具有以下格式 (N <= 7):

{"field1":"value1","field2":"value2",...,"fieldN":"valueN"}

您可能猜到了,我只需要提取,将每个值放入一个单独的(预定义的)列中。问题是不能保证所有记录的字段数量和顺序都相同。因此,使用 SUBSTR/LOCATE 等的解决方案不起作用,我需要使用正则表达式。另一个限制是不能使用第 3 方库,例如 LIB_MYSQLUDF_PREG(在我上面第一个链接的答案中建议)。

到目前为止的解决方案/进展:

我修改了上述链接中的代码,使其返回第一个/最短的匹配项,从左到右;否则,返回 NULL。我还对其进行了一些重构,使标识符对读者/维护者更友好 :) 这是我的版本:

CREATE FUNCTION REGEXP_EXTRACT_SHORTEST(string TEXT, exp TEXT)
RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE adjustStart, adjustEnd BOOLEAN DEFAULT TRUE;
    DECLARE startInd INT DEFAULT 1;
    DECLARE endInd, strLen INT;
    DECLARE candidate TEXT;

    IF string NOT REGEXP exp THEN
        RETURN NULL;
    END IF;

    IF LEFT(exp, 1) = '^' THEN
        SET adjustStart = FALSE;
    ELSE
        SET exp = CONCAT('^', exp);
    END IF;
    IF RIGHT(exp, 1) = '$' THEN
        SET adjustEnd = FALSE;
    ELSE
        SET exp = CONCAT(exp, '$');
    END IF;

    SET strLen = LENGTH(string);
    StartIndLoop: WHILE (startInd <= strLen) DO
        IF adjustEnd THEN
            SET endInd = startInd;
        ELSE
            SET endInd = strLen;
        END IF;
        EndIndLoop: WHILE (endInd <= strLen) DO
            SET candidate = SUBSTRING(string FROM startInd FOR (endInd - startInd + 1));
            IF candidate REGEXP exp THEN
                RETURN candidate;
            END IF;
            IF adjustEnd THEN
                SET endInd = endInd + 1;
            ELSE
                LEAVE EndIndLoop;
            END IF;
        END WHILE EndIndLoop;
        IF adjustStart THEN
            SET startInd = startInd + 1;
        ELSE
            LEAVE StartIndLoop;
        END IF;
    END WHILE StartIndLoop;
    RETURN NULL;
END;

然后我添加了一个辅助函数以避免重复正则表达式模式,正如您从上面看到的那样,所有字段都相同。这是那个函数(我留下了我尝试使用 lookbehind - 在 MySQL 中不受支持 - 作为评论):

CREATE FUNCTION GET_MY_FLD_VAL(inputStr TEXT, fldName TEXT)
RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE valPattern TEXT DEFAULT '"[^"]+"'; /* MySQL doesn't support lookaround :( '(?<=^.{1})"[^"]+"'*/
    DECLARE fldNamePat TEXT DEFAULT CONCAT('"', fldName, '":');
    DECLARE discardLen INT UNSIGNED DEFAULT LENGTH(fldNamePat) + 2;
    DECLARE matchResult TEXT DEFAULT REGEXP_EXTRACT_SHORTEST(inputStr, CONCAT(fldNamePat, valPattern));
    RETURN SUBSTRING(matchResult FROM discardLen FOR LENGTH(matchResult) - discardLen);
END;

目前,我尝试做的只是使用上述代码的简单 SELECT 查询。它工作正常,但它。是。 SLOOOOOOOW...最多只有 7 个字段/列可以拆分(并非所有记录都有全部 7 个)!限制为 20 条记录,大约需要 3 分钟 - 我总共有大约 40,000 条记录(对于数据库来说不是很多,对吧?!):)

因此,最后,我们谈到了实际问题:[如何] 可以在性能方面显着改进上述算法/代码(在这一点上几乎是一种蛮力搜索),以便它可以在实际的数据库在合理的时间内?我开始研究主要的已知模式匹配算法,但很快就迷失了方向,试图找出适合这里的算法,这在很大程度上是由于可用选项的数量及其各自的限制、使用条件等。另外,它似乎在 SQL 中实现其中一个只是为了看看它是否有帮助,可能需要做很多工作。

注意:这是我有史以来的第一篇文章(!),所以如果有什么不清楚的地方,请(友好地)告诉我,我会尽力修复它。提前致谢。

最佳答案

我能够按照上面的 tadman 和 Matt Raines 的建议,通过解析 JSON 来解决这个问题。作为 JSON 概念的新手,我根本没有意识到它可以通过这种方式完成……有点尴尬,但吸取了教训!

反正我用的是common_schema框架中的get_option函数:https://code.google.com/archive/p/common-schema/ (通过这篇文章找到,它还演示了如何使用函数:Parse JSON in MySQL)。因此,我的 INSERT 查询运行大约需要 15 分钟,而使用 REGEXP 解决方案则需要 30 多个小时。谢谢,下次见! :)

关于mysql - 如何提高 MySQL 中 REGEXP 字符串匹配的性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37930451/

相关文章:

mysql - SQL更喜欢一条记录而不是另一条记录

java - 如何跟踪正则表达式中的灾难性回溯?

javascript - 查找数组中的一个或多个数字是否可以加起来等于某个数字

mysql - 创建临时表 CONCAT GROUPS 被放置到它们自己的列中?

mysql - 在 MySQL 中将 RGB 转换为 HSL

java - 通过使用自动更新 jtable 的 jtextfield KeyReleased 事件将模式与 Sql 数据库匹配

JavaScript 正则表达式无法获得所有匹配项

java - 用正则表达式分割字符串

javascript - 数字的乘积、总和和数字

c - 每组幂集中的值的乘积