Postgres: разделение элементов массива на столбцы

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

Рассмотрим это, например:

create table employees(age int, name text, scores int[]);
insert into employees values(25, 'Name1', ARRAY [10, 20, 30]);
insert into employees values(48, 'Name2', ARRAY [40, 50 , 60, 70]);
select scores[1], scores[2], scores[3] from employees where age > 40 and age < 50;

Оператор select возвращает результат в следующем виде:

scores | scores | scores
-------------------------
  40   |   50   |   60

Для доступа к элементам большого массива приведенный выше оператор выбора будет соответственно увеличиваться в размерах, и он недостаточно универсален для массивов разных размеров. Можем ли мы создать запрос, позволяющий сделать это элегантно, независимо от размера массива?

Заранее спасибо!

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

Zegarek 16.04.2024 13:29

Вопрос в том, почему вы хотите, чтобы каждый элемент массива находился в отдельном столбце? Вы можете select * from employees cross join unnest(scores) with ordinality as u(score,score_number); и получить каждый балл в отдельной строке, а не в столбце, при этом score_number укажет, из какой позиции в массиве он взят.

Zegarek 16.04.2024 13:33

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

Maddy 16.04.2024 14:05

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

Maddy 16.04.2024 14:08

Как вы используете результат в своем приложении? Возможно, другим решением было бы изменить это приложение, чтобы оно могло обрабатывать массивы. Что вы используете для хранения данных, которые читаете? ORM, построитель запросов, pandas, excel,...?

julaine 17.04.2024 14:39
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
119
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вопрос в том, почему вы хотите, чтобы каждый элемент массива находился в отдельном столбце? Вы можете получить каждую оценку в отдельной строке, а не в столбце, где score_number указывает, из какой позиции в массиве она взята. Демо на db<>fiddle:

select * from employees
  cross join unnest(scores) with ordinality as u(score,score_number);
возраст имя баллы счет номер_счета 25 Имя1 {10,20,30} 10 1 25 Имя1 {10,20,30} 20 2 25 Имя1 {10,20,30} 30 3 48 Имя2 {40,50,60,70} 40 1 48 Имя2 {40,50,60,70} 50 2 48 Имя2 {40,50,60,70} 60 3 48 Имя2 {40,50,60,70} 70 4

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

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

create function f_employee_scores_into_columns(employee_name text)returns text
language plpgsql as $f$
declare rec record;
        first_score_number int;
        last_score_number int;
        query_body text;
begin
  --check how many elements there are in the target array
  select array_lower(scores,1)
        ,array_upper(scores,1)
  from employees
  where employees.name=employee_name
  limit 1
  into first_score_number
      ,last_score_number;
  --construct a column list, placing each element in separate column
  select string_agg(format('scores[%1$s] as "score_%1$s"',score_number)
                    ,',' order by score_number)
  from generate_series(first_score_number,last_score_number,1)score_number
  into query_body;
  --inject the constructed column list into the final query
  --then execute it, writing the result to a table
  execute format('drop table if exists output_f_employee_scores_into_columns;
                  create table output_f_employee_scores_into_columns as
                  select age,name,%s 
                  from employees 
                  where employees.name=$1',query_body) using employee_name;
    
  --you would want to randomise the output table name and make it temp
  --right now all calls in all sessions would overwrite each other's outputs
  return 'output_f_employee_scores_into_columns';
end $f$;
--2-step call
select f_employee_scores_into_columns('Name2');
select * from output_f_employee_scores_into_columns;
возраст имя оценка_1 оценка_2 оценка_3 оценка_4 48 Имя2 40 50 60 70
--2-step call
select f_employee_scores_into_columns('Name1');
select * from output_f_employee_scores_into_columns;
возраст имя оценка_1 оценка_2 оценка_3 25 Имя1 10 20 30

Вы также можете просто перенести это на внешний интерфейс, где это обычно тривиально.

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

Maddy 17.04.2024 12:34

Другие вопросы по теме