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

Ada*_*tan 59 postgresql database-design datatypes

在 PostgreSQL 中存储电子邮件地址的正确数据类型是什么?

我可以使用varchar(甚至text),但我想知道电子​​邮件是否有更具体的数据类型。

Eva*_*oll 55

自定义DOMAIN小号

我不认为使用citext(case-insensitive) 就足够了[1]。使用 PostgreSQL,我们可以创建一个自定义域,它本质上是对类型的一些定义的约束。我们可以创建一个域,例如在citext类型上,或者在text.

使用 HTML5type=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])?)*$/
Run Code Online (Sandbox Code Playgroud)

这很可能是您想要的,如果它对 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])?)*$' );
Run Code Online (Sandbox Code Playgroud)

现在你可以做...

SELECT 'asdf@foobar.com'::email;
Run Code Online (Sandbox Code Playgroud)

但不是

SELECT 'asdf@foob,,ar.com'::email;
SELECT 'asd@f@foobar.com'::email;
Run Code Online (Sandbox Code Playgroud)

因为这两个都回来了

ERROR:  value for domain email violates check constraint "email_check"
Run Code Online (Sandbox Code Playgroud)

因为这也是基于 citext

SELECT 'asdf@foobar.com'::email = 'ASdf@fooBAR.com';
Run Code Online (Sandbox Code Playgroud)

默认返回真。

使用plperlu/Email::Valid

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

CREATE EXTENSION plperlu;
Run Code Online (Sandbox Code Playgroud)

然后创建函数,注意我们将 at 标记为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';
$$;
Run Code Online (Sandbox Code Playgroud)

然后创建域

CREATE DOMAIN validemail AS text NOT NULL
  CONSTRAINT validemail_check CHECK (valid_email(VALUE));
Run Code Online (Sandbox Code Playgroud)

脚注

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

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

  • regex 方法不接受带有非 ascii 字符的电子邮件,而且这些正变得越来越普遍。 (2认同)

heg*_*mon 52

我总是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)));
Run Code Online (Sandbox Code Playgroud)

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

SELECT * FROM address WHERE email = 'JOHN@example.com';
Run Code Online (Sandbox Code Playgroud)

相比于:

SELECT * FROM address WHERE lower(email) = lower('JOHN@example.com');
Run Code Online (Sandbox Code Playgroud)

CITEXT是在名为 "citext"标准扩展模块中定义的类型,可通过键入:

CREATE EXTENSION citext;
Run Code Online (Sandbox Code Playgroud)

PStextvarcharPostgres 中几乎相同,并且text按预期使用不会受到任何惩罚。检查这个答案:Difference between text and varchar


Col*_*art 11

我始终使用varchar(254)的电子邮件地址不得超过 254 个字符。

/sf/ask/27040611/

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

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

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

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

  • @AndriyM 阅读我链接到的问题的已接受答案。它解释了这一切。肯定是 254,而不是 320。 (3认同)

Vér*_*ace 5

您可能有兴趣使用 a CHECK CONSTRAINT(可能更容易,但可能会拒绝比您想要的更多的内容,或者您​​使用 FUNCTION,在此处此处讨论)。您可以使用带有约束的正则表达式。

你可以这样做:

CREATE TABLE person 
(
  name TEXT,
  address1 TEXT,
  ...
  ...  other fields...
  ...
  email TEXT
  ...    
  CONSTRAINT 
    proper_email CHECK (email ~* '^[A-Za-z0-9._+%-]+@[A-Za-z0-9.-]+[.][A-Za-z]+$')
);
Run Code Online (Sandbox Code Playgroud)

运算~*符匹配时不区分大小写。regex( 1 ) 可以像你喜欢的那样复杂 - 请参阅这个权威答案,其中包含对 6,509 个字符长的明确正则表达式的引用!

基本上,这都是关于特异性和易于实施之间的权衡。不过有趣的话题。PostgreSQL 甚至有一个本地 IP 地址类型,但是 pgfoundry上有一个用于电子邮件数据类型的项目。

然而,我发现最好的是电子邮件。域比检查约束更好,因为如果您更改它,您只需在域定义中执行一次,而不必沿着父子表的轨迹更改所有检查约束。

域真的很酷 - 有点像数据类型,但更容易实现。我在 Firebird 中使用了它们 - Oracle 甚至没有它们!

1)无耻地从这里举出的例子