我有一个表,其中包含两个单独列中的单词对。单词的顺序通常很重要,但有时候我只想基于这两个单词进行聚合,而不管顺序如何。是否有一种简单的方法来处理具有相同单词但具有不同顺序(一行与另一行相反)的两行作为相同的"设置"?换句话说,对待:
apple orange orange apple
为:
(apple,orange) (apple,orange)
答案 0 :(得分:7)
目前没有内置方式。
如果您在保存时始终对它们进行规范化,则可以将数组视为集合,始终将它们存储为已排序和重复数据删除。如果PostgreSQL有一个内置的C函数来做这件事会很棒,但事实并非如此。我看了一下写了一个,但C数组API是可怕的,所以即使我写了一堆扩展,我也只是小心翼翼地远离这个。
如果您不介意适度的性能,可以在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[]
,而不是逗号或空格分隔的文本。有以下一些原因,请参阅this question。
你需要注意一些事情,比如数组之间的强制转换比基本类型之间的强制转换更严格。 E.g:
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)
对于像array-contains或array-overlaps这样的操作,这些列是GiST-indexable;请参阅关于数组索引的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)
);
为每个项目的每个集合条目插入一行,而不是让每个项目都有一个数组值列。主键强制执行的唯一约束确保没有项可能具有给定属性的重复项。属性排序无关紧要/未定义。
可以使用EXCEPT
等SQL集合运算符进行比较,也可以使用array_agg(attribute_name ORDER BY attribute_name)
形成一致排序的数组进行比较。
索引仅限于确定给定项目是否具有给定属性。
就我个人而言,我会在这种方法中使用数组。
您还可以使用具有空值的hstore来存储集,因为hstore可以重复删除密钥。 9.4 jsonb
也适用于此。
regress=# create extension hstore;
CREATE EXTENSION
regress=# SELECT hstore('a => 1, b => 1') = hstore('b => 1, a => 1, b => 1');
?column?
----------
t
(1 row)
但它对文本类型才真正有用。 e.g:
regress=# SELECT hstore('"1.0" => 1, "2.0" => 1') = hstore('"1.00" => 1, "1.000" => 1, "2.0" => 1');
?column?
----------
f
(1 row)
我认为这很难看。所以,我再次赞成阵列。
intarray
扩展提供了有用的快速函数,用于将数组作为集处理。它们只适用于整数数组,但它们确实很有用。