在PostgreSQL中存储电子邮件地址的最佳方法是什么?


Answers:


38

自定义DOMAIN小号

我认为使用citext(不区分大小写)不够[1]。使用PostgreSQL,我们可以创建一个自定义域,该本质上是对类型的一些定义约束。我们可以在citext类型或之上创建一个域text

使用HTML5 type=email规范

当前,在RFC5322中指定了对什么是电子邮件地址这个问题的最正确答案。该规范非常复杂[2],以至于一切都破坏了它。HTML5包含针对电子邮件的其他规范

此要求是对RFC 5322的故意违反,它定义了同时太严格(在“ @”字符之前),太模糊(在“ @”字符之后)和太松散(允许注释)的电子邮件地址语法。 ,空白字符和引号引起的字符串(大多数用户不熟悉的方式)在此处具有实际用途。[...]以下与JavaScript和Perl兼容的正则表达式是上述定义的实现。

/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/

这可能就是您想要的,如果对HTML5足够好,那么对您也足够好。我们可以在PostgreSQL中直接使用它。我也在citext这里使用(从技术上讲,这意味着您可以通过删除大写或小写字母来在视觉上简单地进行正则表达式)。

CREATE EXTENSION citext;
CREATE DOMAIN email AS citext
  CHECK ( value ~ '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$' );

现在您可以...

SELECT 'asdf@foobar.com'::email;

但不是

SELECT 'asdf@foob,,ar.com'::email;
SELECT 'asd@f@foobar.com'::email;

因为这两个都回来了

ERROR:  value for domain email violates check constraint "email_check"

因为这也是基于citext

SELECT 'asdf@foobar.com'::email = 'ASdf@fooBAR.com';

默认情况下返回true。

使用plperlu/Email::Valid

需要注意的是,有一种更正确的方法,使用会更加复杂plperlu。如果你需要这种级别的正确性你想要citextEmail::Valid甚至可以检查域是否具有MX记录(例如Email :: Valid的文档中的示例)!首先,添加plperlu(需要超级用户)。

CREATE EXTENSION plperlu;

然后创建函数,注意我们标记为IMMUTABLE

CREATE FUNCTION valid_email(text)
  RETURNS boolean
  LANGUAGE plperlu
  IMMUTABLE LEAKPROOF STRICT AS
$$
  use Email::Valid;
  my $email = shift;
  Email::Valid->address($email) or die "Invalid email address: $email\n";
  return 'true';
$$;

然后创建域

CREATE DOMAIN validemail AS text NOT NULL
  CONSTRAINT validemail_check CHECK (valid_email(VALUE));

脚注

  1. 使用citext在技​​术上是错误的。SMTP定义local-part为区分大小写。但是,再次,这是规范愚蠢的情况。它包含自己的身份危机。规范说local-part(之前的部分@)“可能区分大小写”……“必须被视为区分大小写”……但“利用邮箱本地部分的区分大小写会妨碍互操作性,因此不鼓励使用。”
  2. 电子邮件地址的规范非常复杂,甚至都不是独立的。Complex确实是轻描淡写,制定规范的人甚至都不理解。。来自regular-expression.info上的文档

    这些正则表达式均未对整个电子邮件地址或本地部分或域名强加长度限制。RFC 5322没有指定任何长度限制。这些源于其他协议的限制,例如用于实际发送电子邮件的SMTP协议。RFC 1035确实规定域不得超过63个字符,但在其语法规范中不包括该域。原因是真正的常规语言不能强制执行长度限制,并且不能同时禁止连续的连字符。


1
W3.org链接已损坏;这是另一种来源:html.spec.whatwg.org/multipage/…–
MaxGabriel

@MaxGabriel感谢您的支持,您会尽快得到编辑权限,直到我将其固定在那里。
埃文·卡罗尔

在字符类中同时存在a-z和是否有原因A-Z
xehpuk '18年

@xehpuk很好,因为~区分大小写,所以您要么(a)~*不区分大小写,要么(b)在char类中使用大小写字母。
埃文·卡洛尔

citext~似乎是不区分大小写对我来说,这就是为什么我问。
xehpuk '18

46

我总是使用CITEXT电子邮件,因为(实际上)电子邮件地址不区分大小写,即John@Example.com与john@example.com相同。

与文本相比,设置唯一索引以防止重复也更容易:

-- citext
CREATE TABLE address (
   id serial primary key,
   email citext UNIQUE,
   other_stuff json
);

-- text
CREATE TABLE address (
   id serial primary key,
   email text,
   other_stuff json
);
CREATE UNIQUE INDEX ON address ((lower(email)));

比较电子邮件也更容易,并且不易出错:

SELECT * FROM address WHERE email = 'JOHN@example.com';

相比于:

SELECT * FROM address WHERE lower(email) = lower('JOHN@example.com');

CITEXT是在名为“ citext”标准扩展模块中定义的类型,可以通过键入以下内容进行使用:

CREATE EXTENSION citext;

PS textvarcharPostgres中的几乎相同,并且使用它不会受到任何惩罚text。检查此答案:文本和varchar之间的区别


10

我一直使用,varchar(254)因为电子邮件地址不能超过254个字符。

参见https://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address

尽管我确实遇到了一些有用的数据类型,但Postgresql没有电子邮件地址的内置类型。

另外,如果您希望在其上添加唯一键,则可能希望添加触发器或一些此类逻辑来标准化电子邮件地址。

特别是,domain电子邮件地址的一部分(格式为local-part@ domain区分大小写,但local-part必须区分大小写。请参阅http://tools.ietf.org/html/rfc5321#section-2.4

另一个要考虑的问题是,如果您希望以表格形式存储名称和电子邮件地址"Joe Bloggs" <joe.bloggs@hotmail.com>,在这种情况下,您需要的字符串要长于254个字符,并且您将无法有意义地使用唯一约束。我不会这样做,建议分开存储名称和电子邮件地址。在您的表示层中始终可以使用这种格式打印漂亮的地址。


根据4.5.3.1。大小限制和最小值,最大长度为320个字符(包括@)。
Andriy M

1
@AndriyM在引用的部分中没有任何内容表示320。tools.ietf.org/html/rfc5321#section-4.5.3.1.3指出的路径的最大长度为256个字符,并且具有到包括周围“<”和“>”使最大254
科林't Hart'3

根据4.5.3.1.1(“用户名或其他本地部分的最大总长度为64个八位字节”)和4.5.3.1.2(“域名的最大总长度”),我达到最大值320或数字为255个八位字节”)。因此,64 + 255 + 1(the @)=320。也许我误解了它。
Andriy M

3
@AndriyM阅读我链接到的问题的可接受答案。它说明了一切。这绝对是254,而不是320
科林“T哈特

3

您可能对使用检查CONSTRAINT感兴趣(可能更容易,但是可能会拒绝使用超出您期望的值,或者使用FUNCTION,在此处此处进行讨论。基本上,这都是关于特异性和易于实现之间的折衷。)不过,PostgreSQL的,甚至有一个本地IP地址类型,但对pgfoundry的电子邮件数据类型的项目在这里。不过,我发现这个最好是电子邮件。该域比检查约束要好,因为如果您对其进行更改,则只需在域定义中执行一次即可,而不必遵循跟踪父子表的情况来更改所有检查约束。域真的很酷-有点像数据类型,但是实现起来更简单。我在Firebird中使用了它们-Oracle甚至没有它们!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.