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] && refs
比x = ANY( refs )
要快。
PS一般来说,我同意上面的答案,但这种方法只是一个不错的select,当你不必真正关心性能和东西。