sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Puede PostgreSQL tener una restricción de unicidad en los elementos de la matriz?

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