Праведный путь
Возможно, вы захотите пересмотреть , нормализуя вашу схему. Не обязательно для всех «присоединяться даже к самому простому запросу» . Для этого создайте 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
-- , 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+
Начиная с pg 9.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)
, который должен иметь место, поскольку это должен быть первичный ключ. Кроме того, ограничение UNIQUE
для hostname(hostname)
автоматически реализует другой необходимый индекс.
В Postgres 9.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); -- pk enforces uniqueness
Функция триггера:
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;
Trigger:
CREATE TRIGGER host_insupdelbef
BEFORE INSERT OR DELETE OR UPDATE OF hostnames ON host
FOR EACH ROW EXECUTE PROCEDURE trg_host_insupdelbef();
SQL Fiddle с тестовым прогоном.
Используйте индекс GIN для столбца массива host.hostnames
и операторы массива для работы с ним: