PostgreSQLには、タイプセーフなfirst()集約関数がありますか?


21

完全な質問の書き直し

First()集計関数を探しています。

ここで、ほとんど機能するものを見つけました。

CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

問題は、varchar(n)列がfirst()関数を通過するときに、単純なvarchar(サイズなし)に変換されることです。関数でクエリをRETURNS SETOF anyelementとして返そうとすると、次のエラーが表示されます。

エラー:クエリの構造がSQL:42804関数結果の型と一致しません。 )RETURN QUERYの31行目

同じwikiページには、上記を置き換える関数のCバージョンへのリンクがあります。インストール方法はわかりませんが、このバージョンで問題を解決できるかどうかは疑問です。

一方、上記の関数を変更して、入力列とまったく同じタイプを返す方法はありますか?

回答:


17

DISTINCT ON()

サイドノートとして、これは正確に何をするかですDISTINCT ON()(混同しないでくださいDISTINCT

SELECT DISTINCT ON ( expression [, ...] ) 指定された式がequalと評価される各行セットの最初の行のみを保持します。DISTINCT ON式はと同じ規則を使用して解釈されるORDER BY(上記参照)。各セットの「最初の行」はORDER BY、目的の行が最初に表示されるようにするために使用されない限り、予測できないことに注意してください。例えば

もしあなたが書くなら

SELECT myFirstAgg(z)
FROM foo
GROUP BY x,y;

効果的に

SELECT DISTINCT ON(x,y) z
FROM foo;
-- ORDER BY z;

その点で最初にかかるz。2つの重要な違いがあります。

  1. さらに集約することなく、他の列を選択することできます。

    SELECT DISTINCT ON(x,y) z, k, r, t, v
    FROM foo;
    -- ORDER BY z, k, r, t, v;
  2. あるので何もGROUP BY次のことができないことと(実際の)骨材を使用していません。

    CREATE TABLE foo AS
    SELECT * FROM ( VALUES
      (1,2,3),
      (1,2,4),
      (1,2,5)
    ) AS t(x,y,z);
    
    SELECT DISTINCT ON (x,y) z, sum(z)
    FROM foo;
    
    -- fails, as you should expect.
    SELECT DISTINCT ON (x,y) z, sum(z)
    FROM foo;
    
    -- would not otherwise fail.
    SELECT myFirstAgg(z), sum(z)
    FROM foo
    GROUP BY x,y;

忘れないで ORDER BY

また、私はそれを太字にしなかったが、それから私は今

ORDER BYを使用して目的の行が最初に表示されるようにしない限り、各セットの「最初の行」は予測できないことに注意してください。例えば

常にORDER BYwithを使用するDISTINCT ON

順序付き集合関数の使用

私は多くの人々が探している想像しfirst_value順序セットの集計機能。ただそこに放り出したかっただけです。関数が存在する場合、次のようになります。

SELECT a, b, first_value() WITHIN GROUP (ORDER BY z)    
FROM foo
GROUP BY a,b;

しかし、悲しいかなあなたはこれを行うことができます。

SELECT a, b, percentile_disc(0) WITHIN GROUP (ORDER BY z)   
FROM foo
GROUP BY a,b;

1
この答えの問題は、選択リストに1つの集約が必要な場合にのみ機能することです。これは、質問によって暗示されていません。たとえば、1つのテーブルから選択し、複数の順序付けされた最初の値を見つけたいDISTINCT ON場合、この場合は機能しません。これは集計関数ではなく、実際にデータをフィルタリングするため、一度しか実行できません。
DB140141

6

イェイ、PostgreSQL 9.4+のいくつかの機能を使用して、あなたのケースで簡単な方法を見つけました

この例を見てみましょう:

select  (array_agg(val ORDER BY i))[1] as first_value_orderby_i,
    (array_agg(val ORDER BY i DESC))[1] as last_value_orderby_i,
    (array_agg(val))[1] as last_value_all,
    (array_agg(val))[array_length(array_agg(val),1)] as last_value_all
   FROM (
        SELECT i, random() as val
        FROM generate_series(1,100) s(i)
        ORDER BY random()
    ) tmp_tbl

あなたの場合に役立つことを願っています。


このソリューションの問題は、DOMAINデータ型やその他の小さな例外では機能しないことです。また、はるかに複雑で時間がかかり、データセット全体の配列を構築します。簡単な解決策は、カスタム集計を作成することですが、これまでのところ、理想的な解決策は見つかりませんでした。彼らはあなたが集計(FILTER文で、またはCROSSの横のJOIN)使用することができますと同じように使用することはできませんので、ウィンドウ関数は、また悪いです
AlexanderMP

5

質問に対する直接的な答えではありませんが、first_valueウィンドウ機能を試してください。それはこのように動作します:

CREATE TABLE test (
    id SERIAL NOT NULL PRIMARY KEY,
    cat TEXT,
    value VARCHAR(2)
    date TIMESTAMP WITH TIME ZONE

);

次に、各cat(カテゴリ)の最初のアイテムが必要な場合は、次のようにクエリを実行します。

SELECT
    cat,
    first_value(date) OVER (PARTITION BY cat ORDER BY date)
FROM
    test;

または:

SELECT
    cat,
    first_value(date) OVER w
FROM
    test
WINDOW w AS (PARTITION BY cat ORDER BY date);

申し訳ありませんが、これは私のユースケースには当てはまらないと思います。First_valueは集計関数ではなく、特定の順序(サンプルの日付)に従って最初であると評価される特定の共通値(サンプルの猫)を持つすべてのレコードを表示します。私のニーズは異なります。同じselectで、最初のnull以外の値を選択して複数の列を集計する必要があります。つまり、GROUP BYの値の組み合わせごとに1つのレコードを出力する必要があります。
アレクサンドルネトー14年

2
上記を、distinctをmixにスローすることで機能させることができますselect distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from ...。おそらく非効率ですが、プロトタイプを作成するには十分です。間違いなく再訪するもの!
マックスマーフィー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.