【发布时间】:2016-06-20 19:34:58
【问题描述】:
前言:
我对此进行了大量(重新)搜索,并找到了以下 SO 帖子/答案:https://stackoverflow.com/a/5361490/6095216,这与我正在寻找的内容非常接近。相同的代码,但更有用的 cmets 出现在此处:http://thenoyes.com/littlenoise/?p=136。
问题描述:
我需要将 1 列 MySQL TEXT 数据拆分为多列,其中原始数据具有这种格式(N
{"field1":"value1","field2":"value2",...,"fieldN":"valueN"}
正如您可能猜到的,我只需要提取值,将每个值放入单独的(预定义)列中。问题是字段的数量和顺序不能保证对于所有记录都是相同的。因此,使用 SUBSTR/LOCATE 等的解决方案不起作用,我需要使用正则表达式。另一个限制是不能使用 3rd 方库,例如 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 中实现其中一个只是为了看看它是否有帮助,可能需要做很多工作。
注意:这是我的第一篇文章(!),所以如果有什么不清楚的地方,请告诉我(很好地),我会尽力解决它。提前致谢。
【问题讨论】:
-
你正在打一场你在这里无法获胜的战斗。正则表达式总是很慢,它们不能被索引。如果这是一个 JSON 列,并且您还不太喜欢 MySQL,那么 Postgres 对 JSON 和对内容的深度索引的 fantastic 支持。
-
或升级到 5.7.8 或更高版本以获得 MySQL 中的原生 JSON 支持 dev.mysql.com/doc/refman/5.7/en/json.html
-
@tadman 是的,但即使使用正则表达式,我也很确定我的解决方案可以改进。例如,我使用了来自loggly.com/blog/… 的建议并将 * 量词更改为 +,这使我的查询运行速度快了近 3 倍(忘了在我的问题中提及)。我的下一个想法是尝试使用lookbehind,这样它就不必每次都从字符串的开头开始查找(是的,我知道我说过不能保证字段顺序......) - 但 MySQL 没有不支持。
-
@tadman 如果建议 Postgres(作为拆分数据的临时占位符,然后将其放回 MySQL),我会给你 +1,但我认为我的声誉还不够高.虽然这似乎是一种非常激进的方法:)
-
我对 Postgres + JSON 有过一些非常好的体验,尤其是新优化的
JSONB列,它提供了改进的性能和更紧凑的存储。此处的索引和查询功能可与 MongoDB 等文档存储相媲美。
标签: mysql regex algorithm sqlperformance