Пересечение нескольких массивов в PostgreSQL - PullRequest
9 голосов
/ 11 августа 2011

У меня есть представление, определенное как:

 CREATE VIEW View1 AS 
 SELECT Field1, Field2, array_agg(Field3) AS AggField 
 FROM Table1 
 GROUP BY Field1, Field2;

Я бы хотел получить пересечение массивов в AggField с чем-то вроде:

SELECT intersection(AggField) FROM View1 WHERE Field2 = 'SomeValue';

Это вообще возможно, или есть лучший способ добиться того, чего я хочу?

Ответы [ 3 ]

18 голосов
/ 11 августа 2011

Ближайшая вещь к пересечению массива, о которой я могу думать, это:

select array_agg(e)
from (
    select unnest(a1)
    intersect
    select unnest(a2)
) as dt(e)

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

create function array_intersect(a1 int[], a2 int[]) returns int[] as $$
declare
    ret int[];
begin
    -- The reason for the kludgy NULL handling comes later.
    if a1 is null then
        return a2;
    elseif a2 is null then
        return a1;
    end if;
    select array_agg(e) into ret
    from (
        select unnest(a1)
        intersect
        select unnest(a2)
    ) as dt(e);
    return ret;
end;
$$ language plpgsql;

Тогда вы могли бы сделать такие вещи:

=> select array_intersect(ARRAY[2,4,6,8,10], ARRAY[1,2,3,4,5,6,7,8,9,10]);
 array_intersect 
-----------------
 {6,2,4,10,8}
(1 row)

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

-- Pre-9.1
create aggregate array_intersect_agg(
    sfunc    = array_intersect,
    basetype = int[],
    stype    = int[],
    initcond = NULL
);

-- 9.1+ (AFAIK, I don't have 9.1 handy at the moment
-- see the comments below.
create aggregate array_intersect_agg(int[]) (
    sfunc = array_intersect,
    stype = int[]
);

И теперь мы понимаем, почему array_intersect делает забавные и несколько глупые вещи с NULL. Нам нужно начальное значение для агрегации, которая ведет себя как универсальный набор, и мы можем использовать для этого NULL (да, это немного пахнет, но я не могу придумать ничего лучшего из головы).

Как только все это на месте, вы можете делать такие вещи:

> select * from stuff;
    a    
---------
 {1,2,3}
 {1,2,3}
 {3,4,5}
(3 rows)

> select array_intersect_agg(a) from stuff;
 array_intersect_agg 
---------------------
 {3}
(1 row)

Не совсем просто или эффективно, но, может быть, это разумная отправная точка и лучше, чем ничего.

Полезные ссылки:

0 голосов
/ 06 мая 2015

Уже поздно отвечать на этот вопрос, но, может быть, кому-то это понадобится, поэтому я решил поделиться чем-то, что написал, потому что не нашел готового решения для пересечения любого числа массивов. Так и здесь. Эта функция получает массив массивов, если это только один массив, функция возвращает первый массив, если есть 2 массива, функция пересекает 2 массива и возвращает результат, если оно больше 2 массивов, функция берет пересечение 2 первых массивов, сохраняет в некоторой переменной и перебирает все другие массивы, пересекает каждый следующий массив с сохраненным результатом и сохраняет результат в переменной. если результат равен нулю, он существует с нулем. В и переменная, которая хранит массив с взаимодействующими данными, возвращенными из функции.

CREATE OR REPLACE FUNCTION array_intersected(iarray bigint[][])
  RETURNS bigint[] AS
$BODY$
    declare out_arr bigint[]; set1 bigint[]; set2 bigint[];
    BEGIN
        --RAISE NOTICE '%', array_length(iarray, 1);
        if array_length(iarray, 1) = 1 then
            SELECT ARRAY(SELECT unnest(iarray[1:1])) into out_arr;
        elseif array_length( iarray, 1) = 2 then
            set1 := iarray[1:1];
            set2 := iarray[2:2];
            SELECT ARRAY(SELECT unnest(set1) INTERSECT SELECT unnest(set2))into out_arr;
        elseif array_length(iarray, 1) > 2 then
            set1 := iarray[1:1];
            set2 := iarray[2:2];
            --exit if no common numbers exists int 2 first arrays
            SELECT ARRAY(SELECT unnest(set1) INTERSECT SELECT unnest(set2))into out_arr;
            if out_arr = NULL then
                EXIT;
                END IF;
            FOR i IN 3 .. array_upper(iarray, 1)
            LOOP
               set1 := iarray[i:i];
               SELECT ARRAY(SELECT unnest(set1) INTERSECT SELECT unnest(out_arr))into out_arr;
               if out_arr = NULL then
                EXIT;
                   END IF;
            END LOOP;
        end if;

    return out_arr;

    END;
    $BODY$
  LANGUAGE plpgsql VOLATILE;

Вот код для проверки его работоспособности.

select array_intersected(array[[1, 2]]::bigint[][]);

select array_intersected(array[[1, 2],[2, 3]]::bigint[][]);

select array_intersected(array[[1, 2],[2, 3], [2, 4]]::bigint[][]);

select array_intersected(array[[1, 2, 3, 4],[null, null, 4, 3], [3, 1, 4, null]]::bigint[][]);
0 голосов
/ 29 августа 2014

Принятый ответ не работает для меня.Вот как я это исправил.

create or replace function array_intersect(a1 int[], a2 int[]) returns int[] as $$
declare
  ret int[];
begin
  -- RAISE NOTICE 'a1 = %', a1;
  -- RAISE NOTICE 'a2 = %', a2;
  if a1 is null then
    -- RAISE NOTICE 'a1 is null';
    return a2;
  -- elseif a2 is null then
  --    RAISE NOTICE 'a2 is null';
  --    return a1;
  end if;
  if array_length(a1,1) = 0 then
    return '{}'::integer[];
  end if;
  select array_agg(e) into ret
  from (
    select unnest(a1)
    intersect
    select unnest(a2)
  ) as dt(e);
  if ret is null then
    return '{}'::integer[];
  end if;
  return ret;
end;
$$ language plpgsql;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...