В момента няма вграден начин.
Като масиви
Ако последователно ги нормализирате при записване, можете да третирате масивите като набори, като винаги ги съхранявате сортирани и дедупликирани. Би било чудесно, ако PostgreSQL имаше вградена C функция за това, но не е така. Погледнах да напиша един, но API за C масив е ужасен , така че въпреки че написах куп разширения, просто се отдръпнах внимателно от това.
Ако нямате нищо против умерено неприятната производителност, можете да го направите в 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;
след това обвийте всички записвания в извиквания към array_uniq_sort
или да го наложите със спусък. След това можете просто да сравните вашите масиви за равенство. Можете да избегнете array_uniq_sort
извиква данни от приложението, ако вместо това просто сте извършили сортирането/уникалното от страната на приложението.
Ако направите това моля съхранявайте вашите „набори“ като колони на масив, като text[]
, а не текст, разделен със запетая или интервал. Вижте този въпрос
поради някои от причините.
Трябва да внимавате за няколко неща, като факта, че кастанията между масивите са по-строги от кастингите между базовите им типове. Напр.:
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)
Такива колони са GiST-индексируеми за операции като array-contains или array-overlaps; вижте документацията на PostgreSQL относно индексирането на масиви.
Като нормализирани редове
Другият вариант е просто да съхранявате нормализирани редове с подходящ ключ. Все още бих използвал array_agg
за тяхното сортиране и сравняване, тъй като SQL операциите с множество могат да бъдат тромави за използване за това (особено като се има предвид липсата на XOR / двустранна операция за разлика в набора).
Това обикновено е известно като EAV (entity-attribute-value). Аз самият не съм фен, но има място от време на време. Освен че ще го използвате без value
компонент.
Създавате таблица:
CREATE TABLE item_attributes (
item_id integer references items(id),
attribute_name text,
primary key(item_id, attribute_name)
);
и вмъкнете ред за всеки набор от записи за всеки елемент, вместо всеки елемент да има колона със стойност на масив. Уникалното ограничение, наложено от първичния ключ, гарантира, че никой елемент не може да има дубликати на даден атрибут. Подреждането на атрибутите е неуместно/недефинирано.
Сравненията могат да се правят с оператори за SQL набор като EXCEPT
или чрез array_agg(attribute_name ORDER BY attribute_name)
за формиране на последователно сортирани масиви за сравнение.
Индексирането е ограничено до определяне дали даден елемент има/няма даден атрибут.
Лично аз бих използвал масиви вместо този подход.
hstore
Можете също да използвате hstores с празни стойности за съхраняване на набори, тъй като hstore премахва дублиращи се ключове. jsonb
на 9.4 също ще работи за това.
regress=# create extension hstore;
CREATE EXTENSION
regress=# SELECT hstore('a => 1, b => 1') = hstore('b => 1, a => 1, b => 1');
?column?
----------
t
(1 row)
Това обаче е наистина полезно само за типове текст. напр.:
regress=# SELECT hstore('"1.0" => 1, "2.0" => 1') = hstore('"1.00" => 1, "1.000" => 1, "2.0" => 1');
?column?
----------
f
(1 row)
и мисля, че е грозно. Така че отново бих предпочел масивите.
Само за масиви с цели числа
intarray
разширението предоставя полезни, бързи функции за третиране на масиви като набори. Те са достъпни само за масиви с цели числа, но са наистина полезни.