MariaDB 如何统计两个等长字符串中位置相同的字符?
数据小陈
结论
逐个比较两个字符串相同位置上的字符,再用相等字符的数量除以字符串长度,就能算出匹配比例。keyboard 和 ekyboard 有 6 个位置的字符相同,因此匹配比例为 6 / 8 = 75%。
对这个需求,按位置逐字符比较即可。下面的递归 CTE 示例适用于支持递归 CTE 的 MariaDB 版本:
WITH RECURSIVE positions (i) AS (
SELECT 1
UNION ALL
SELECT i + 1
FROM positions
WHERE i < CHAR_LENGTH('keyboard')
)
SELECT
SUM(
BINARY SUBSTRING('keyboard', i, 1)
= BINARY SUBSTRING('ekyboard', i, 1)
) AS matching_chars,
100.0 * SUM(
BINARY SUBSTRING('keyboard', i, 1)
= BINARY SUBSTRING('ekyboard', i, 1)
) / CHAR_LENGTH('keyboard') AS matching_percent
FROM positions;
结果是 matching_chars = 6,matching_percent = 75.0000。CHAR_LENGTH 按字符计算长度。BINARY 会按字符本身进行比较,避免不区分大小写的排序规则将大小写不同的字符视为相等。
用于正则匹配结果
问题中的 r_str 是由字符类组成的正则表达式。找到匹配文本的起始位置后,可以取出与 str 等长的片段,再逐位比较。下面是结构示意,表名和字段名需要替换为实际名称。这里假设 search_terms.id 和 addresses.id 是唯一键:
WITH RECURSIVE
candidate AS (
SELECT
a.id AS address_id,
t.id AS term_id,
a.noun,
t.str,
REGEXP_INSTR(a.noun, t.r_str) AS loc
FROM addresses AS a
JOIN search_terms AS t
ON a.noun RLIKE t.r_str
),
walk (address_id, term_id, noun, str, loc, i, matched) AS (
SELECT address_id, term_id, noun, str, loc, 1, 0
FROM candidate
WHERE loc > 0
AND CHAR_LENGTH(str) > 0
UNION ALL
SELECT
address_id,
term_id,
noun,
str,
loc,
i + 1,
matched + (
BINARY SUBSTRING(str, i, 1)
= BINARY SUBSTRING(noun, loc + i - 1, 1)
)
FROM walk
WHERE i <= CHAR_LENGTH(str)
)
SELECT
address_id,
term_id,
noun,
str,
loc,
matched AS matching_chars,
100.0 * matched / CHAR_LENGTH(str) AS percentage
FROM walk
WHERE i = CHAR_LENGTH(str) + 1;
matched 表示正则命中的片段与原字符串在相同位置上相等的字符数。可以将最终结果与 priority=1 的精确匹配结果合并,再按优先级、匹配比例和位置排序,例如使用 ORDER BY priority ASC, percentage DESC, loc ASC。
注意事项
- 这个计算要求正则匹配片段的长度与
str的字符长度一致。如果正则可能匹配不同长度的文本,应先确定取哪段文本以及如何对齐。 - 空字符串没有可计算的百分比,示例会将其排除。
REGEXP_INSTR返回0表示没有匹配,不能将其作为有效起始位置。 - 查询中的
GROUP BY noun同时选择了多个未聚合字段。启用ONLY_FULL_GROUP_BY时,查询可能报错;关闭该选项时,非分组字段的取值也可能不符合预期。应先确定每个noun要保留哪条结果,再进行分组或排序。
备注:内容仅供参考。
