V tuto chvíli neexistuje žádný vestavěný způsob.
Jako pole
Pokud je při ukládání důsledně normalizujete, můžete s poli zacházet jako s množinami, a to tak, že je vždy uložíte setříděná a deduplikovaná. Bylo by skvělé, kdyby měl PostgreSQL vestavěnou funkci C, která to dělá, ale nemá. Podíval jsem se na psaní, ale C array API je příšerné , takže i když jsem napsal spoustu rozšíření, od tohoto jsem opatrně ustoupil.
Pokud vám nevadí středně špatný výkon, můžete to udělat v SQL:
CREATE OR REPLACE FUNCTION array_uniq_sort(anyarray) RETURNS anyarray AS $$
SELECT array_agg(DISTINCT f ORDER BY f) FROM unnest($1) f;
$$ LANGUAGE sql IMMUTABLE;
pak zabalte všechna uložení do volání array_uniq_sort
nebo to vynutit spouštěčem. Pak můžete jen porovnat vaše pole pro rovnost. Můžete se vyhnout array_uniq_sort
vyžaduje data z aplikace, pokud jste místo toho provedli řazení/jedinečnost na straně aplikace.
Pokud to uděláte, prosím uložte své "sady" jako sloupce pole, například text[]
, nikoli text oddělený čárkou nebo mezerou. Viz tuto otázku
z některých důvodů.
Musíte si dát pozor na několik věcí, jako je skutečnost, že přetypování mezi poli je přísnější než přetypování mezi jejich základními typy. Např.:
regress=> SELECT 'a' = 'a'::varchar, 'b' = 'b'::varchar;
?column? | ?column?
----------+----------
t | t
(1 row)
regress=> SELECT ARRAY['a','b'] = ARRAY['a','b']::varchar[];
ERROR: operator does not exist: text[] = character varying[]
LINE 1: SELECT ARRAY['a','b'] = ARRAY['a','b']::varchar[];
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
regress=> SELECT ARRAY['a','b']::varchar[] = ARRAY['a','b']::varchar[];
?column?
----------
t
(1 row)
Takové sloupce lze indexovat GiST pro operace, jako jsou pole-obsahuje nebo pole-překrývání; viz dokumentaci PostgreSQL o indexování polí.
Jako normalizované řádky
Druhou možností je pouze uložit normalizované řádky pomocí vhodného klíče. Stále bych použil array_agg
pro jejich třídění a porovnávání, protože operace množin SQL mohou být k tomuto účelu neohrabané (zejména vzhledem k tomu, že neexistuje operace XOR / oboustranný rozdíl množin).
To je obecně známé jako EAV (entity-attribute-value). Sám nejsem fanoušek, ale občas má své místo. Až na to, že byste jej používali bez value
komponenta.
Vytvoříte tabulku:
CREATE TABLE item_attributes (
item_id integer references items(id),
attribute_name text,
primary key(item_id, attribute_name)
);
a vložte řádek pro každou položku sady pro každou položku, místo aby každá položka měla sloupec s hodnotou pole. Jedinečné omezení vynucené primárním klíčem zajišťuje, že žádná položka nesmí mít duplikáty daného atributu. Pořadí atributů je irelevantní/nedefinované.
Porovnání lze provést pomocí operátorů sady SQL, jako je EXCEPT
, nebo pomocí array_agg(attribute_name ORDER BY attribute_name)
vytvořit konzistentně seřazená pole pro porovnání.
Indexování je omezeno na určení, zda daná položka má/nemá daný atribut.
Osobně bych na tento přístup použil pole.
hstore
K ukládání sad můžete také použít hstores s prázdnými hodnotami, protože hstore deduplikuje klíče. 9.4 jsonb
bude fungovat i pro toto.
regress=# create extension hstore;
CREATE EXTENSION
regress=# SELECT hstore('a => 1, b => 1') = hstore('b => 1, a => 1, b => 1');
?column?
----------
t
(1 row)
Je to však užitečné pouze pro textové typy. např.:
regress=# SELECT hstore('"1.0" => 1, "2.0" => 1') = hstore('"1.00" => 1, "1.000" => 1, "2.0" => 1');
?column?
----------
f
(1 row)
a podle mě je to ošklivé. Znovu bych tedy upřednostnil pole.
Pouze pro celočíselná pole
intarray
rozšíření poskytuje užitečné a rychlé funkce pro zacházení s poli jako s množinami. Jsou k dispozici pouze pro celočíselná pole, ale jsou opravdu užitečné.