在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的形式返回查询时,出现以下错误:

错误:查询的结构与函数结果类型不匹配Estado de SQL:42804 Detalhe:返回的类型字符变化与第2列中的预期类型字符变化(40)不匹配。 )第31行,位于RETURN QUERY

在同一个Wiki页面中,有指向该版本的函数C版本的链接。我不知道如何安装它,但是我想知道这个版本是否可以解决我的问题。

同时,有没有一种方法可以更改上述函数,使其返回与输入列完全相同的类型?

Answers:


17

DISTINCT ON()

恰如其分,这正是DISTINCT ON()功能所在(请勿与混淆DISTINCT

SELECT DISTINCT ON ( expression [, ...] ) 仅保留给定表达式等于的每组行的第一行。该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。有两个重要的区别,

  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 BYDISTINCT ON

使用有序集合聚合函数

我想很多人都在寻找first_value有序组聚合函数。只是想把那个扔出去。如果函数存在,它将看起来像这样:

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

但是,a,您可以执行此操作。

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

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 LATERAL中使用)
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不是一个聚合函数,它显示具有某个公共值(您的示例猫)的的所有记录,这些记录根据某个顺序(您的示例日期)被评估为第一个。我的需求是不同的。我需要在同一选择中,通过选择第一个非null值来汇总几列。也就是说,它应该为GROUP BY中的每个值组合输出一条记录。
亚历山大·内托

2
可以通过以下方法使上述方法起作用:select distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from ...。可能效率低下,但足以让我继续进行原型设计。绝对是要重温的东西!
Max Murphy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.