如何提高 MySQL 中 REGEXP 字符串匹配的性能?
How to improve performance for REGEXP string matching in MySQL?
前言:
我对此做了很多(重新)搜索,并找到了以下 SO post/answer: which was pretty close to what I'm looking for. The same code, but with somewhat more helpful comments, appears here: http://thenoyes.com/littlenoise/?p=136 .
问题描述:
我需要将 MySQL TEXT 数据的 1 列拆分为多列,其中原始数据的格式为 (N <= 7):
{"field1":"value1","field2":"value2",...,"fieldN":"valueN"}
您可能猜到了,我只需要提取 值 ,将每个值放入一个单独的(预定义)列中。问题是不能保证所有记录的字段数量和顺序都相同。因此,使用 SUBSTR/LOCATE 等的解决方案不起作用,我需要使用正则表达式。另一个限制是无法使用第 3 方库,例如 LIB_MYSQLUDF_PREG(在我上面第一个 link 的答案中建议)。
Solution/Progress 到目前为止:
我修改了上面 link 的代码,使其 returns 和 first/shortest 从左到右匹配;否则,返回 NULL。我还对其进行了一些重构,使标识符更多 reader/maintainer-friendly :)
这是我的版本:
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;
然后我添加了一个辅助函数以避免重复正则表达式模式,正如您从上面看到的那样,所有字段都相同。这是该功能(我尝试使用后视 - 在 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 个 fields/columns 可以拆分,最大值(并非所有记录都有全部 7 个)!限制为 20 条记录,大约需要 3 分钟 - 我总共有大约 40,000 条记录(对于数据库来说不是很多,对吧?!):)
所以,最后,我们得到了实际的问题:[如何]可以显着提高上面的 algorithm/code(此时几乎是一个蛮力搜索)的性能,这样它就可以运行 在合理的时间内处理实际数据库?我开始研究主要的已知模式匹配算法,但很快就迷失了方向,试图找出适合这里的算法,这在很大程度上是由于可用选项的数量及其各自的限制、使用条件等。另外,它似乎在 SQL 中实现其中之一只是为了看看它是否有帮助,可能需要做很多工作。
注意:这是我的第一个 post(!),所以如果有什么不清楚的地方,请(友好地)告诉我,我会尽力解决。提前致谢。
我能够按照上面的 tadman 和 Matt Raines 的建议,通过解析 JSON 来解决这个问题。作为 JSON 概念的新手,我根本没有意识到可以用这种方式完成...有点尴尬,但吸取了教训!
总之,我使用了common_schema框架中的get_option函数:https://code.google.com/archive/p/common-schema/ (found through this post, which also demonstrates how to use the function: Parse JSON in MySQL)。结果,我的 INSERT 查询花费了大约 15 分钟到 运行,而使用 REGEXP 解决方案则需要 30 多个小时。谢谢,下次见! :)
不要在SQL中这样做;使用 PHP 或其他具有用于解析 JSON.
的内置工具的语言来完成
前言:
我对此做了很多(重新)搜索,并找到了以下 SO post/answer: which was pretty close to what I'm looking for. The same code, but with somewhat more helpful comments, appears here: http://thenoyes.com/littlenoise/?p=136 .
问题描述:
我需要将 MySQL TEXT 数据的 1 列拆分为多列,其中原始数据的格式为 (N <= 7):
{"field1":"value1","field2":"value2",...,"fieldN":"valueN"}
您可能猜到了,我只需要提取 值 ,将每个值放入一个单独的(预定义)列中。问题是不能保证所有记录的字段数量和顺序都相同。因此,使用 SUBSTR/LOCATE 等的解决方案不起作用,我需要使用正则表达式。另一个限制是无法使用第 3 方库,例如 LIB_MYSQLUDF_PREG(在我上面第一个 link 的答案中建议)。
Solution/Progress 到目前为止:
我修改了上面 link 的代码,使其 returns 和 first/shortest 从左到右匹配;否则,返回 NULL。我还对其进行了一些重构,使标识符更多 reader/maintainer-friendly :) 这是我的版本:
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;
然后我添加了一个辅助函数以避免重复正则表达式模式,正如您从上面看到的那样,所有字段都相同。这是该功能(我尝试使用后视 - 在 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 个 fields/columns 可以拆分,最大值(并非所有记录都有全部 7 个)!限制为 20 条记录,大约需要 3 分钟 - 我总共有大约 40,000 条记录(对于数据库来说不是很多,对吧?!):)
所以,最后,我们得到了实际的问题:[如何]可以显着提高上面的 algorithm/code(此时几乎是一个蛮力搜索)的性能,这样它就可以运行 在合理的时间内处理实际数据库?我开始研究主要的已知模式匹配算法,但很快就迷失了方向,试图找出适合这里的算法,这在很大程度上是由于可用选项的数量及其各自的限制、使用条件等。另外,它似乎在 SQL 中实现其中之一只是为了看看它是否有帮助,可能需要做很多工作。
注意:这是我的第一个 post(!),所以如果有什么不清楚的地方,请(友好地)告诉我,我会尽力解决。提前致谢。
我能够按照上面的 tadman 和 Matt Raines 的建议,通过解析 JSON 来解决这个问题。作为 JSON 概念的新手,我根本没有意识到可以用这种方式完成...有点尴尬,但吸取了教训!
总之,我使用了common_schema框架中的get_option函数:https://code.google.com/archive/p/common-schema/ (found through this post, which also demonstrates how to use the function: Parse JSON in MySQL)。结果,我的 INSERT 查询花费了大约 15 分钟到 运行,而使用 REGEXP 解决方案则需要 30 多个小时。谢谢,下次见! :)
不要在SQL中这样做;使用 PHP 或其他具有用于解析 JSON.
的内置工具的语言来完成