【问题标题】:MySQL best practice: matching prefixesMySQL 最佳实践:匹配前缀
【发布时间】:2011-07-05 09:04:09
【问题描述】:

我有一个带有代码的表格和另一个带有前缀的表格。我需要匹配每个代码的(最长)前缀。

还有一个次要范围,我必须在其中限制前缀(这涉及引入其他表)。我认为在大多数情况下这并不重要,但这是一个简化(规范化)的方案(我必须设置 item.prefix_id):

group (id)
subgroup (id, group_id)
prefix (id, subgroup_id, prefix)
item (id, group_id, code, prefix_id)

可以将前缀的长度缓存在一个新的字段中并对其进行索引。可以将 group_id 缓存在前缀表中(尽管组是相当小的表,但在大多数情况下,我认为不会获得任何性能提升)。 item表包含几十万条记录,前缀最多包含500条。

编辑:

对不起,如果问题没有得到足够的定义。当使用“前缀”这个词时,我的意思是它,所以代码必须以实际前缀开始

subgroup
id   group_id
-------------
1    1
2    1
3    1
4    2

prefix
id   subgroup_id  prefix
------------------------
1    1            a
2    2            abc
3    2            123
4    4            abcdef

item
id   group_id     code    prefix_id
-----------------------------------
1    1            abc123  NULL
2    1            abcdef  NULL
3    1            a123    NULL
4    2            abc123  NULL

前缀列的预期结果是(item.id, item.prefix_id):

(1, 2) 因为:子组 1、2、3 在组 1 下,代码 abc123 以前缀 a 开头,前缀 abcabc 是两者中的 logest , 所以我们把abc 的id 2 放入item.prefix_id.

(2, 2) 因为:尽管前缀 {4}(即abcdef)是最匹配的前缀,但它的子组(即 4)在组 2 下但项目在组 1 下,所以我们可以从子组 1、2、3 中进行选择,而 abc 仍然是三个可能前缀中的对数匹配。

(3, 1) 因为:a 是最匹配的。

(4, NULL) 因为:第 4 项在组 2 下,组 2 下的唯一前缀是 abcdef,与 abc123 不匹配(因为 abc123 不以 abcdef 开头)。

但正如我所说,整个摸索的事情不是问题的重要部分。我主要关心的是将带有可能前缀的表与字符串表相匹配,以及如何以 best 的方式进行匹配。 (最好的意思是可读性、可维护性和性能之间的最佳权衡——因此是标题中的“最佳实践”)。

目前我正在做类似的事情:

UPDATE item USE INDEX (code3)
    LEFT JOIN prefix ON prefix.length=3 AND LEFT(item.code,3)=prefix.prefix
    LEFT JOIN subgroup ON subgroup.id=prefix.subgroup_id
WHERE subgroup.group_id == item.group_id AND
    item.segment_id IS NULL

其中code3KEY code3 (segment_id, group_id, code(3))。 - 以 1、2、3 和 4 为长度重复相同的逻辑。它看起来非常有效,但我不喜欢其中存在重复(单个操作有 4 个查询)。 - 当然,这是在前缀的最大长度为 4 的情况下。

感谢大家到目前为止分享你的想法。

【问题讨论】:

  • 到目前为止您尝试过哪些查询?
  • 两个长度相同的前缀怎么办?
  • @vbence code 的列类型是什么?如果是 varchar,那么 varchar 的长度是多少?还有关于前缀的同样问题。
  • 系统中有两个地方用到了这个逻辑。在一种情况下,它最多为 8 个字符,另一种情况下,它的固定长度为 4。它们是存储在 VARCHAR 字段中的字母数字。没有相同的前缀,因此每个长度最多有一个匹配项。

标签: mysql


【解决方案1】:

可以将group_id缓存在前缀表中。

所以让我们在表prefix中创建列group_id,并用适当的值填充该列。我假设您知道如何执行此操作,所以让我们进行下一步。

我们将从这个复合索引中获得最大的性能优势:

ALTER TABLE `prefix` ADD INDEX `c_index` (
    `group_id` ASC, 
    `prefix` ASC
);

还有 UPDATE 语句:

