Могу ли я иметь функцию postgres plpgsql, возвращающую записи переменных-столбцов? - PullRequest
1 голос
/ 23 октября 2009

Я хочу создать функцию postgres, которая создает набор столбцов возвращается на лету; короче, надо взять список ключей, собрать один столбец на ключ и возвращает запись, состоящую из любого набора колонн было. Вкратце вот код:

CREATE OR REPLACE FUNCTION reports.get_activities_for_report() RETURNS int[] AS $F$
BEGIN
    RETURN ARRAY(SELECT activity_id FROM public.activity WHERE activity_id NOT IN (1, 2));
END;
$F$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION reports.get_amount_of_time_query(format TEXT, _activity_id INTEGER) RETURNS TEXT AS $F$
DECLARE
    _label TEXT;
BEGIN
    SELECT label INTO _label FROM public.activity WHERE activity_id = _activity_id;
    IF _label IS NOT NULL THEN
        IF lower(format) = 'percentage' THEN
            RETURN $$TO_CHAR(100.0 *$$ ||
            $$ (SUM(CASE WHEN activity_id = $$ || _activity_id || $$ THEN EXTRACT(EPOCH FROM ended - started) END) /$$ ||
            $$ SUM(EXTRACT(EPOCH FROM ended - started))),$$ ||
            $$ '990.99 %') AS $$ || quote_ident(_label);
        ELSE
            RETURN $$SUM(CASE WHEN activity_id = $$ || _activity_id || $$ THEN ended - started END)$$ ||
            $$ AS $$ || quote_ident(_label);
        END IF;
    END IF;
END;
$F$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION reports.build_activity_query(format TEXT, activities int[]) RETURNS TEXT AS $F$
DECLARE
    _activity_id INT;
    query TEXT;
    _activity_count INT;
BEGIN
    _activity_count := array_upper(activities, 1);
    query := $$SELECT agent_id, portal_user_id, SUM(ended - started) AS total$$;
    FOR i IN 1.._activity_count LOOP
        _activity_id := activities[i];

        query := query || ', ' || reports.get_amount_of_time_query(format, _activity_id);
    END LOOP;
    query := query || $$ FROM public.activity_log_final$$ ||
    $$ LEFT JOIN agent USING (agent_id)$$ ||
    $$ WHERE started::DATE BETWEEN actual_start_date AND actual_end_date$$ ||
    $$ GROUP BY agent_id, portal_user_id$$ ||
    $$ ORDER BY agent_id$$;
    RETURN query;
END;
$F$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION reports.get_agent_activity_breakdown(format TEXT, start_date DATE, end_date DATE) RETURNS SETOF RECORD AS $F$
DECLARE
    actual_end_date DATE;
    actual_start_date DATE;
    query TEXT;
    _rec RECORD;
BEGIN
    actual_start_date := COALESCE(start_date, '1970-01-01'::DATE);
    actual_end_date := COALESCE(end_date, now()::DATE);
    query := reports.build_activity_query(format, reports.get_activities_for_report());

    FOR _rec IN EXECUTE query LOOP
        RETURN NEXT _rec;
    END LOOP;
END
$F$
LANGUAGE plpgsql;

Это создает запросы, которые выглядят (примерно) так:

SELECT agent_id, 
    portal_user_id, 
    SUM(ended - started) AS total, 
    SUM(CASE WHEN activity_id = 3 THEN ended - started END) AS "Label 1"
    SUM(CASE WHEN activity_id = 4 THEN ended - started END) AS "Label 2"
FROM public.activity_log_final 
    LEFT JOIN agent USING (agent_id) 
WHERE started::DATE BETWEEN actual_start_date AND actual_end_date 
GROUP BY agent_id, portal_user_id 
ORDER BY agent_id

Когда я пытаюсь вызвать функцию get_agent_activity_breakdown(), я получаю эту ошибку:

psql:2009-10-22_agent_activity_report_test.sql:179: ERROR:  a column definition list is required for functions returning "record"
CONTEXT:  SQL statement "SELECT * FROM reports.get_agent_activity_breakdown('percentage', NULL, NULL)"
PL/pgSQL function "test_agent_activity" line 92 at SQL statement

Хитрость, конечно, в том, что столбцы с метками «Метка 1» и «Метка» 2 'зависят от набора видов деятельности, определенных в содержании таблица активности, которую я не могу предсказать при вызове функции. Как я могу создать функцию для доступа к этой информации?

Ответы [ 4 ]

2 голосов
/ 24 октября 2009

В конце концов, ответ Саймона может быть лучше, я просто говорю вам, как это сделать, не меняя того, что у вас есть.

С Документы :

from_item может быть одним из:

...
function_name ( [ argument [, ...] ] ) [ AS ] alias [ ( column_alias [, ...] | column_definition [, ...] ) ]
function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] )

Другими словами, позже говорится:

Если функция была определена как возвращая тип данных записи, затем псевдоним или ключевое слово AS должно быть настоящее, затем колонка определение списка в форме ( имя_столбца data_type [, ...]). список определений столбцов должен соответствовать фактическое количество и типы столбцов возвращается функцией.

Я думаю, что псевдоним - это опция, только если вы где-то заранее определили тип (например, если вы имитируете вывод предопределенной таблицы или действительно использовали CREATE TYPE ... не цитируйте меня по этому поводу) хотя.)

Итак, я думаю, вам нужно что-то вроде:

SELECT *
  FROM reports.get_agent_activity_breakdown('percentage', NULL, NULL)
    AS (agent_id integer, portal_user_id integer, total something, ...)

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

2 голосов
/ 24 октября 2009

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

0 голосов
/ 22 ноября 2009

вы не можете изменить количество выходных столбцов, но вы можете использовать refcursor и вы можете вернуть открытый курсор.

больше на http://okbob.blogspot.com/2008/08/using-cursors-for-generating-cross.html

0 голосов
/ 26 октября 2009

И ответы Саймона, и Кева хорошие, но в итоге я разделил вызовы к базе данных на два запроса:

  1. Создайте запрос, используя методы конструктора запросов, которые я включил в вопрос, и верните его в приложение.
  2. Вызовите запрос напрямую и верните эти данные.

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

...