web-dev-qa-db-fra.com

Dans PostgreSQL, existe-t-il une fonction d'agrégation First () de type sécurisé?

Réécriture complète de la question

Je recherche une fonction d'agrégation First ().

Ici J'ai trouvé quelque chose qui fonctionne presque:

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
);

Le problème est que lorsqu'une colonne varchar (n) passe par la première fonction (), elle est convertie en varchar simple (sans taille). En essayant de renvoyer la requête dans une fonction en tant qu'élément RETURNS SETOF, j'obtiens l'erreur suivante:

ERREUR: la structure de la requête ne correspond pas au type de résultat de la fonction ) ligne 31 à RETURN QUERY

Dans la même page wiki, il y a un lien vers un Version C de la fonction qui remplacerait ce qui précède. Je ne sais pas comment l'installer, mais je me demande si cette version pourrait résoudre mon problème.

En attendant, existe-t-il un moyen de modifier la fonction ci-dessus afin qu'elle renvoie exactement le même type de la colonne d'entrée?

22
Alexandre Neto

DISTINCT ON()

Juste comme note latérale, c'est précisément ce que fait DISTINCT ON() (à ne pas confondre avec DISTINCT)

SELECT DISTINCT ON ( expression [, ...] ) ne conserve que la première ligne de chaque ensemble de lignes où les expressions données sont égales . Le DISTINCT ON les expressions sont interprétées en utilisant les mêmes règles que pour ORDER BY (voir au dessus). Notez que la "première ligne" de chaque ensemble est imprévisible à moins que ORDER BY est utilisé pour s'assurer que la ligne souhaitée apparaît en premier. Par exemple

Donc, si vous deviez écrire,

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

C'est effectivement

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

En ce qu'il faut le premier z. Il y a deux différences importantes,

  1. Vous pouvez aussi sélectionner d'autres colonnes sans frais d'agrégation supplémentaire.

    SELECT DISTINCT ON(x,y) z, k, r, t, v
    FROM foo;
    -- ORDER BY z, k, r, t, v;
    
  2. Parce qu'il n'y a pas GROUP BY vous pouvez pas utiliser des agrégats (réels) avec.

    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;
    

N'oubliez pas ORDER BY

Aussi, même si je ne l'ai pas mis en gras, je vais maintenant

Notez que la "première ligne" de chaque ensemble est imprévisible, sauf si ORDER BY est utilisé pour garantir que la ligne souhaitée apparaît en premier. Par exemple

Utilisez toujours un ORDER BY avec DISTINCT ON

Utilisation d'une fonction d'agrégation d'ensemble ordonné

J'imagine que beaucoup de gens recherchent first_value, Fonctions d'agrégat définies par ordre . Je voulais juste jeter ça là-bas. Cela ressemblerait à ceci, si la fonction existait:

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

Mais, hélas, vous pouvez le faire.

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

Oui, j'ai découvert un moyen simple avec votre cas en utilisant certaines fonctionnalités de PostgreSQL 9.4+

Voyons cet exemple:

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

J'espère que cela vous aidera dans votre cas.

6
Mabu Kloesen

Pas une réponse directe à votre question, mais vous devriez essayer le first_value fonction de fenêtre. Cela fonctionne comme ceci:

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

);

Ensuite, si vous voulez le premier élément de chaque cat (catégorie), vous interrogerez comme ça:

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

ou:

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