正确的路径
您可能需要重新考虑对模式进行规范化。并非每个人都需要“即使对于最简单的查询也要加入”。可以为此创建一个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
键可以使许多操作更快。创建一个外键约束来链接到表 host
。
创建如下视图:
CREATE VIEW v_host AS
SELECT h.*
, array_agg(hn.hostname) AS hostnames
FROM host h
JOIN hostname hn USING (host_id)
GROUP BY h.host_id;
从pg9.1开始,GROUP BY
中的主键会覆盖在SELECT
列表中该表的所有列。版本9.1的发布说明:
当在GROUP BY
子句中指定了主键时,查询目标列表可以使用非GROUP BY
列。
查询可以像表一样使用视图。这种方法搜索主机名将会更加快速:
SELECT *
FROM host h
JOIN hostname hn USING (host_id)
WHERE hn.hostname = 'foobar';
如果你在host(host_id)
上有索引,这应该是主键,那么唯一性约束hostname(hostname)
会自动实现所需的其他索引。
在Postgres9.2+中,如果可以使用仅索引扫描,多列索引将更好:
CREATE INDEX hn_multi_idx ON hostname (hostname, host_id);
从Postgres 9.3开始,您可以使用MATERIALIZED VIEW
,如果情况允许的话。尤其是当您更频繁读取表格时。
黑暗面(您实际要求的内容)
如果我无法说服您选择正确的方向,这里为黑暗面提供一些帮助:
以下是如何强制执行主机名唯一性的演示。我使用一个hostname
表来收集主机名,并在host
表上使用触发器以使其保持最新状态。唯一性冲突将引发异常并中止操作。
CREATE TABLE host(hostnames text[]);
CREATE TABLE hostname(hostname text PRIMARY KEY);
触发函数:
CREATE OR REPLACE FUNCTION trg_host_insupdelbef()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
IF TG_OP = 'UPDATE' THEN
IF OLD.hostnames IS DISTINCT FROM NEW.hostnames THEN
ELSE
RETURN NEW;
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;
END IF;
END IF;
INSERT INTO hostname(hostname)
SELECT h
FROM unnest(NEW.hostnames) h;
RETURN NEW;
END
$func$;
触发器:
CREATE TRIGGER host_insupdelbef
BEFORE INSERT OR DELETE OR UPDATE OF hostnames ON host
FOR EACH ROW EXECUTE FUNCTION trg_host_insupdelbef();
SQL Fiddle 提供了测试运行。
在数组列 host.hostnames
上使用GIN索引和数组操作符进行操作: