Как пройти через array_agg в виде повторяющихся столбцов? - PullRequest
3 голосов
/ 05 июля 2011

Используя PostgreSQL 8.4, я успешно смог использовать array_agg (), чтобы выполнить несколько заказов и создать одну строку для клиентов:

Из этого:

order_id|customer_id|order_date  |order_desc
1       |1          |"2010-01-01"|"Tom's First" 
2       |1          |"2010-04-01"|"Tom's Second" 
7       |1          |"2010-04-13"|"Tom's Third" 
8       |1          |"2011-04-13"|"Tom's Last" 
5       |1          |"2011-06-20"|"Tom's Really Last." 
3       |2          |"2010-07-07"|"Dick's First" 
6       |2          |"2011-07-07"|"Dick's Other" 
4       |3          |"2011-04-04"|"Harry's Only"

Используя это:

select cu.customer, array_agg(ord.order_id) as orders from test_order ord
inner join test_customer cu
on ord.customer_id = cu.customer_id
group by cu.customer

Результат:

customer   |orders  
"Tom"      |"{1,2,7,8,5}"  
"Dick"     |"{3,6}"  
"Harry"    |"{4}"  

И я могу захватывать части массива для создания новых столбцов, если я жестко кодирую каждую итерацию:

select cu.customer,   
(array_agg(ord.order_id))[1] as order_1,  
(array_agg(ord.order_id))[2] as order_2,  
(array_agg(ord.order_id))[3] as order_3,  
(array_agg(ord.order_id))[4] as order_4,  
(array_agg(ord.order_id))[5] as order_5   
from test_order ord  
inner join test_customer cu  
on ord.customer_id = cu.customer_id  
group by cu.customer  

Результат:

customer|order_1|order_2|order_3|order_4|order_5  
"Dick"  |3      |6      |       |       |  
"Harry" |4      |       |       |       |   
"Tom"   |8      |1      |5      |2      |7  

Однако, что я хотел бы сделать, в два этапа:

  1. Для Loop я прохожу записи, чтобы мне не приходилось создавать каждую итерацию поля. Хорошая новость заключается в том, что приведенная выше структура не дает ошибок и просто пропускает NULL, но если я когда-либо получу какое-то безумное количество записей, мне не нужно будет вручную создавать order_55, order_56 и т. Д. В моем выражении.

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

    customer|order_id1|order_date1|order_desc1|order_id2|order_date2|order_desc2| ... 
    

    и т. Д. И т. Д. По сути, объединение родительской таблицы (customer) с дочерней (order), но с несколькими дочерними записями, проходящими через одну строку вместо создания нескольких.

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

ОБНОВЛЕНИЕ : я приблизился к функции каннибализации ... при первом вызове она создает столбцы и заполняет одного из клиентов. Но по какой-то причине мой IF NOT EXISTS работает, когда запускается отдельно, но не внутри функции: я получаю ошибку «column order_id1 Существует». Я также хотел бы в конечном итоге изменить это, чтобы конкретные поля не были жестко закодированы; вместо customer_id я хотел бы сделать что-то вроде передачи родительской таблицы, дочерней таблицы и идентификатора соединения и сделать так, чтобы она полностью добавляла дочернюю таблицу таким способом перекрестной таблицы.

CREATE FUNCTION loop_test(integer) RETURNS integer AS $$

DECLARE
rOrder RECORD;
loop_counter INT := 1;
target_customer_id ALIAS FOR $1;        
BEGIN

FOR rOrder IN SELECT * 
    FROM vdad_data.test_order 
    WHERE customer_id = target_customer_id 
    ORDER BY order_id LOOP

    IF NOT EXISTS
        (
        SELECT * FROM information_schema.COLUMNS
        WHERE COLUMN_NAME= 'order_id' || loop_counter
        AND TABLE_NAME='test_customer'
        AND TABLE_SCHEMA='vdad_data'
        )
        THEN

        EXECUTE 'ALTER TABLE vdad_data.test_customer
        ADD COLUMN order_id' || loop_counter || ' integer';
    END IF;

    IF NOT EXISTS
        (
        SELECT * FROM information_schema.COLUMNS
        WHERE COLUMN_NAME= 'order_date' || loop_counter
        AND TABLE_NAME='test_customer'
        AND TABLE_SCHEMA='vdad_data'
        )
        THEN

        EXECUTE 'ALTER TABLE vdad_data.test_customer
        ADD COLUMN order_date' || loop_counter || ' date';
    END IF;


    IF NOT EXISTS
        (
        SELECT * FROM information_schema.COLUMNS
        WHERE COLUMN_NAME= 'order_desc' || loop_counter
        AND TABLE_NAME='test_customer'
        AND TABLE_SCHEMA='vdad_data'
        )
        THEN

        EXECUTE 'ALTER TABLE vdad_data.test_customer
        ADD COLUMN order_desc' || loop_counter || ' character varying';
    END IF;

EXECUTE 'UPDATE vdad_data.test_customer 
      SET order_id' || loop_counter || ' = ' || rOrder.order_id ||',
      order_date' || loop_counter || ' = ' || quote_literal(to_char(rOrder.order_date,'yyyy-mm-dd')) ||',         
      order_desc' || loop_counter  || ' = ' ||  quote_literal(rOrder.order_desc) ||'
      WHERE customer_id = ' ||rOrder.customer_id;

loop_counter = loop_counter + 1;
END LOOP;

RETURN 1;
END;
$$ LANGUAGE plpgsql;

Я прошу прощения за то, что был на всем протяжении карты, поскольку я пытался решить несколько вещей об этом сразу, что я не могу полностью понять. Любая помощь приветствуется, спасибо!

Ответы [ 2 ]

1 голос
/ 17 июля 2011

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

Что-то вроде:

select cu.customer,
       row_number() OVER(PARTITION BY cu.customer ORDER BY ord.order_date)
from test_order ord inner join test_customer cu  
  on ord.customer_id = cu.customer_id  
group by cu.customer  
0 голосов
/ 22 мая 2014

Это:

select array_agg(row(order_id,order_date,order_desc))
from (
select 1 order_id,1 customer_id,'2010-01-01' order_date,'Tom''s First' order_desc union 
select 2 order_id,1 customer_id,'2010-04-01' order_date,'Tom''s Second' order_desc union 
select 7 order_id,1 customer_id,'2010-04-13' order_date,'Tom''s Third' order_desc union 
select 8 order_id,1 customer_id,'2011-04-13' order_date,'Tom''s Last' order_desc union 
select 5 order_id,1 customer_id,'2011-06-20' order_date,'Tom''s Really Last.' order_desc union 
select 3 order_id,2 customer_id,'2010-07-07' order_date,'Dick''s First' order_desc union 
select 6 order_id,2 customer_id,'2011-07-07' order_date,'Dick''s Other' order_desc union 
select 4 order_id,3 customer_id,'2011-04-04' order_date,'Harry''s Only' order_desc
) orders
group by orders.customer_id

дает вам три строки:

"{"(2,2010-04-01,\"Tom's Second\")","(1,2010-01-01,\"Tom's First\")","(7,2010-04-13,\"Tom's Third\")","(5,2011-06-20,\"Tom's Really Last.\")","(8,2011-04-13,\"Tom's Last\")"}"

"{"(3,2010-07-07,\"Dick's First\")","(6,2011-07-07,\"Dick's Other\")"}"

"{"(4,2011-04-04,\"Harry's Only\")"}"

Это выглядит очень близко к тому, что вы сказали бы "еще лучше":

клиент | order_id1 | order_date1 | order_desc1 | order_id2 | order_date2 | order_desc2 |...

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...