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?
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 . LeDISTINCT ON
les expressions sont interprétées en utilisant les mêmes règles que pourORDER BY
(voir au dessus). Notez que la "première ligne" de chaque ensemble est imprévisible à moins queORDER 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,
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;
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;
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
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;
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.
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);