Массив массивов в PostgreSQL - PullRequest
4 голосов
/ 06 февраля 2012

Я использую оператор %% для типа hstore в PostgreSQL, который преобразует hstore (тип значения ключа) в массив, чьи элементы чередуются {{key, value}, {key value}}.

Когда я хочу вернуть массив этих уплощенных hstores, я получаю эту ошибку: could not find array type for data type text[] из-за отсутствия поддержки PostgreSQL для массива массивов.

С точки зрения любопытства, кто-нибудь знает, почему этоне поддерживается?И что еще более важно, есть ли обходной путь для этого типа сценария?

В данный момент я объединяю результаты в строку (через запятую) и анализирую их на стороне приложения (C # и NPGSQL).Однако этот подход не совсем верен, я хотел бы иметь возможность читать строку обратно как массив массивов .NET или массив значений ключей и т. Д.

Большое спасибо.

Ответы [ 3 ]

16 голосов
/ 09 мая 2012

PostgreSQL имеет ограниченную поддержку "массива массивов"

см. Руководство

Это ограниченная форма "массива массивов". Как говорит Павел (ответ), он называется «многомерный массив», но на самом деле является матрицей, поэтому в каждом измерении должно быть одинаковое количество элементов.

Вы можете использовать такую ​​структуру для отображения многомерных и разнородных декартовых координат в научных приложениях, но не для хранения произвольных векторов векторов, таких как данные XML или JSON.

ПРИМЕЧАНИЕ. Хорошо известная двумерная (2D) однородная матрица представляет собой математическую матрицу . Фактически, научные применения матрицы, которые мотивировали тип данных «Ограниченный многомерный массив PostgreSQL», и поведение функций массива с такими массивами. Подумайте о «3D-массиве» как «3D-матрице», «4D-массиве» как «4D-матрице» и т. Д.

Примеры:

SELECT array_cat(ARRAY[[1,2],[3,4]], ARRAY[5,6]);
---------------------
 {{1,2},{3,4},{5,6}}
SELECT array_cat(ARRAY[[1,2],[3,4]], ARRAY[[5,6]]); -- SAME RESULT

SELECT ARRAY[ARRAY[1,2],ARRAY[5,6]];
---------------
 {{1,2},{5,6}}

SELECT array_cat(ARRAY[ARRAY[1,2]],ARRAY[3]); -- ERROR1
SELECT ARRAY[ARRAY[1,2],ARRAY[4]];  -- ERROR2 

Комментарии @Daniel_Lyons о том, «почему они не поддерживаются», касаются «неоднородных массивов массивов» (см. Случаи ошибок выше). ERROR1 выше: потому что можно объединять массивы только одного размера ERROR2 выше: все массивы для определенного измерения должны иметь одинаковую длину, как матрица.

Еще одна любопытная вещь в отношении встроенных функций и операторов: «поведение по умолчанию» в PostgreSQL предназначено для отдельных массивов и элементов. Там нет перегрузки для стандартных array_append(),

SELECT array_append(ARRAY[1,2],5); -- now ok, 5 is a element
 {1,2,5}

SELECT array_cat(ARRAY[1,2], ARRAY[5,6]);
----------
 {1,2,5,6}

SELECT array_append(ARRAY[[1,2],[3,4]], ARRAY[5,6]); -- ERROR3 
SELECT array_append(ARRAY[1,2],ARRAY[5,6]); -- ERROR4

ERROR3 выше: НЕТ ПЕРЕГРУЗКИ, чтобы добавить «элемент массива» (даже версия 9,2 pg). ERROR4 выше: необходимо использовать array_cat для «объединения всех в один массив».

"Поведение слияния" в последнем примере array_cat является любопытным, а не созданным массивом массивов. Используйте array_cat(a1, ARRAY[a2]) для достижения этого результата,

SELECT array_cat(ARRAY[1,2], ARRAY[ARRAY[5,6]]);  -- seems illogical...
---------------
{{1,2},{5,6}}

Разреженная матрица

Чтобы избежать проблем с разреженной матрицей и аналогичными структурами данных, используйте функцию ниже. Он заполняет остальные элементы, устанавливая затем значение NULL (или любое постоянное значение).

 CREATE or replace FUNCTION array_fillTo(
    p_array anyarray, p_len integer, p_null anyelement DEFAULT NULL
 ) RETURNS anyarray AS $f$
   SELECT CASE 
       WHEN len=0 THEN array_fill(p_null,array[p_len])
       WHEN len<p_len THEN p_array || array_fill($3,array[$2-len])
       ELSE $1 END
   FROM ( SELECT COALESCE( array_length(p_array,1), 0) ) t(len)
 $f$ LANGUAGE SQL IMMUTABLE;

PS: пожалуйста, отредактируйте этот ответ, чтобы добавить исправления / оптимизации, это Вики!

Возвращаясь к первым примерам, теперь мы можем избежать ошибок (см. ERROR1),

SELECT array_cat(ARRAY[ARRAY[1,2]],array_fillTo(ARRAY[3],2));
-- {{1,2},{3,NULL}}
SELECT array_cat(
   ARRAY[ARRAY[1.1::float,2.0]],
   array_fillTo(ARRAY[]::float[],2,0::float)
);
-- {{1.1,2},{0,0}}
SELECT array_fillto(array['Hello'],2,'');
-- {Hello,""}

ПРИМЕЧАНИЕ о старом array_fillTo ()

array_fill() становится встроенной функцией с PostgreSQL v8.4, для v8.3 или старше:

 CREATE FUNCTION array_fillTo(anyarray,integer,anyelement DEFAULT NULL) 
 RETURNS anyarray AS $$
   DECLARE
     i integer;
     len integer;
     ret ALIAS FOR $0;
   BEGIN
     len = array_length($1,1);
     ret = $1;
     IF len<$2 THEN
         FOR i IN 1..($2-len) LOOP
           ret = ret || $3;
         END LOOP;
     END IF;
     RETURN ret;
   END;
 $$ LANGUAGE plpgsql IMMUTABLE;
8 голосов
/ 08 февраля 2012

С точки зрения любопытства кто-нибудь знает, почему они не поддерживаются?

Один общий ответ заключается в том, что массивы по своей природе являются антиреляционными. Удаление повторяющихся значений - вот как вы получаете 1-ую нормальную форму . Иметь повторяющиеся группы из повторяющихся групп представляется совершенно безумным с точки зрения теории отношений.

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

CREATE TABLE users (
  id integer primary key,
  name varchar,
  favorite_colors varchar[],
  ...
);

вам следует переопределить это отношение следующим образом:

CREATE TABLE users (
  id integer primary key,
  name varchar,
  ...
);

CREATE TABLE favorite_colors (
  user_id integer references users,
  color varchar
);

Или даже:

CREATE TABLE users (
  id integer primary key,
  name varchar,
  ...
);

CREATE TABLE colors (
  color varchar primary key
);

CREATE TABLE favorite_colors (
  user_id integer references users,
  color varchar references colors,
  primary key (user_id, color)
);

Hstore поддерживает множество функций, многие из которых помогут легко интегрировать его в реляционное мировоззрение. Я думаю, что самым простым способом решения вашей проблемы было бы использование функции each для преобразования значений hstore в отношения, которые затем можно использовать как обычный набор значений. Так или иначе вы обращаетесь к нескольким значениям в других базах данных: к запросам и работе с наборами результатов.

5 голосов
/ 08 февраля 2012

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

postgres=# create table fx(a int[]);
CREATE TABLE
postgres=# insert into fx values(array[1,3,4]);
INSERT 0 1
postgres=# insert into fx values(array[6,7]);
INSERT 0 1
postgres=# select array_agg(row(a)) from fx;
            array_agg            
---------------------------------
 {"(\"{1,3,4}\")","(\"{6,7}\")"}
(1 row)
...