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

我试图为当前在LDAP存储中的主机数据提出一个PostgreSQL模式。 部分数据是机器可以拥有的主机名称列表,该属性通常是大多数人用来查找主机logging的关键。

我希望摆脱这种数据到RDBMS的一件事是能够设置主机名列的唯一性约束,以便重复的主机名不能分配。 如果主机只能有一个名字,那么这很容易,但是由于它们可以有多个,所以它更复杂。

我意识到完全规范化的方法是使用外键指向主机表的主机名表,但是我希望避免每个人都需要为最简单的查询进行连接:

select hostnames.name,hosts.* from hostnames,hosts where hostnames.name = 'foobar' and hostnames.host_id = hosts.id; 

我想使用PostgreSQL数组可以为此工作,他们当然使简单的查询简单:

 select * from hosts where names @> '{foobar}'; 

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

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

正义的道路

你可能想重新考虑规范你的模式。 即使是最简单的查询,也没有必要“join”。 为此创build一个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 ); 

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

 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+ 

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

当在GROUP BY子句中指定主键时,允许在查询目标列表中使用非GROUP BY

查询可以像使用表一样使用视图。 search一个主机名会更快这种方式:

 SELECT * FROM host h JOIN hostname hn USING (host_id) WHERE hn.hostname = 'foobar'; 

假设你有一个在host(host_id)上的索引host(host_id) ,应该是这种情况,因为它应该是主键。 另外, hostname(hostname)上的UNIQUE约束自动实现其他所需的索引。

在Postgres 9.2+中 ,多列索引会更好,如果你只能得到一个仅索引的扫描

 CREATE INDEX hn_multi_idx ON hostname (hostname, host_id) 

从Postgres 9.3开始,在情况许可的情况下,您可以使用MATERIALIZED VIEW 。 特别是如果你阅读的次数比写在桌上的次数多得多。

黑暗的一面(你真正问到的)

如果我不能说服你们正义的道路,我也会帮助黑暗的一方。 我很灵活。 🙂

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

 CREATE TABLE host(hostnames text[]); CREATE TABLE hostname(hostname text PRIMARY KEY); -- pk enforces uniqueness 

触发function

 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 = dx; 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; 

触发:

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

SQL小提琴与testing运行。

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

  • 为什么我的PostgreSQL数组索引不被使用(Rails 4)?
  • 检查Postgres数组中是否存在给定数组值

如果有人仍然需要原来的问题:

 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] ); 

这会给你:

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

在Windows上检查Postgres 9.5。

请注意,这将使用运算符&&创build一个索引。 所以当你使用testing表时,由于Postgres的索引内部原因,检查ARRAY[x] && refsx = ANY( refs )要快。

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