【问题标题】:Compare arrays for equality, ignoring order of elements比较数组是否相等,忽略元素的顺序
【发布时间】:2012-10-03 22:05:43
【问题描述】:

我有一个包含 4 个数组列的表。结果如下:

ids       signed_ids   new_ids   new_ids_signed
{1,2,3} | {2,1,3}    | {4,5,6} | {6,5,4}

是否通过忽略元素的顺序来比较 idssigned_ids 以使它们相等?

【问题讨论】:

  • 那些数组不相等。我猜你的真正问题是“我如何比较两个 PostgreSQL 数组,就好像它们是集合一样,即不考虑顺序?”
  • 是的,这正是我要的:)
  • 问题已编辑以反映意图。

标签: arrays postgresql


【解决方案1】:

最简单的做法是对它们进行排序并比较它们的排序。见sorting arrays in PostgreSQL

给定样本数据:

CREATE TABLE aa(ids integer[], signed_ids integer[]);
INSERT INTO aa(ids, signed_ids) VALUES (ARRAY[1,2,3], ARRAY[2,1,3]);

如果数组条目总是整数,最好的办法是使用 intarray 扩展,如Erwin explains in his answer。它比任何纯 SQL 公式都快很多

否则,对于适用于任何数据类型的通用版本,请定义array_sort(anyarray)

CREATE OR REPLACE FUNCTION array_sort(anyarray) RETURNS anyarray AS $$
SELECT array_agg(x order by x) FROM unnest($1) x;
$$ LANGUAGE 'SQL';

并使用它对排序后的数组进行排序和比较:

SELECT array_sort(ids) = array_sort(signed_ids) FROM aa;

有一个重要的警告:

SELECT array_sort( ARRAY[1,2,2,4,4] ) = array_sort( ARRAY[1,2,4] );

将是错误的。这可能是也可能不是您想要的,取决于您的意图。


或者,定义一个函数array_compare_as_set

CREATE OR REPLACE FUNCTION array_compare_as_set(anyarray,anyarray) RETURNS boolean AS $$
SELECT CASE
  WHEN array_dims($1) <> array_dims($2) THEN
    'f'
  WHEN array_length($1,1) <> array_length($2,1) THEN
    'f'
  ELSE
    NOT EXISTS (
        SELECT 1
        FROM unnest($1) a 
        FULL JOIN unnest($2) b ON (a=b) 
        WHERE a IS NULL or b IS NULL
    )
  END
$$ LANGUAGE 'SQL' IMMUTABLE;

然后:

SELECT array_compare_as_set(ids, signed_ids) FROM aa;

这与比较两个array_sorted 值略有不同。 array_compare_as_set 将消除重复,使array_compare_as_set(ARRAY[1,2,3,3],ARRAY[1,2,3]) 为真,而array_sort(ARRAY[1,2,3,3]) = array_sort(ARRAY[1,2,3]) 为假。

这两种方法的性能都很差。考虑确保您始终首先存储已排序的数组。

【讨论】:

  • 为您的表添加别名,非常棒!谢谢:)
  • @user766987 更新了更好的定义
  • @user766987 ... 并用不同的方法再次更新,只是为了好玩。
  • @user766987 哦,我忘了提到 intarray contrib 模块,它会让这一切都快数百倍。
  • 啊,您在答案中添加了intarray。让我的回答有点多余……除了其他方法。
【解决方案2】:

附加模块intarrayinteger 数组提供运算符,这些运算符通常(快得多)。每个数据库安装一次(在 Postgres 9.1 或更高版本中):

CREATE EXTENSION intarray;

那么你可以:

SELECT <b>uniq</b>(<b>sort</b>(ids)) = <b>uniq</b>(<b>sort</b>(signed_ids));

或者:

SELECT ids <b>@&gt;</b> signed_ids AND ids <b>&lt;@</b> signed_ids;

粗体强调来自 intarray 的函数和运算符。
在第二个示例中,如果 left 和 right 参数的类型为 integer[],则运算符解析到达专门的 intarray 运算符。

这两个表达式都将忽略元素的顺序和重复性。进一步阅读有用的手册here

intarray 运算符仅适用于 integer (int4) 的数组,不适用于 bigint (int8) 或 smallint (int2) 或任何其他数据类型。

与默认的通用运算符不同,intarray 运算符不接受数组中的 NULL 值。任何涉及的数组中的 NULL 都会引发异常。如果您需要使用 NULL 值,您可以默认使用标准的通用运算符,方法是使用 OPERATOR 构造对运算符进行模式限定:

SELECT ARRAY[1,4,null,3]::int[] <b>OPERATOR(pg_catalog.@&gt;)</b> ARRAY[3,1]::int[]

通用运算符不能将 indexesintarray 运算符类一起使用,反之亦然。

相关:

【讨论】:

  • 第一个解决方案(使用sort)是否比第二个解决方案(使用@&gt;&lt;@)快?如果没有,我想我可以不用intarray
  • @mattdipasquale:我添加了更多解释。
【解决方案3】:

您可以使用包含的运算符:

(array1 <@ array2 and array1 @> array2)

【讨论】:

  • 作为 postgres 数组的新手,这似乎是最好的答案。与上述相比,它的缺点是什么?
  • 警告!这种比较仅在所有元素都是唯一的情况下才有效:select ARRAY[1,2] &lt;@ ARRAY[1,2,2], ARRAY[1,2] @&gt; ARRAY[1,2,2]; 将在两个条件下返回 true,但数组不匹配(并且您不能只添加长度检查,因为那样您将无法区分[1,2,2][1,1,2]
  • @JasonAxelson 这使它成为array_compare_as_set 的一个很好的实现,您希望它的行为与您描述的完全一样:)
【解决方案4】:

select (string_agg(a,',' order by a) = string_agg(b,',' order by b)) from (select unnest(array[1,2,3,2])::text as a,unnest(array[2,2,3,1])::text as b) A

【讨论】:

    【解决方案5】:

    如果您的数组没有重复项并且具有相同的维度

    • 使用数组包含@&gt;
    • AND array_length,其中长度必须与两边的尺寸相匹配

    【讨论】:

    • @jason-axelson 如第一句所述,我的回答仅适用于没有重复的数组
    猜你喜欢
    • 1970-01-01
    • 2023-03-21
    • 2018-08-16
    • 1970-01-01
    • 1970-01-01
    • 2022-01-25
    • 1970-01-01
    • 1970-01-01
    • 2011-12-27
    相关资源
    最近更新 更多