PostgreSQL可以对数组元素有唯一性约束吗?

Lar*_*row 19 arrays postgresql database-design ldap unique-constraint

我正在尝试为当前在LDAP存储中的主机数据提出PostgreSQL架构.部分数据是机器可以拥有的主机名列表,该属性通常是大多数人用来查找主机记录的关键.

我想将这些数据移动到RDBMS的一件事是能够在hostname列上设置唯一性约束,以便无法分配重复的主机名.如果主机只能有一个名称,这将很容易,但由于它们可以有多个名称,因此它更复杂.

我意识到这样做的完全规范化的方法是让一个主机名表的外键指向hosts表,但是我想避免让每个人都需要为最简单的查询做连接:

select hostnames.name,hosts.*
  from hostnames,hosts
 where hostnames.name = 'foobar'
   and hostnames.host_id = hosts.id;
Run Code Online (Sandbox Code Playgroud)

我认为使用PostgreSQL数组可以为此工作,它们肯定使简单的查询变得简单:

select * from hosts where names @> '{foobar}';
Run Code Online (Sandbox Code Playgroud)

但是,当我在hostnames属性上设置唯一性约束时,它当然会将整个名称列表视为唯一值而不是每个名称.有没有办法让每个名称在每一行都是唯一的?

如果没有,有没有人知道另一种更有意义的数据建模方法?

Erw*_*ter 26

正义的道路

您可能想重新考虑规范化架构.每个人都没有必要"加入即使是最简单的查询".VIEW为此创建一个.

表可能如下所示:

CREATE TABLE hostname (
  hostname_id serial PRIMARY KEY
, host_id     int  REFERENCES host(host_id) ON UPDATE CASCADE ON DELETE CASCADE
, hostname    text UNIQUE
);
Run Code Online (Sandbox Code Playgroud)

代理主键hostname_id可选的.我更喜欢有一个.在你的情况下hostname可能是主键.但是,使用简单的小integer密钥,许多操作速度更快.创建外键约束以链接到表host.
创建一个这样的视图:

CREATE VIEW v_host AS
SELECT h.*
     , array_agg(hn.hostname) AS hostnames
--   , string_agg(hn.hostname, ', ') AS hostnames  -- text instead of array
FROM   host h
JOIN   hostname hn USING (host_id)
GROUP  BY h.host_id;   -- works in v9.1+
Run Code Online (Sandbox Code Playgroud)

从第9.1页开始,主键GROUP BY覆盖SELECT列表中该表的所有列.版本9.1发行说明:

GROUP BYGROUP BY子句中指定主键时,允许查询目标列表中的非列

查询可以像表一样使用视图.搜索一个域名将是快是这样的:

SELECT *
FROM   host h
JOIN   hostname hn USING (host_id)
WHERE  hn.hostname = 'foobar';
Run Code Online (Sandbox Code Playgroud)

如果您有索引host(host_id),应该是主键,因为它应该是主键.另外,UNIQUE约束会hostname(hostname)自动实现其他所需的索引.

在Postgres 9.2+中,如果您可以从中获取仅索引扫描,那么多列索引会更好:

CREATE INDEX hn_multi_idx ON hostname (hostname, host_id);
Run Code Online (Sandbox Code Playgroud)

从Postgres 9.3开始,您可以在MATERIALIZED VIEW情况允许的情况下使用.特别是如果你读的频率比你写的那么多.

黑暗的一面(你实际问的是什么)

如果我不能说服你正确的道路,我也会在黑暗的一面帮助你.我很灵活.:)

这是一个如何强制主机名唯一性的演示.我使用表hostname来收集主机名和表上的触发器host以使其保持最新.唯一违规会引发错误并中止操作.

CREATE TABLE host(hostnames text[]);
CREATE TABLE hostname(hostname text PRIMARY KEY);  --  pk enforces uniqueness
Run Code Online (Sandbox Code Playgroud)

触发功能

CREATE OR REPLACE FUNCTION trg_host_insupdelbef()
  RETURNS trigger AS
$func$
BEGIN
-- split UPDATE into DELETE & INSERT
IF TG_OP = 'UPDATE' THEN
   IF OLD.hostnames IS DISTINCT FROM NEW.hostnames THEN  -- keep going
   ELSE RETURN NEW;  -- exit, nothing to do
   END IF;
END IF;

IF TG_OP IN ('DELETE', 'UPDATE') THEN
   DELETE FROM hostname h
   USING  unnest(OLD.hostnames) d(x)
   WHERE  h.hostname = d.x;

   IF TG_OP = 'DELETE' THEN RETURN OLD;  -- exit, we are done
   END IF;
END IF;

-- control only reaches here for INSERT or UPDATE (with actual changes)
INSERT INTO hostname(hostname)
SELECT h
FROM   unnest(NEW.hostnames) h;

RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

触发:

CREATE TRIGGER host_insupdelbef
BEFORE INSERT OR DELETE OR UPDATE OF hostnames ON host
FOR EACH ROW EXECUTE PROCEDURE trg_host_insupdelbef();
Run Code Online (Sandbox Code Playgroud)

SQL小提琴测试运行.

在数组列和数组运算符上使用GIN索引来处理它:host.hostnames

  • 你已经说服了我正确的道路.:)非常感谢非常详细的回复! (2认同)
  • @LarsDamerow:现在我的阴暗面正在受到伤害。也许一个低等的灵魂会被如此自由地提供的黑魔法所诱捕。]:-) (2认同)

vol*_*avl 5

如果有人仍然需要原始问题中的内容:

CREATE TABLE testtable(
    id serial PRIMARY KEY,
    refs integer[],
    EXCLUDE USING gist( refs WITH && )
);

INSERT INTO testtable( refs ) VALUES( ARRAY[100,200] );
INSERT INTO testtable( refs ) VALUES( ARRAY[200,300] );
Run Code Online (Sandbox Code Playgroud)

这会给你:

ERROR:  conflicting key value violates exclusion constraint "testtable_refs_excl"
DETAIL:  Key (refs)=({200,300}) conflicts with existing key (refs)=({100,200}).
Run Code Online (Sandbox Code Playgroud)

在Windows上查看Postgres 9.5.

请注意,这将使用运算符创建索引&&.因此,当您使用时testtable,检查速度会ARRAY[x] && refsx = ANY( refs )Postgres的内部索引更快.

PS一般我同意上面的答案,但这种方法只是一个不错的选择,当你不必真正关心性能和东西.