PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Kann PostgreSQL eine Eindeutigkeitsbeschränkung für Array-Elemente haben?

Der rechtschaffene Weg

Vielleicht möchten Sie die Normalisierung noch einmal überdenken Ihr Schema. Es ist nicht notwendig, dass jeder "selbst bei der einfachsten Frage mitmacht" . Erstellen Sie eine VIEW dafür.

Die Tabelle könnte so aussehen:

CREATE TABLE hostname (
  hostname_id serial PRIMARY KEY
, host_id     int  REFERENCES host(host_id) ON UPDATE CASCADE ON DELETE CASCADE
, hostname    text UNIQUE
);

Der Ersatz-Primärschlüssel hostname_id ist optional . Ich habe lieber einen. In Ihrem Fall hostname könnte der Primärschlüssel sein. Aber viele Operationen sind mit einer einfachen, kleinen integer schneller Schlüssel. Erstellen Sie eine Fremdschlüsseleinschränkung, um mit der Tabelle host zu verknüpfen .
Erstellen Sie eine Ansicht wie diese:

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+

Beginnend mit Seite 9.1 , der Primärschlüssel in GROUP BY deckt alle Spalten dieser Tabelle im SELECT ab aufführen. Die Versionshinweise für Version 9.1:

Nicht-GROUP BY zulassen Spalten in der Abfragezielliste, wenn der Primärschlüssel in GROUP BY angegeben ist Klausel

Abfragen können die Ansicht wie eine Tabelle verwenden. Die Suche nach einem Hostnamen wird viel sein schneller so:

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

In Postgres 9.2+ Ein mehrspaltiger Index wäre sogar noch besser, wenn Sie einen Nur-Index-Scan erhalten könnten daraus:

CREATE INDEX hn_multi_idx ON hostname (hostname, host_id);

Ab Postgres 9.3 , könnten Sie eine MATERIALIZED VIEW verwenden , wenn es die Umstände zulassen. Vor allem, wenn Sie viel öfter lesen als schreiben.

Die dunkle Seite (was Sie eigentlich gefragt haben)

Wenn ich dich nicht vom rechtschaffenen Weg überzeugen kann, helfe ich auch auf der dunklen Seite. Ich bin flexibel. :)

Hier ist eine Demo, wie man die Eindeutigkeit von Hostnamen erzwingt. Ich verwende eine Tabelle hostname zum Sammeln von Hostnamen und einen Trigger auf die Tabelle host um es aktuell zu halten. Eindeutige Verstöße lösen eine Ausnahme aus und brechen die Operation ab.

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

Triggerfunktion:

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;

Auslöser:

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

SQL-Geige mit Probelauf.

Verwenden Sie einen GIN-Index in der Array-Spalte host.hostnames und Array-Operatoren um damit zu arbeiten:

  • Warum wird mein PostgreSQL-Array-Index nicht verwendet (Rails 4)?
  • Überprüfen Sie, ob ein bestimmtes Array von Werten in einem Postgres-Array vorhanden ist