MySQL最佳实践:匹配前缀

时间:2011-07-05 09:04:10

标签: mysql

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

还有一个辅助范围,我必须限制前缀(这涉及引入其他表)。我不认为这在大多数情况下都很重要,但这是一个简化的(规范化的)方案(我必须设置 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和前缀abc和{{1}开头这是两个中的最大值,因此我们将abc的id设为2,并将其放入abc

(2,2)因为:即使前缀{4}(item.prefix_id)是最喜欢的匹配前缀,它的子组(也就是4)在组2下,但该项在组1下,所以我们可以从子组1,2,3中进行选择,而abcdef仍然是三个可能前缀中的最佳匹配。

(3,1)因为:abc是最热门的匹配。

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

但正如我所说,整个摸索的事情不是问题的必要部分。我主要关注的是将表格与可能的前缀匹配到字符串表中,以及如何以最佳方式进行匹配。 (最好意味着可读性,可维护性和性能之间的最佳权衡 - 因此标题中的“最佳预告”)。

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

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的情况。

感谢大家分享您的想法。

3 个答案:

答案 0 :(得分:2)

  

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

因此,让我们在表前缀中创建列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 的全部好处。

答案 1 :(得分:1)

除非我过度简化,否则应该如此简单...启动内部预查询以获取最长的前缀(无论多个代码的长度是否相同)

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 )))

现在,如果你想对那些有多个具有相同前缀长度的人做一些特别的事情,那么需要进行一些调整,但这应该适合你。

答案 2 :(得分:1)

要尝试更新元素,要重新回答,请尝试以下更新查询。现在这里是关于这个问题......“PreQuery”实际上将返回给定项目的所有匹配前缀...但是,由于订单基于前缀长度,对于那些具有多个匹配“前缀”的条目,它将首先使用最短的前缀进行更新,然后使用下一个较长的前缀命中记录,最后以匹配中最长的那个结束。所以最后,它应该得到你所需要的东西。

如上所述(我现在无法专门测试),如果它只是根据给定ID的FIRST条目进行更新,那么只需按照前缀长度的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