是否可以在 PostgreSQL 中设置复合 NOT NULL 约束

ukl*_*kll 7 postgresql constraint

出于日志目的,在我设计的一个表中,我想存储用户登录时的IP信息。

当用户登录时,应存储 IPv4、IPv6 或两个 IP 地址。但我想设置一个不能为 NULL 的约束。

是否可以在带有约束的标准 SQL 中执行此操作,还是应该在 PostgreSQL 或业务层中使用 PL/pgSQL 执行此操作?

joa*_*olo 16

让我们假设您的表的结构是这样的:

CREATE  TABLE log_logins 
(
    user_id INTEGER NOT NULL,
    login_time TIMESTAMP NOT NULL DEFAULT now(),
    ip_v4 TEXT /* or any other representation */,
    ip_v6 TEXT /* or any other representation */,
    PRIMARY KEY (user_id, login_time) 
) ;
Run Code Online (Sandbox Code Playgroud)

您可以添加一个CHECK保证其中之一ip_v4ip_v6不为空的,但不能同时添加一个(对我来说同时拥有 v4 地址和 v6 地址是没有意义的;您通常不会同时使用这两种协议) . 这将通过以下语句完成:

ALTER TABLE log_logins
  ADD CONSTRAINT one_and_only_one_of_ip_v4_or_ip_v6
  CHECK ((ip_v4 IS NULL) <> (ip_v6 IS NULL));
Run Code Online (Sandbox Code Playgroud)

如果同时拥有 v4 和 v6 地址是合理的,则使用的约束将是:

ALTER TABLE log_logins
  ADD CONSTRAINT at_least_one_of_ip_v4_or_ip_v6
  CHECK ((ip_v4 IS NOT NULL) OR (ip_v6 IS NOT NULL));
Run Code Online (Sandbox Code Playgroud)

或者,查看RhodiumToad/ip4r 扩展。如果您使用它,您可以以紧凑而高效的方式表示ip4, ip6or ipaddress(可以包含 IPv4 或 IPv6 地址),并在 IP 地址和 IP 地址范围上拥有一组运算符。我实际上会推荐它。

尽管 PostgreSQL 包含inet可用于存储 Internet 地址(与网络掩码一起)的数据类型,但ip4r如果您需要一个 IP 地址(没有网络掩码),则该扩展提供了一些优势。优点之一是更紧凑的表示,如果要记录的数据量很大,这可能会起作用。如果您需要使用ip ranges. 一个用例是将log_logins表的 IP 列连接到另一个包含(任意)IP 范围及其对应的国家/地区的表。

  • 不需要扩展,Postgres 的 `inet` 数据类型可以存储 ipv4 和 ipv6 地址。 (2认同)

Eva*_*oll 8

是的。这是。只需在表上设置约束即可。

CREATE TEMP TABLE foo (
  userid serial PRIMARY KEY,
  ipv4   inet   CHECK (family(ipv4) = 4),
  ipv6   inet   CHECK (family(ipv6) = 6),
  CHECK (ipv4 IS NOT NULL OR ipv6 IS NOT NULL)
);
Run Code Online (Sandbox Code Playgroud)

或者,您可以只使用inet支持 ipv4 或 ipv6的相同类型 ( ) ..

CREATE TEMP TABLE foo (
  userid   serial PRIMARY KEY,
  userip   inet   NOT NULL 
);
Run Code Online (Sandbox Code Playgroud)