UPDATE item i
SET 
    prefix_id = (
        SELECT p.id
        FROM prefix p USE INDEX (`c_index`)
        WHERE 
            p.group_id = i.group_id AND 
            p.prefix IN (
                LEFT(i.code, 4), 
                LEFT(i.code, 3), 
                LEFT(i.code, 2), 
                LEFT(i.code, 1)
            )                
        ORDER BY LENGTH(p.prefix) DESC
        LIMIT 1        
    )

在这个例子中,我假设前缀是可变长度 {1,4}。我一起决定使用 IN 子句而不是 LIKE 来获得 c_index 的全部好处。

【讨论】:

  • 我添加了一些示例数据以澄清问题。
  • 我认为您非常接近 vbence 更新查询所需的内容。一个问题。您的查询只是通过组 ID 抓取,而不考虑“Prefix.Prefix = Item.Code”中的匹配文本(即:项目代码必须以与其连接的 Prefix.prefix 相同的值开头......)修复那个,我认为你有需要的东西。
  • @DRapp 是的,但它不是那么简单,因为修改不会让我们使用 ORDER BY 的索引。
  • +1 似乎它完成了工作。只要问题的“最佳实践”部分出现:有没有办法在不硬编码 1、2、3、4 的情况下仍然使用索引? - 在我看来,MySQL 迫切需要一个 STARTSWITH 函数,因为这种问题可以在较低级别上得到最好的解决(读取现有索引有点不同)。
【解决方案2】:

除非我过于简化,否则应该像...启动内部预查询以获取最长前缀(不管每个代码是否具有相同长度)

select
      PreQuery.Code,
      P2.ID,
      P2.SubGroup_ID,
      P2.Prefix 
   From
      ( select
              i.code,
              max( length( trim( p.Prefix ))) as LongestPrefix
           from
              item i
                 join prefix p
                    on i.prefix_id = p.id
           group by
              i.code ) PreQuery
      Join item i2
         on PreQuery.Code = i2.Code
         Join Prefix P2
            on i2.Prefix_ID = P2.ID
            AND PreQuery.LongestPrefix = length( trim( P2.Prefix )))

现在,如果您想对多个具有相同前缀长度的人做一些特别的事情,则需要进行一些调整,但这应该会为您解决。

【讨论】:

  • item.prefix_id 有 NULL 值,任务是设置它的值。
  • @vbence,那么你能提供每个表格的几行来显示你有什么吗???
  • 除 item.prefix_id 之外的每个字段都有正确的值 - 正如我在原始帖子中所说:“我必须设置 item.prefix_id”。我会在早上提供更多信息。
  • 我添加了一些示例数据以澄清问题。
【解决方案3】:

要重新回答,因为您正在尝试更新元素,请尝试以下更新查询。现在这里有一个问题......“PreQuery”实际上将返回给定项目的所有匹配前缀......但是,由于顺序基于前缀长度,因此对于那些具有多个匹配“前缀”的条目,它将首先用最短的前缀更新,然后用下一个较长的前缀命中记录,最后以匹配最长的记录结束。所以最后,它应该能满足你的需求。

话虽如此(我现在不能专门测试),如果它只是根据为给定 ID 找到的第一个条目进行更新,那么只需按照前缀长度的 DESCENDING 顺序进行排序。

    update Item,
           ( SELECT 
                   I.ID, 
                   P.ID Prefix_ID, 
                   P.Prefix, 
                   I.Code, 
                   LENGTH( TRIM( P.Prefix )) as PrefixLen 
                FROM 
                   Item I 
                      JOIN SubGroup SG 
                         ON I.Group_ID = SG.Group_ID 
                            JOIN Prefix P 
                               ON SG.ID = P.SubGroup_ID 
                              AND LEFT( P.Prefix, LENGTH( TRIM( P.Prefix ))) 
                                = LEFT( I.Code, LENGTH( TRIM( P.Prefix ))) 
                ORDER BY 
                   I.ID,
                   LENGTH( TRIM( P.Prefix ))  ) PreQuery
      set 
         Prefix_ID = PreQuery.Prefix_ID
      where 
         ID = PreQuery.ID

【讨论】:

    猜你喜欢
    • 2019-01-11
    • 1970-01-01
    • 2021-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-16
    • 2012-03-17
    • 1970-01-01
    相关资源
    最近更新 更多