问题 表示关系数据库中的数字范围(MySQL)


我试图了解是否有任何标准的最佳实践方法来建模关系数据库中的数字范围(在这种情况下是MySQL),如果这实际上是一件明智的事情。

我将解释引发上下文问题的任务。

我目前正在设计一个数据库,该数据库将为客户分配标识符池。

潜在标识符池的范围为0到大约2 ^ 30

可以为给定客户分配从单个标识符到多个连续块中的数百万个的任意数量的标识符。

给定的标识符只能分配给单个客户(即,它是一对多的关系)

显然,将有一个Customer表和一个包含Customer键的Identifier表。

复杂性来自于如何为标识符建模:

选项一是使用一行代表单个标识符。这将导致表中可能存在大量行,但会搜索谁拥有哪个标识符以及给定标识符是否在使用中是微不足道的。

第二个(我认为更有前途的)选项是让一行代表一系列具有最小值和最大值的值。这会使查询更复杂(我假设用于检查标识符是否正在使用的查询将查询“最小值低于X”和“最大值高于X”的范围)但是会导致远行数更少,可能更容易管理和更新。

我欢迎任何关于这是否是一个好方法的意见,如果没有,如果有一个明显更好的方法我错过了。


8297
2018-05-03 11:40


起源



答案:


如果范围不相交,则可以将它们存储为成对 INT 值:

CREATE TABLE customer_range
        (
        customerId INT,
        rgStart INT,
        rgEnd INT,
        PRIMARY KEY (customerId, rgStart),
        UNIQUE KEY (rgStart)
        )

要查询号码所属的客户,请使用以下命令:

SELECT  customerId
FROM    customer_range
WHERE   rgStart <= $mynum
        AND rgEnd >= $mynum
ORDER BY
        rgStart DESC
LIMIT 1

7
2018-05-03 11:50



这也是我建议的方法。如果您索引这些字段,MySQL将更容易找到相关的客户记录。 - James C
伟大的看起来我正在考虑的方法并非完全愚蠢。我一直在想我必须在某个地方找到一个我想念的地方。 - Nick Long
@Nick:你应该检查范围在插入时是否相交。可能会有一些警告,尤其是 InnoDB。 - Quassnoi
问题是SQL(标准SQL,即没有)对区间类型没有本机支持,因此也不支持Allen的运算符。所以你必须拼出“I1begin <= I2end和I1end> = I2begin”,而不仅仅是“I1重叠I2”。表达式很快就会变得过于繁琐,更不用说在阅读/试图理解它们时进行解码。但答案确实指出了最合适的方法。 - Erwin Smout
正如Quassnoi的评论指出的那样,另一个问题是,如果您的间隔(/范围)是(时间(/范围)键的一部分),您可以忘记参照完整性支持。 - Erwin Smout


答案:


如果范围不相交,则可以将它们存储为成对 INT 值:

CREATE TABLE customer_range
        (
        customerId INT,
        rgStart INT,
        rgEnd INT,
        PRIMARY KEY (customerId, rgStart),
        UNIQUE KEY (rgStart)
        )

要查询号码所属的客户,请使用以下命令:

SELECT  customerId
FROM    customer_range
WHERE   rgStart <= $mynum
        AND rgEnd >= $mynum
ORDER BY
        rgStart DESC
LIMIT 1

7
2018-05-03 11:50



这也是我建议的方法。如果您索引这些字段,MySQL将更容易找到相关的客户记录。 - James C
伟大的看起来我正在考虑的方法并非完全愚蠢。我一直在想我必须在某个地方找到一个我想念的地方。 - Nick Long
@Nick:你应该检查范围在插入时是否相交。可能会有一些警告,尤其是 InnoDB。 - Quassnoi
问题是SQL(标准SQL,即没有)对区间类型没有本机支持,因此也不支持Allen的运算符。所以你必须拼出“I1begin <= I2end和I1end> = I2begin”,而不仅仅是“I1重叠I2”。表达式很快就会变得过于繁琐,更不用说在阅读/试图理解它们时进行解码。但答案确实指出了最合适的方法。 - Erwin Smout
正如Quassnoi的评论指出的那样,另一个问题是,如果您的间隔(/范围)是(时间(/范围)键的一部分),您可以忘记参照完整性支持。 - Erwin Smout


如果我理解你正确你需要使用多个范围,这可能会变得棘手。您可能想要查看PostgreSQL 9.2范围类型。它们看起来与您要做的事情相关。

在现实世界中,范围可以重叠,相互包含或不重叠,并且它们可以是开放的或封闭的,使得范围检查查询可能复杂且容易出错。范围类型消除了大部分这种复杂性,并且通过索引本机支持它们。

https://wiki.postgresql.org/images/7/73/Range-types-pgopen-2012.pdf

最好的祝愿,

缺口


2
2018-04-02 05:56





通常情况下,我不会仅仅为了它而尝试减少行数 - 原则上,只要您的查询命中,具有十亿行的索引良好的表应该与具有100行的表一样快指数。

我会更多地研究您可能想要运行的实际查询,并在此基础上设计解决方案。例如,您是否要列出属于单个客户的所有ID?您想检查哪个客户拥有多个ID吗?您想要找到客户拥有的ID数量吗? 如果你有“范围”表,后者有点棘手 - 而不是做 "select count(*) from ranges where customer = 1",您必须为客户计算每个范围内的IP数量,然后将它们相加。不是火箭科学,但在现实世界中可能会更慢......


1
2018-05-03 11:58



这是我最感兴趣的事情之一。使用这两种方法,哪种类型的查询会很慢或过于复杂。不幸的是,可能针对它运行的类型查询仍然定义得很差。 - Nick Long
我认为 SELECT SUM(end - start) 在三个范围内,每个1M数字代替 SELECT COUNT(*) 3M记录在现实世界中会更快。 - Quassnoi
“原则上,只要您的查询达到索引,具有十亿行的索引良好的表应该与具有100行的表一样快。” - 所以插入十亿行就像插入100行一样快“如果表格索引良好并且插入点击索引”? - Erwin Smout
@Erwin - 你是对的,我应该澄清我的意思是“查询”。但是,原则上,无论是插入包含100条记录的表还是包含10亿条记录的表,插入的每条记录的时间都不应发生重大变化。 - Neville Kuyt
一个 B-Tree 插入需要 O(log(n)) 只在寻求,而不是关于缓存,页面拆分等。 - Quassnoi


如果你做一个像这样的桌子

表ids

id_start not null unsigned integer /*not autoincrement!*/
id_end not null unsigned integer 
customer_id unsigned integer not null
foreign key FK_customer (customer_id) REFERENCES customer.id
primary key (id_start, id_end)
key id_end (id_end)

现在您可以通过执行操作来检查免费密钥

SELECT count(*) as occupied FROM ids
WHERE 100 between id_start and id_end;

要检查自由范围吗

SELECT count(*) as occupied FROM ids
WHERE NOT ('$low' > id_end) AND NOT ('$high' < id_start)

0
2018-05-03 11:57





一种可能性是使用正则表达式来表示标识符池,根据需要在字符串和数字之间进行转换。这里的问题是为给定的标识符列表找到正则表达式。这可以使用Aho-Corasick算法自动完成。只有当这些ID池看起来大致相同时,这才是实用的。显然,如果它们是随机分配的,那么很难找到比一长列ORd文字更好的正则表达式。


0
2017-10-03 17:02