在数据库中存储规范化电话号码是否有标准?

103

在数据库字段中存储电话号码的好的数据结构是什么?我正在寻找一种足够灵活的方法来处理国际号码,并且允许高效地查询号码的各个部分。

编辑:为了澄清这里的用例:我目前将号码存储在一个单独的varchar字段中,我将它们保留为客户输入的内容。然后,当代码需要该号码时,我对其进行规范化。问题是,如果我想查询几百万行以查找匹配的电话号码,则涉及到一个函数,例如

where dbo.f_normalizenum(num1) = dbo.f_normalizenum(num2)

这种方法非常低效。而且,当只有一个varchar字段时,查询像区号这样的内容变得非常棘手。

[编辑]

这里有很多人提出了很好的建议,谢谢!作为更新,这是我现在正在做的:我仍然将数字存储为它们输入的样子,在一个varchar字段中,但是不再在查询时规范化数据,我有一个触发器,在插入或更新记录时完成所有工作。因此,对于任何需要查询的部分,我有整数或大整数,这些字段已经索引以使查询运行更快。


这个关于编程的问题的现代答案在这里 - https://dev59.com/PHVD5IYBdhLWcg3wQJKT#51761170。它的要点是 - 使用RFC 3966进行存储,使用libphonenumber进行解析/验证。 - Alex Klaus
18个回答

84

首先,除了国家代码外,没有真正的标准。最好的方法是,通过国家代码识别出特定电话号码所属的国家,并根据该国家的格式处理其余部分的号码。

然而,通常情况下,电话设备和相关设施是标准化的,因此您几乎总能将给定的电话号码分解为以下组件:

  • C 国家代码1-10位数字(现在为4位或更少,但可能会发生变化)
  • A 区域代码(省/州/地区)0-10位数字(实际上可能需要单独一个区域字段和一个区域代码字段,而不是一个区域代码)
  • E 交换(前缀或开关)代码0-10位数字
  • L 线路编号1-10位数字

使用此方法,您可以将号码分开,以便找到例如彼此接近的人,因为他们具有相同的国家、区域和交换代码。不过对于手机而言,这已经不再是您可以依赖的东西了。

此外,在每个国家内部都有不同的标准。在美国,您总是可以依赖于 (AAA) EEE-LLLL,但在另一个国家,您可能会在城市中使用 (AAA) EE-LLL 进行交换,在农村地区则只需使用行号 (AAA) LLLL。您将不得不从某种形式的树顶部开始,并按照您拥有的信息进行格式化。例如,国家代码 0 具有已知的其余数字格式,但对于国家代码 5432,您可能需要在了解其余数字之前先检查区号。

您还可能希望处理诸如 (800) Lucky-Guy 的“虚荣”号码,这需要认识到,如果它是美国号码,则数字多了一个(您可能需要完整的表示以进行广告或其他用途),并且在美国,字母与德国不同映射到数字。

您还可以将整个号码分别存储为文本字段(具有国际化),以便稍后返回并重新解析数字,以应对事物的变化,或者作为备份,以防某人提交错误的方法来解析特定国家的格式并丢失信息。


1
你知道有什么好的JavaScript验证方法可以尝试来验证这个吗? - cmcculloh
7
E164对号码长度设置了非常严格的限制:国家编码限长1-3,最大长度为15。考虑到全球电话系统,这种情况短期内不会发生改变。 - Rich
5
并不是每个电话系统都符合ITU-T E.164标准,但绝大多数都符合。在遵守标准和让一些人无法使用之间进行权衡是值得的。需要注意的是,E.164可以被视为上述方案的子集。但我认为最好的格式是尽可能保留用户所输入的内容,并在需要时使用解析算法对其进行分词,而不是将分词形式存储在数据库中。 - Adam Davis
  1. 可以假设所有国际号码都符合C-A-E组件吗?
  2. 您可以假设C组件是唯一根据您所拨打的位置而有所不同的内容吗?例如,美国号码850-555-1234具有A = 850和E = 555-1234,然后如果从美国拨打,则C = 1,如果从英国拨打,则C = 001。重点是无论您从哪里拨打,A和E都不会以任何方式动态变化,对吗?
- AaronLS
@AaronLS 1) 不是的。我见过只有CAL的号码,我想有人可能会有CEL,但对于大多数用途来说,两者之间几乎没有实际区别。2)00是英国的退出代码。1是美国的国家代码。因此,在这两种情况下,C、A和E都是相同的-它们是您要拨打的号码,不会根据您来自哪里而改变。然而,国家退出代码会发生变化-这是您告诉当地电话系统您想要拨打国外电话的方式。C、A、E、L不是动态的。 - Adam Davis
显示剩余3条评论

58

KISS - 我对许多美国网站感到厌倦。它们有一些精巧编写的代码来验证邮政编码和电话号码。当我输入完全有效的挪威联系信息时,我经常发现它被拒绝。

除非您需要更高级的功能,否则将其保留为字符串。


