Как разделить строку в sql на столбцы по разделителю

Имеется таблица:

СТ 1 СТ 2
А Z;Y;X;W
А;B Z;Y;X
А;B;C Z;Y
А;B;C;D Z

Необходимо разбить строки по разделителю ";" на отдельные столбцы, чтобы таблица приняла следующий вид:

CТ 1.1 CТ 1.2 CТ 1.3 CТ 1.4 CТ 2.1 CТ 2.2 CТ 2.3 CТ 2.4
A NULL NULL NULL Z Y X W
A B NULL NULL Z Y X NULL
A B C NULL Z Y NULL NULL
A B C D Z NULL NULL NULL

Количество столбцов, значения в которых нужно поделить по разделителю, заранее неизвестно. Так же нет информации про максимальное количество разделителей в строке (привел пример, когда таких встречается максимум 3, соответственно каждый из столбцов поделился на 4 столбца). Вероятно путь к решению данной задаче лежит через представление строк в виде массивов с помощью string_to_array:

SELECT string_to_array('А;B;C;D', ';') -> {A,B,C,D}

Однако, как указал ранее, количество таких столбцов заранее неизвестно.

Использую PostgreSQL 14.


Ответы (1 шт):

Автор решения: ValNik

Пример

create table test (id int,ct1 varchar(100), ct2 varchar(100));
insert into test values
 (1,'А','Z;Y;X;W')
,(2,'А;B','Z;Y;X')
,(3,'А;B;C','Z;Y')
,(4,'А;B;C;D','Z')
;
select * from test;

Процедура разборки и сборки

create or replace procedure Pvtd() 
AS $$
declare sSql0 varchar(1000);
declare sSql1 varchar(1000);
declare sSql2 varchar(1000);
BEGIN
  -- упростим исходные данные: сделаем структуру линейной по одному элементу в строке  
  -- и запишем во временную таблицу vals
  -- содержание получившейся таблицы показано ниже в тексте
  sSql0:='create temporary table vals (id int,ct1 varchar(100),ct2 varchar(100)
        ,elem1 varchar(10), nr1 int,col varchar(10));';
  execute sSql0;
  with t as(
    select id,ct1,ct2,elem1,nr1,concat('ct1_',nr1) col
    from test t
    left join unnest(string_to_array(t.ct1, ';'))WITH ORDINALITY AS c1(elem1, nr1)
       on 1=1
    union all
    select id,ct1,ct2,elem2,nr2,concat('ct2_',nr2) col
    from test t
    left join unnest(string_to_array(t.ct2, ';'))WITH ORDINALITY AS c2(elem2, nr2)
      on 1=1
  )
  insert into vals
  select * from t;
  
   -- создадим временную таблицу - выходной результат
   -- здесь только динамический SQL
   -- список колонок таблицы создаем через string_agg
  sSql1:=concat('create temporary table pvtd_table '
      ,'(id int, ct1  varchar(100),ct2  varchar(100),'
      , (select string_agg(distinct concat(col,' varchar(10)')
   ,',' order by concat(col,' varchar(10)' )) from vals),')'
  );
  execute sSql1;

   -- собственно вычисление нужных данных с использованием CROSSTAB
   -- список выходных колонок результата также создаем через string_agg
  sSql2:=(select 
  concat('insert into pvtd_table select * '
    ,'from crosstab(',' ''select id,ct1,ct2,col,elem1 from vals order by 1,2,3'' '
               ,', ''select distinct col from vals order by 1'' ',')' 
    ,'as pvt(id int, ct1  varchar,ct2  varchar,'
   , (select string_agg(distinct concat(col,' varchar')
   ,',' order by concat(col,' varchar' )) from vals),')'
  ));
  execute sSql2;
END
$$
LANGUAGE plpgsql;

-- эта строка нужна, чтобы был доступен CROSSTAB
CREATE EXTENSION IF NOT EXISTS tablefunc;
-- вызываем процедуру, чтобы получить результат во временную таблицу
call pvtd();
select * from pvtd_table;

Вывод

CREATE TABLE
INSERT 0 4
id ct1 ct2
1 А Z;Y;X;W
2 А;B Z;Y;X
3 А;B;C Z;Y
4 А;B;C;D Z
SELECT 4
CREATE PROCEDURE
CREATE EXTENSION
CALL
id ct1 ct2 ct1_1 ct1_2 ct1_3 ct1_4 ct2_1 ct2_2 ct2_3 ct2_4
1 А Z;Y;X;W А null null null Z Y X W
2 А;B Z;Y;X А B null null Z Y X null
3 А;B;C Z;Y А B C null Z Y null null
4 А;B;C;D Z А B C D Z null null null

Промежуточная таблица vals

id ct1 ct2 col elem1
1 А Z;Y;X;W ct1_1 А
1 А Z;Y;X;W ct2_1 Z
1 А Z;Y;X;W ct2_2 Y
1 А Z;Y;X;W ct2_3 X
1 А Z;Y;X;W ct2_4 W
2 А;B Z;Y;X ct2_3 X
2 А;B Z;Y;X ct1_2 B
2 А;B Z;Y;X ct1_1 А
2 А;B Z;Y;X ct2_2 Y
2 А;B Z;Y;X ct2_1 Z
3 А;B;C Z;Y ct1_2 B
3 А;B;C Z;Y ct2_1 Z
3 А;B;C Z;Y ct2_2 Y
3 А;B;C Z;Y ct1_1 А
3 А;B;C Z;Y ct1_3 C
4 А;B;C;D Z ct1_3 C
4 А;B;C;D Z ct1_2 B
4 А;B;C;D Z ct1_1 А
4 А;B;C;D Z ct2_1 Z
4 А;B;C;D Z ct1_4 D
→ Ссылка