Уникальное ограничение Postgres для массива

Как создать ограничение на уникальность всех значений в массиве, как:

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

моя функция сортировки

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

мне нужно, чтобы значение {10, 22} и {22, 10} считалось одинаковым и проверялось под уникальным ограничением

3 ответов


Я не думаю, что вы можете использовать функцию уникальное ограничение но вы можете с уникальный индекс. Поэтому, учитывая функцию сортировки, что-то вроде этого:

create function sort_array(integer[]) returns integer[] as $$
    select array_agg(n) from (select n from unnest() as t(n) order by n) as a;
$$ 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

@mu уже продемонстрировал, как индекс на выражение может решить вашу проблему.

мое внимание привлекли используемые функции. Оба кажутся излишним для массива из двух целых чисел. Это может быть упрощением реальной ситуации. (?)

в любом случае, я был заинтригован и провел тест с несколькими вариантами.

настройка тест

-- 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() 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() AS t(n);
$$ LANGUAGE sql STRICT IMMUTABLE;


-- 3) uralbash's query
CREATE OR REPLACE FUNCTION sort_array3(anyarray)  RETURNS anyarray AS
$$
SELECT ARRAY(
    SELECT [i]
    FROM   generate_series(array_lower(,1), array_upper(,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 [i]
    FROM   generate_series(array_lower(,1), array_upper(,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 [i]
    FROM   generate_series(1, array_upper(,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]),([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] > [2] THEN ARRAY[[2], [1]] ELSE  END;
$$ LANGUAGE sql STRICT IMMUTABLE;

-- 8) without STRICT modifier
CREATE OR REPLACE FUNCTION sort_array8(int[])  RETURNS int[] AS
$$
SELECT CASE WHEN [1] > [2] THEN ARRAY[[2], [1]] ELSE  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 (!!!)

Это результаты v9.0.5 сервер на Debian Squeeze. Аналогичные результаты по v. 8.4.

Я также протестировал варианты plpgsql, которые были немного медленнее, как ожидалось: слишком много накладных расходов для крошечной операции, нет плана запроса для кэширования.

на простая функция (nr. 7) существенно быстрее, чем другие. Этого следовало ожидать, накладные расходы других вариантов слишком велики для крошечного массива.

но что оставляя в стороне STRICT модификатор больше, чем двойники скорость не ожидалась. По крайней мере, я этого не сделал. Я опубликовал вопрос об этом явлении здесь.


просто создайте уникальный индекс для двух значений:

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