Den retfærdige vej
Du vil måske genoverveje normalisering dit skema. Det er ikke nødvendigt for alle at "deltage for selv den enkleste forespørgsel" . Opret en VIEW
for det.
Tabellen kunne se sådan ud:
CREATE TABLE hostname (
hostname_id serial PRIMARY KEY
, host_id int REFERENCES host(host_id) ON UPDATE CASCADE ON DELETE CASCADE
, hostname text UNIQUE
);
Den primære surrogatnøgle hostname_id
er valgfri . Jeg foretrækker at have en. I dit tilfælde hostname
kunne være den primære nøgle. Men mange operationer er hurtigere med et simpelt, lille integer
nøgle. Opret en fremmednøglebegrænsning for at linke til tabellen host
.
Opret en visning som denne:
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+
Starter med side 9.1 , den primære nøgle i GROUP BY
dækker alle kolonner i den tabel i SELECT
liste. Udgivelsesbemærkningerne til version 9.1:
Tillad ikke-GROUP BY
kolonner i forespørgselsmållisten, når primærnøglen er angivet i GROUP BY
klausul
Forespørgsler kan bruge visningen som en tabel. At søge efter et værtsnavn vil være meget hurtigere på denne måde:
SELECT *
FROM host h
JOIN hostname hn USING (host_id)
WHERE hn.hostname = 'foobar';
I Postgres 9.2+ et indeks med flere kolonner ville være endnu bedre, hvis du kan få en kun-indeksscanning ud af det:
CREATE INDEX hn_multi_idx ON hostname (hostname, host_id);
Starter med Postgres 9.3 , kan du bruge en MATERIALIZED VIEW
, hvis omstændighederne tillader det. Især hvis du læser meget oftere, end du skriver til bordet.
Den mørke side (det du faktisk spurgte om)
Hvis jeg ikke kan overbevise dig om den retfærdige vej, vil jeg også hjælpe på den mørke side. Jeg er fleksibel. :)
Her er en demo, hvordan man håndhæver unikke værtsnavne. Jeg bruger en tabel hostname
at indsamle værtsnavne og en trigger på tabellen host
for at holde det opdateret. Unikke overtrædelser rejser en undtagelse og afbryde operationen.
CREATE TABLE host(hostnames text[]);
CREATE TABLE hostname(hostname text PRIMARY KEY); -- pk enforces uniqueness
Udløserfunktion:
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;
Udløser:
CREATE TRIGGER host_insupdelbef
BEFORE INSERT OR DELETE OR UPDATE OF hostnames ON host
FOR EACH ROW EXECUTE PROCEDURE trg_host_insupdelbef();
SQL Fiddle med testkørsel.
Brug et GIN-indeks i array-kolonnen host.hostnames
og array-operatorer at arbejde med det:
- Hvorfor bliver mit PostgreSQL-array-indeks ikke brugt (Rails 4)?
- Tjek, om nogen af en given matrix af værdier er til stede i en Postgres-array