【问题标题】:Postgres UNIQUE CONSTRAINT for arrayPostgres UNIQUE CONSTRAINT 用于数组
【发布时间】:2012-01-16 15:22:39
【问题描述】:

如何创建对数组中所有值的唯一性的约束,如:

CREATE TABLE mytable
(
    interface integer[2],
    CONSTRAINT link_check UNIQUE (sort(interface))
)

我的排序功能

create or replace function sort(anyarray)
returns anyarray as $$
select array(select $1[i] from generate_series(array_lower($1,1),
array_upper($1,1)) g(i) order by 1)
$$ language sql strict immutable; 

我需要将值 {10, 22} 和 {22, 10} 视为相同并在 UNIQUE CONSTRAINT 下检查

【问题讨论】:

    标签: arrays postgresql unique-constraint


    【解决方案1】:

    我认为您不能将函数与unique constraint 一起使用,但您可以与unique index 一起使用。所以给定一个类似这样的排序函数:

    create function sort_array(anyarray) returns anyarray as $$
        select array_agg(distinct n order by n) from unnest($1) as t(n);
    $$ language sql immutable;
    

    那么你可以这样做:

    create table mytable (
        interface integer[2] 
    );
    create unique index mytable_uniq on mytable (sort_array(interface));
    

    然后发生以下情况:

    => insert into mytable (interface) values (array[11,23]);
    INSERT 0 1
    => insert into mytable (interface) values (array[11,23]);
    ERROR:  duplicate key value violates unique constraint "mytable_uniq"
    DETAIL:  Key (sort_array(interface))=({11,23}) already exists.
    => insert into mytable (interface) values (array[23,11]);
    ERROR:  duplicate key value violates unique constraint "mytable_uniq"
    DETAIL:  Key (sort_array(interface))=({11,23}) already exists.
    => insert into mytable (interface) values (array[42,11]);
    INSERT 0 1
    

    【讨论】:

      【解决方案2】:

      @mu 已经演示了index on an expression 如何解决您的问题。

      我的注意力被使用过的功能所吸引。对于两个整数的数组来说,两者似乎都是多余的。这可能是对真实情况的简化。 (?)

      无论如何,我很感兴趣并用几个变体进行了测试。

      测试设置

      -- temporary table with 10000 random pairs of integer
      CREATE TEMP TABLE arr (i int[]);
      
      INSERT INTO arr 
      SELECT ARRAY[(random() * 1000)::int, (random() * 1000)::int]
      FROM   generate_series(1,10000);
      

      测试候选人用简短的评论来解释每个人:

      -- 1) mu's query
      CREATE OR REPLACE FUNCTION sort_array1(integer[])  RETURNS int[] AS
      $$
          SELECT array_agg(n) FROM (SELECT n FROM unnest($1) AS t(n) ORDER BY n) AS a;
      $$ LANGUAGE sql STRICT IMMUTABLE;
      
      -- 2) simplified with ORDER BY inside aggregate (pg 9.0+)
      CREATE OR REPLACE FUNCTION sort_array2(int[])  RETURNS int[] AS
      $$
      SELECT array_agg(n ORDER BY n) FROM unnest($1) AS t(n);
      $$ LANGUAGE sql STRICT IMMUTABLE;
      
      
      -- 3) uralbash's query
      CREATE OR REPLACE FUNCTION sort_array3(anyarray)  RETURNS anyarray AS
      $$
      SELECT ARRAY(
          SELECT $1[i]
          FROM   generate_series(array_lower($1,1), array_upper($1,1)) g(i)
          ORDER  BY 1)
      $$ LANGUAGE sql STRICT IMMUTABLE;
      
      -- 4) change parameters to int[]
      CREATE OR REPLACE FUNCTION sort_array4(int[])  RETURNS int[] AS
      $$
      SELECT ARRAY(
          SELECT $1[i]
          FROM   generate_series(array_lower($1,1), array_upper($1,1)) g(i)
          ORDER  BY 1)
      $$ LANGUAGE sql STRICT IMMUTABLE;
      
      -- 5) simplify array_lower() - it's always 1
      CREATE OR REPLACE FUNCTION sort_array5(int[])  RETURNS int[] AS
      $$
      SELECT ARRAY(
          SELECT $1[i]
          FROM   generate_series(1, array_upper($1,1)) g(i)
          ORDER  BY 1)
      $$ LANGUAGE sql STRICT IMMUTABLE;
      
      -- 6) further simplify to case with 2 elements
      CREATE OR REPLACE FUNCTION sort_array6(int[])  RETURNS int[] AS
      $$
      SELECT ARRAY(
          SELECT i
          FROM  (VALUES ($1[1]),($1[2])) g(i)
          ORDER  BY 1)
      $$ LANGUAGE sql STRICT IMMUTABLE;
      
      
      -- 7) my radically simple query
      CREATE OR REPLACE FUNCTION sort_array7(int[])  RETURNS int[] AS
      $$
      SELECT CASE WHEN $1[1] > $1[2] THEN ARRAY[$1[2], $1[1]] ELSE $1 END;
      $$ LANGUAGE sql STRICT IMMUTABLE;
      
      -- 8) without STRICT modifier
      CREATE OR REPLACE FUNCTION sort_array8(int[])  RETURNS int[] AS
      $$
      SELECT CASE WHEN $1[1] > $1[2] THEN ARRAY[$1[2], $1[1]] ELSE $1 END;
      $$ LANGUAGE sql IMMUTABLE;
      

      结果

      我执行了大约 20 次,并从 EXPLAIN ANALYZE 中获得了最好的结果。

      SELECT sort_array1(i) FROM arr  -- Total runtime: 183 ms
      SELECT sort_array2(i) FROM arr  -- Total runtime: 175 ms
      
      SELECT sort_array3(i) FROM arr  -- Total runtime: 183 ms
      SELECT sort_array4(i) FROM arr  -- Total runtime: 183 ms
      SELECT sort_array5(i) FROM arr  -- Total runtime: 177 ms
      SELECT sort_array6(i) FROM arr  -- Total runtime: 144 ms
      
      SELECT sort_array7(i) FROM arr  -- Total runtime: 103 ms
      SELECT sort_array8(i) FROM arr  -- Total runtime:  43 ms (!!!)
      

      这些是来自 Debian Squeeze 上的 v9.0.5 服务器的结果。 v.8.4 上的类似结果。

      我还测试了 plpgsql 变体,这些变体与预期的一样慢:一个微小的操作开销太大,没有要缓存的查询计划。

      简单函数(nr. 7)比其他函数快得多。这是意料之中的,其他变体的开销对于一个小数组来说实在是太大了。

      但是,将STRICT 修饰符去掉超过加倍的速度是意料之中的。至少我没有。我发了question about this phenomenon here

      【讨论】:

        【解决方案3】:

        只需为这两个值创建一个唯一索引:

        create unique index ix on 
          mytable(least(interface[1], interface[2]), greatest(interface[1], interface[2])); 
        

        【讨论】:

        • +1 以获得另一个简洁的版本!执行速度几乎与 CASE 语句一样快。如果索引更有用的是保存两个整数而不是数组,那么这是要走的路。
        • 此选项的问题是 AFAICT,您无法在查询中使用生成的索引。
        猜你喜欢
        • 1970-01-01
        • 2012-03-01
        • 1970-01-01
        • 2022-11-29
        • 2019-02-11
        • 2022-11-29
        • 2023-01-12
        • 2012-06-10
        • 1970-01-01
        相关资源
        最近更新 更多