El camino recto
Es posible que desee reconsiderar normalizar tu esquema No es necesario que todos "se unan incluso para la consulta más simple" . Crear una VIEW
por eso.
La tabla podría verse así:
CREATE TABLE hostname (
hostname_id serial PRIMARY KEY
, host_id int REFERENCES host(host_id) ON UPDATE CASCADE ON DELETE CASCADE
, hostname text UNIQUE
);
La clave principal sustituta hostname_id
es opcional . Prefiero tener uno. En tu caso hostname
podría ser la clave principal. Pero muchas operaciones son más rápidas con un integer
simple y pequeño llave. Cree una restricción de clave externa para vincular a la tabla host
.
Cree una vista como esta:
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+
Comenzando con pg 9.1 , la clave principal en el GROUP BY
cubre todas las columnas de esa tabla en el SELECT
lista. Las notas de la versión 9.1:
Permitir no GROUP BY
columnas en la lista de objetivos de consulta cuando la clave principal se especifica en GROUP BY
cláusula
Las consultas pueden usar la vista como una tabla. Buscar un nombre de host será mucho más rápido de esta manera:
SELECT *
FROM host h
JOIN hostname hn USING (host_id)
WHERE hn.hostname = 'foobar';
En Postgres 9.2+ un índice de varias columnas sería aún mejor si puede obtener un escaneo de solo índice fuera de eso:
CREATE INDEX hn_multi_idx ON hostname (hostname, host_id);
A partir de Postgres 9.3 , podría usar una MATERIALIZED VIEW
, si las circunstancias lo permiten. Especialmente si lees mucho más a menudo de lo que escribes en la mesa.
El lado oscuro (lo que realmente preguntaste)
Si no puedo convencerte del camino correcto, también te ayudaré en el lado oscuro. Soy flexible. :)
Aquí hay una demostración de cómo hacer cumplir la unicidad de los nombres de host. Uso una tabla hostname
para recopilar nombres de host y un activador en la tabla host
para mantenerlo actualizado. Las infracciones únicas generan una excepción y anulan la operación.
CREATE TABLE host(hostnames text[]);
CREATE TABLE hostname(hostname text PRIMARY KEY); -- pk enforces uniqueness
Función de disparo:
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;
Activador:
CREATE TRIGGER host_insupdelbef
BEFORE INSERT OR DELETE OR UPDATE OF hostnames ON host
FOR EACH ROW EXECUTE PROCEDURE trg_host_insupdelbef();
Violín SQL con ejecución de prueba.
Usa un índice GIN en la columna de la matriz host.hostnames
y operadores de matriz para trabajar con él:
- ¿Por qué no se usa mi índice de matriz de PostgreSQL (Rails 4)?
- Comprueba si alguno de los valores de una matriz determinada está presente en una matriz de Postgres