2014-02-20 6 views
2

Я, вероятно, что-то неправильно делаю с формированием литерала. Предположим, у меня есть простой хранимой процедуры, как это:Передача массива сложного типа в хранимую процедуру

CREATE OR REPLACE FUNCTION do_something(input_array composite_type[]) 
    RETURNS SETOF text AS 
$BODY$ 
DECLARE 
    temp_var composite_type; 
BEGIN 

    FOR temp_var IN SELECT unnest(input_array) LOOP 
     return next temp_var.message; 
    END LOOP; 

END 
$BODY$ 
    LANGUAGE plpgsql; 

composite_type определяется как:

CREATE TYPE composite_type AS 
    (message text, 
    amount numeric(16,2)); 

Выполнение запроса, как это:

SELECT * FROM do_something('{"(test,11)","(test2,22)"}') 

Производит этот результирующий набор:

(test,11.00) 
(test2,22.00) 

Вместо:

test 
test2 

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

+1

Отличный вопрос! –

ответ

2

Как вы определяете свой вклад появляется хорошо, как то же самое поведение наблюдается при row- и массив-конструктор синтаксисом:

SELECT * FROM do_something(ARRAY[ ROW('test',11), ROW('test2',22) ]::composite_type[]); 

И:

SELECT ARRAY[ ROW('test',11), ROW('test2',22) ]::composite_type[]; 

производит:

'{"(test,11.00)","(test2,22.00)"}' 

Если вы добавите:

RAISE NOTICE '!%!',temp_var; 

внутри цикла выход:

NOTICE: !("(test,11.00)",)! 
NOTICE: !("(test2,22.00)",)! 

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

So. Зачем?

Это немного тонкий. Вы используете:

SELECT unnest(input_array) 

, который, кажется, что вы хотите, верно:

regress=>  SELECT unnest(ARRAY[ ROW('test',11), ROW('test2',22) ]::composite_type[]); 
    unnest  
--------------- 
(test,11.00) 
(test2,22.00) 
(2 rows) 

... но на самом деле, это возвращает один столбец типа composite_type. Композитный тип PL/PgSQL присваивает один столбец для столбца типа. Таким образом, одиночный col вбивается в «сообщение», и нет второго столбца.

Вместо этого написать:

SELECT * FROM unnest(input_array) 

распаковывать композит для назначения. Затем он работает, как ожидалось:

regress=> SELECT * FROM do_something(ARRAY[ ROW('test',11), ROW('test2',22) ]::composite_type[]); 
do_something 
-------------- 
test 
test2 
(2 rows) 

Если первое поле composite_type были типа нетекстового, вы получите сообщение об ошибке, которая была гораздо более информативный об этом.

+0

Отличное объяснение. Это довольно неудачный дизайн в plpgsql. Пограничный баг. (Я просто выполнял один и тот же набор тестов. Вы избили меня до этого :) :) –

+0

@ErwinBrandstetter Да, как правило, согласен, что это пограничная ошибка. –

+1

@CraigRinger: это не ошибка, это особенность :(- это следствие двух фактов: Postgres поддерживает вложенные составные типы - и кто-то должен быть осторожным, на каком уровне вложенности, и plpgsql является типом подчиненного и использует поздний текстовый кастинг - поэтому все преобразуется в текст, а затем в целевой тип - но вы теряете метаданные о типе. –

1

Крейг хорошо объяснил причину этого поведения - Назначение переменной = значение внутри оператора FOR ожидает нулевого вложения. Так что вы должны сделать:

CREATE OR REPLACE FUNCTION do_something(input_array composite_type[]) 
RETURNS SETOF text AS $BODY$ 
DECLARE 
    temp_var record; 
BEGIN 
    -- unnesting 
    FOR temp_var IN SELECT (unnest(input_array)).* 
    LOOP 
     RETURN NEXT temp_var.message; 
    END LOOP; 
    RETURN; 
END 
$BODY$ LANGUAGE plpgsql; 

или - предпочтительнее - новее использование SetReturnedFunction внутри "список столбцов"

CREATE OR REPLACE FUNCTION do_something(input_array composite_type[]) 
RETURNS SETOF text AS $BODY$ 
DECLARE 
    temp_var record; 
BEGIN 
    -- SELECT FROM 
    FOR temp_var IN SELECT * FROM unnest(input_array) 
    LOOP 
     RETURN NEXT temp_var.message; 
    END LOOP; 
    RETURN; 
END 
$BODY$ LANGUAGE plpgsql; 
Смежные вопросы