1

オンザフライで返される列のセットを構築する postgres 関数を作成したいと考えています。つまり、キーのリストを受け取り、キーごとに 1 つの列を作成し、その列のセットが何であれ、それから構成されるレコードを返す必要があります。簡単に言うと、コードは次のとおりです。

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

もちろん、'Label 1' と 'Label 2' というラベルの付いた列は、アクティビティ テーブルのコンテンツで定義された一連のアクティビティに依存しているため、関数を呼び出すときに予測できません。この情報にアクセスする関数を作成するにはどうすればよいですか?

4

4 に答える 4

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 を 2 回選択することになります。

于 2009-10-23T21:36:18.847 に答える
2

そのようなテーブルを動的に作成したい場合は、関数内に一時テーブルを作成して、必要な列を含めることができます。関数がすべての行を返す代わりにテーブルに挿入するようにします。この関数は、テーブルの名前のみを返すことも、知っている正確なテーブル名を 1 つだけ返すこともできます。その関数を実行した後、テーブルからデータを選択するだけです。この関数は、一時テーブルが存在するかどうかも確認する必要があるため、それを削除または切り捨てる必要があります。

于 2009-10-23T21:27:56.103 に答える
0

Simon と Kev の両方の回答は良いものですが、私が最終的に行ったのは、データベースへの呼び出しを 2 つのクエリに分割することでした。

  1. 質問に含めたクエリ コンストラクター メソッドを使用してクエリを作成し、それをアプリケーションに返します。
  2. クエリを直接呼び出して、そのデータを返します。

私の場合、動的な列リストは頻繁に変更されることがないため、これは安全です。したがって、これらの呼び出しの間にクエリのターゲット データが変更されることを心配する必要はありません。そうしないと、私の方法が機能しない可能性があります。

于 2009-10-26T14:45:23.667 に答える
0

出力列の数を変更することはできませんが、refcursor を使用することはでき、開いているカーソルを返すことができます。

http://okbob.blogspot.com/2008/08/using-cursors-for-generating-cross.htmlの詳細

于 2009-11-22T20:24:14.487 に答える