PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

PostgreSQL peut-il avoir une contrainte d'unicité sur les éléments du tableau ?

Le droit chemin

Vous voudrez peut-être reconsidérer la normalisation votre schéma. Il n'est pas nécessaire que tout le monde "s'associe même pour la requête la plus simple" . Créer une VIEW pour ça.

Le tableau pourrait ressembler à ceci :

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 clé primaire de substitution hostname_id est facultatif . Je préfère en avoir un. Dans votre cas hostname pourrait être la clé primaire. Mais de nombreuses opérations sont plus rapides avec un simple et petit integer clé. Créer une contrainte de clé étrangère à lier à la table host .
Créez une vue comme celle-ci :

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+

À partir de la page 9.1 , la clé primaire dans le GROUP BY couvre toutes les colonnes de cette table dans le SELECT liste. Les notes de version pour la version 9.1 :

Autoriser non-GROUP BY colonnes dans la liste cible de la requête lorsque la clé primaire est spécifiée dans le GROUP BY clause

Les requêtes peuvent utiliser la vue comme une table. La recherche d'un nom d'hôte sera beaucoup plus vite de cette façon :

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

Dans Postgres 9.2+ un index multicolonne serait encore mieux si vous pouviez obtenir une analyse d'index uniquement en sortir :

CREATE INDEX hn_multi_idx ON hostname (hostname, host_id);

À partir de Postgres 9.3 , vous pouvez utiliser une MATERIALIZED VIEW , si les circonstances le permettent. Surtout si vous lisez beaucoup plus souvent que vous n'écrivez à la table.

Le côté obscur (ce que vous avez réellement demandé)

Si je ne peux pas vous convaincre du droit chemin, j'aiderai aussi du côté obscur. Je suis flexible. :)

Voici une démonstration sur la manière d'appliquer l'unicité des noms d'hôte. J'utilise une table hostname pour collecter les noms d'hôtes et un trigger sur la table host pour le tenir à jour. Les violations uniques déclenchent une exception et annulent l'opération.

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

Fonction déclencheur :

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;

Déclencheur :

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

Violon SQL avec essai.

Utiliser un indice GIN sur la colonne du tableau host.hostnames et opérateurs de tableau pour travailler avec :

  • Pourquoi mon index de tableau PostgreSQL n'est-il pas utilisé (Rails 4) ?
  • Vérifier si l'une des valeurs d'un tableau donné est présente dans un tableau Postgres