PostgreSQL - лучший способ вернуть массив пар ключ-значение - PullRequest
14 голосов
/ 03 февраля 2012

Я пытаюсь выбрать количество полей, одно из которых должно быть массивом, каждый элемент которого содержит два значения.Каждый элемент массива должен содержать имя (изменяющийся символ) и идентификатор (числовой).Я знаю, как вернуть массив отдельных значений (используя ключевое слово ARRAY), но я не уверен, как вернуть массив объекта, который сам по себе содержит два значения.

Запрос похож на

SELECT
    t.field1,
    t.field2,
    ARRAY(--with each element containing two values i.e. {'TheName', 1 })
FROM MyTable t

Я прочитал, что один из способов сделать это - выбрать значения в тип, а затем создать массив этого типа.Проблема в том, что остальная часть функции уже возвращает тип (что означает, что я бы тогда имел вложенные типы - это нормально? Если так, как бы вы прочитали эти данные обратно в код приложения - то есть с провайдером данных .Net, например NPGSQL?)

Любая помощь очень ценится.

Ответы [ 2 ]

12 голосов
/ 03 февраля 2012

Массивы могут содержать только элементы одного типа

В вашем примере показано значение text и integer (без одинарных кавычек 1). Как правило, невозможно смешивать типы в массиве. Чтобы получить эти значения в массив, вы должны создать composite type, а затем сформировать Массив этого составного типа, как вы уже упоминали сами.

В качестве альтернативы вы можете использовать типы данных json в Postgres 9.2+, jsonb в Postgres 9.4+ или hstore для пар ключ-значение.


Конечно, вы можете привести integer к text и работать с двумерным текстовым массивом. Рассмотрим два варианта синтаксиса для ввода массива в демонстрационном примере ниже и ознакомьтесь с руководством по вводу массива .

Есть ограничение, которое нужно преодолеть. Если вы попытаетесь объединить ARRAY (построение по ключу и значению) в двумерный массив, агрегирующая функция array_agg() или ошибка конструктора ARRAY:

ERROR:  could not find array type for data type text[]

Хотя есть способы обойти это.

Объединить пары ключ-значение в двумерный массив

PostgreSQL 9.1 с standard_conforming_strings= on:

CREATE TEMP TABLE tbl(
 id     int
,txt    text
,txtarr text[]
);

Столбец txtarr только для демонстрации вариантов синтаксиса в команде INSERT. Третий ряд украшен метасимволами:

INSERT INTO tbl VALUES
 (1, 'foo', '{{1,foo1},{2,bar1},{3,baz1}}')
,(2, 'bar', ARRAY[['1','foo2'],['2','bar2'],['3','baz2']])
,(3, '}b",a{r''', '{{1,foo3},{2,bar3},{3,baz3}}'); -- txt has meta-characters

SELECT * FROM tbl;

Простой случай: объединить два целых числа (я использую одно и то же дважды) в двумерный массив int:

Обновление: лучше с пользовательской функцией агрегирования

С полиморфным типом anyarray он работает для всех базовых типов:

CREATE AGGREGATE array_agg_mult (anyarray)  (
    SFUNC     = array_cat
   ,STYPE     = anyarray
   ,INITCOND  = '{}'
);

Звоните:

SELECT array_agg_mult(ARRAY[ARRAY[id,id]]) AS x        -- for int
      ,array_agg_mult(ARRAY[ARRAY[id::text,txt]]) AS y -- or text
FROM   tbl;

Обратите внимание на дополнительный слой ARRAY[], чтобы сделать его многомерным массивом.

Обновление для Postgres 9.5 +

Postgres теперь поставляет вариант array_agg(), принимающий входные данные массива, и вы можете заменить мою пользовательскую функцию сверху следующим:

Руководство:

array_agg(expression)
...
входные массивы объединяются в массив из одного более высокое измерение (входные данные должны иметь одинаковую размерность и не могут быть пустым или NULL)

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

Я подозреваю, что, не зная больше о вашем приложении, я не смогу получить вас до нужного вам результата. Но мы можем получить довольно далеко. Для начала, есть функция ROW:

# SELECT 'foo', ROW(3, 'Bob');
 ?column? |   row   
----------+---------
 foo      | (3,Bob)
(1 row)

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

# CREATE TYPE person(id INTEGER, name VARCHAR);
CREATE TYPE
# SELECT now(), row(3, 'Bob')::person;
              now              |   row   
-------------------------------+---------
 2012-02-03 10:46:13.279512-07 | (3,Bob)
(1 row)

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

# DROP TYPE person;
DROP TYPE

# CREATE TABLE people (id SERIAL, name VARCHAR);
NOTICE:  CREATE TABLE will create implicit sequence "people_id_seq" for serial column "people.id"
CREATE TABLE

# SELECT 'foo', row(3, 'Bob')::people;
 ?column? |   row   
----------+---------
 foo      | (3,Bob)
(1 row)

Смотрите, в третьем запросе я использовал people как тип.

Теперь эта помощь вряд ли поможет так сильно, как вы думаете по двум причинам:

  1. Я не могу найти удобный синтаксис для извлечения данных из вложенной строки.

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

  2. Драйвер PostgreSQL на вашем языке может не обрабатывать строковые данные, вложенные в строку.

    Я не могу говорить о NPGSQL, но так как это очень специфичная для PostgreSQL функция, вы не найдете поддержки в библиотеках, которые поддерживают другие базы данных. Например, Hibernate не сможет обрабатывать выборку объекта, хранящегося в виде значения ячейки в строке. Я даже не уверен, что JDBC сможет дать Hibernate информацию с пользой, поэтому проблема может быть достаточно глубокой.

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

...