PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

PostgreSQL può avere un vincolo di unicità sugli elementi dell'array?

Il retto sentiero

Potresti voler riconsiderare la normalizzazione il tuo schema Non è necessario che tutti "partecipino anche alla domanda più semplice" . Crea un VIEW per quello.

La tabella potrebbe assomigliare a questa:

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 chiave primaria surrogata hostname_id è opzionale . Preferisco averne uno. Nel tuo caso hostname potrebbe essere la chiave primaria Ma molte operazioni sono più veloci con un semplice integer chiave. Crea un vincolo di chiave esterna da collegare alla tabella host .
Crea una vista come questa:

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+

A partire da pagina 9.1 , la chiave primaria nel GROUP BY copre tutte le colonne di quella tabella in SELECT elenco. Le note di rilascio per la versione 9.1:

Consenti non GROUP BY colonne nell'elenco di destinazione della query quando la chiave primaria è specificata in GROUP BY clausola

Le query possono utilizzare la vista come una tabella. La ricerca di un nome host sarà molto più veloce in questo modo:

SELECT *
FROM   host h
JOIN   hostname hn USING (host_id)
WHERE  hn.hostname = 'foobar';

In Postgres 9.2+ un indice multicolonna sarebbe ancora meglio se puoi ottenere una scansione solo indice fuori di esso:

CREATE INDEX hn_multi_idx ON hostname (hostname, host_id);

A partire da Postgres 9.3 , potresti usare una MATERIALIZED VIEW , circostanze permettendo. Soprattutto se leggi molto più spesso di quanto scrivi al tavolo.

Il lato oscuro (quello che hai effettivamente chiesto)

Se non riesco a convincerti della retta via, assisterò anche sul lato oscuro. Sono flessibile. :)

Ecco una demo su come rafforzare l'unicità dei nomi host. Uso una tabella hostname per raccogliere nomi host e un trigger sulla tabella host per tenerlo aggiornato. Violazioni uniche generano un'eccezione e interrompono l'operazione.

CREATE TABLE host(hostnames text[]);
CREATE TABLE hostname(hostname text PRIMARY KEY);  --  pk enforces uniqueness

Funzione di attivazione:

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;

Attivazione:

CREATE TRIGGER host_insupdelbef
BEFORE INSERT OR DELETE OR UPDATE OF hostnames ON host
FOR EACH ROW EXECUTE PROCEDURE trg_host_insupdelbef();

SQL Fiddle con prova.

Utilizza un indice GIN nella colonna dell'array host.hostnames e operatori di array per lavorarci:

  • Perché il mio indice di matrice PostgreSQL non viene utilizzato (Rails 4)?
  • Controlla se uno qualsiasi di un dato array di valori è presente in un array Postgres