一个好的旧的 nvarchar(42),加上一点验证 /^+?[0-9 -\.\(\)#*]{4,41}$/,非常有效! - SandRock
我同意,但同时又不同意。通常情况下,您希望对存储的电话号码进行某些操作,例如显示它。与其走这条试图解析它以便按照您想要的方式显示它的路线,我宁愿以规范化的方式存储它。现在我并不是说我们应该进一步强制使用区号周围的括号。我的意思是,所有数字都没有破折号等符号。 - The Muffin Man
4
我认为电话号码应该在存储之前进行解析,这样它们可以被验证并以标准化的方式存储。使用googlei18n/libphonenumber可以很好地处理国际电话号码的解析和格式化。 - Roel
关于libphonenumber - 我拒绝为了解析电话号码而将1000多个文件添加到任何项目中。当然,它大部分时间都能正常工作,但是这个项目更适合作为一组模块而不是一个庞大的电话号码工具箱。 - Andy Gee

22

4
不,那个标准只是定义了电话号码的结构(由三个数字组成),但它并没有规定这些号码应如何显示和/或存储。我说标准吗?我是指“建议书”。 - BlueWizard

10

存储

使用RFC 3966(如+1-202-555-0252+1-202-555-7166;ext=22)存储电话号码。与E.164的主要区别是:

  • 长度没有限制
  • 支持扩展

为了优化获取数据的速度,除了 RFC 3966 字段外,还应以国内/国际格式存储电话号码。

除非你有充分的理由要求在 UI 上输入国家代码,否则不要将国家代码存储在单独的字段中。

大多数情况下,人们按照所听到的输入电话号码。例如,如果本地格式以08开头,则实时进行转换会很麻烦(例如,"好的,不要输入'0',选择国家并在此字段中输入此人说的剩余部分")。

解析

Google提供了帮助。他们的libphonenumber库可以验证和解析任何电话号码。几乎可以将其移植到任何语言。

因此,让用户只需输入“0449053501”或“04 4905 3501”或“(04) 4905 3501”,工具会为您解决剩下的问题。

查看官方演示文稿,以了解它有多大的帮助。


8
这是我的建议结构,我希望能得到反馈:
电话数据库字段应该是一个varchar(42),格式如下:
CountryCode - Number x Extension
例如,在美国,我们可以有:
1-2125551234x1234
这代表一个美国号码(国家代码1)与区号/号码(212)555 1234和分机号1234。
用破折号分隔国家代码使得正在浏览数据的人清楚地知道国家代码。这不是严格必要的,因为国家代码是“prefix codes”(您可以从左到右阅读它们,您将始终能够明确地确定国家)。但是,由于国家代码长度不同(目前在1到4个字符之间),您无法轻松地一眼看出国家代码,除非使用某种分隔符。
我使用“x”来区分分机,否则很难(在许多情况下)确定哪个是号码,哪个是分机。
以这种方式,您可以将整个号码(包括国家代码和扩展名)存储在单个数据库字段中,然后可以使用它来加速查询,而不是像迄今为止那样痛苦地连接用户定义的函数。
为什么选择varchar(42)?首先,国际电话号码的长度各不相同,因此需要“var”。我存储了一个破折号和一个“x”,所以解释了“char”,而且无论如何,您不会对电话号码进行整数运算(我猜测),因此尝试使用数字类型没有多大意义。至于长度为42,我根据Adam Davis的答案将所有字段的最大可能长度相加,并添加了2个破折号和“x”的长度。

7
请查阅 E.164。基本上,您将电话号码存储为以国家前缀和可选的 PBX 后缀开头的代码。显示是本地化问题。验证也可以进行,但这也是一个本地化问题(基于国家前缀)。
例如,+12125551212+202 在 en_US 区域设置中的格式为 (212) 555-1212 x202。在 en_GB 或 de_DE 中将有不同的格式。
关于 ITU-T E.164 的信息很多,但它们很难理解。

6

我个人喜欢存储规范化的varchar电话号码(例如9991234567),然后在显示时内联格式化该电话号码。

这样,您数据库中的所有数据都是“干净”的,没有格式问题。


3

好的,根据这个页面上的信息,以下是一个国际电话号码验证器的起点:

function validatePhone(phoneNumber) {
    var valid = true;
    var stripped = phoneNumber.replace(/[\(\)\.\-\ \+\x]/g, '');    

    if(phoneNumber == ""){
        valid = false;
    }else if (isNaN(parseInt(stripped))) {
        valid = false;
    }else if (stripped.length > 40) {
        valid = false;
    }
    return valid;
}

该脚本的灵感部分来自于此页面:http://www.webcheatsheet.com/javascript/form_validation.php


3
也许可以将电话号码的各个部分存储在不同的列中,允许空白或空值条目?

2
数字格式的标准是e.164,您应该始终以此格式存储数字。您不应该在同一字段中存储分机号码和电话号码,它们应该分别存储。至于数字与字母混合的情况,这取决于您将要对数据进行什么操作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接