1

我想创建一个 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

4 回答 4

2

如果你真的想动态创建这样的表,也许只是在函数中创建一个临时表,这样它就可以有你想要的任何列。让函数将所有行插入表中,而不是返回它们。该函数可以只返回表的名称,或者您可以只返回一个您知道的确切表名称。运行该函数后,您只需从表中选择数据。该函数还应检查临时表是否存在,以便删除或截断它。

于 2009-10-23T21:27:56.103 回答
2

西蒙的回答最终可能会更好,我只是告诉你如何在不改变你所拥有的情况下做到这一点。

文档

from_item 可以是以下之一:

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

换句话说,后来它说:

如果函数已定义为返回记录数据类型,则必须存在别名或关键字 AS,后跟格式为 ( column_name 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 上选择两次。

于 2009-10-23T21:36:18.847 回答
0

Both Simon's and Kev's answers are good ones, but what I ended up doing was splitting the calls to the database into two queries:

  1. Build the query using the query constructor methods I included in the question, returning that to the application.
  2. Call the query directly, and return that data.

This is safe in my case because the dynamic column list is not subject to frequent change, so I don't need to worry about the query's target data changing in between these calls. Otherwise, though, my method might not work.

于 2009-10-26T14:45:23.667 回答
0

you cannot change number of output columns, but you can to use refcursor, and you can return opened cursor.

more on http://okbob.blogspot.com/2008/08/using-cursors-for-generating-cross.html

于 2009-11-22T20:24:14.487 回答