返回任何(第一个)遇到的值的聚合函数

Krz*_*iak 5 postgresql aggregate

如何告诉 PostgreSQL 返回第一个遇到的值而不是聚合列?

                          Table "public.cache"
  Column   |  Type   | Modifiers | Storage | Stats target | Description 
-----------+---------+-----------+---------+--------------+-------------
 user_id   | integer |           | plain   |              | 
 object_id | integer |           | plain   |              | 
 data      | integer |           | plain   |              | 
 data2     | boolean |           | plain   |              | 
Indexes:
    "cache_object_id_user_id_key" UNIQUE CONSTRAINT, btree (object_id, user_id)
    "cache_user_id_object_id_key" UNIQUE CONSTRAINT, btree (user_id, object_id)
Has OIDs: no
Run Code Online (Sandbox Code Playgroud)

按 object_id 和 data2 进行查询分组将使哈希聚合,这是我想避免的。

SELECT object_id, data2 FROM cache GROUP BY object_id, data2;
Run Code Online (Sandbox Code Playgroud)

我发现bool_or()但它会扫描坏情况下的所有值。

SELECT object_id, bool_or(data2) FROM cache GROUP BY object_id;
Run Code Online (Sandbox Code Playgroud)

而且,任何数据类型都没有这样的函数。我想要做的是从 data2 列获取任何值,以便引擎不必迭代所有行。

如果数据列是整数呢?

ype*_*eᵀᴹ 7

有多种方法可以做到这一点,具有不同的性能,具体取决于数据的分布(不同object_id值的数量等)。

\n\n

最容易编写的查询 - 但不一定是最有效的,当然是使用聚合,MIN()或者MAX()

\n\n
SELECT object_id, MIN(data2) AS data2 \nFROM cache \nGROUP BY object_id ;\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果你有一个索引,那么(object_id, data2)在最近版本的 Postgres 中,这不会太糟糕,它可以使用仅索引扫描作为执行计划。

\n\n
\n\n

另一种方法是使用DISTINCT ON语法。与上面相同的索引会有所帮助:

\n\n
SELECT DISTINCT ON (object_id)\n    object_id, data2 \nFROM cache \nORDER BY object_id ;\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

如果与表大小相比,值的数量较少object_id,则不同的方法会更有效。假设您还有另一个表(例如objects),object_id其主键为:

\n\n
SELECT o.object_id, c.data2 \nFROM objects AS o\n  CROSS JOIN LATERAL \n     ( SELECT data2\n       FROM cache AS c \n       WHERE c.object_id = o.object_id\n       ORDER BY c.data2 \n       LIMIT 1\n     ) AS c ;\n
Run Code Online (Sandbox Code Playgroud)\n\n

需要相同的索引。这ORDER BY不是必需的,但有了索引,就不会影响效率。如果您没有objects表,那么该部分必须替换为:

\n\n
---\nFROM ( SELECT DISTINCT object_id FROM cache) AS o\n  CROSS JOIN LATERAL \n---\n
Run Code Online (Sandbox Code Playgroud)\n\n

但你会损失一些效率,尤其是在旧版本中。在这种情况下,您可以将此子查询替换为有效遍历索引的复杂递归查询object_id。有关更多详细信息,请参阅 Posgres 文档:松散索引扫描

\n\n

另请阅读埃尔文在相关问题中的精彩回答:

\n\n\n\n
\n\n

最后但并非最不重要的一点是,出现问题的主要原因是:

\n\n
\n

我有非规范化的数据,我想避免......(我知道该组中的所有值都是相等的布尔值)。

\n
\n\n

规范化表将导致更高效的查询。

\n


小智 5

我已经多次遇到同样的问题,但 StackExchange 对我没有帮助。您可以创建用户定义的聚合函数

此类函数将在组中的行上运行。至少您需要提供一个聚合函数。在我的例子中是func_first_value.

下面的函数variadic argument type适用于 Postgres 可以推导的任何类型。默认值为NULL。我添加了前缀,以便在必要时可以轻松删除它们。

CREATE OR REPLACE FUNCTION func_first_value(v0 anyelement, v1 anyelement) RETURNS anyelement AS $$
BEGIN
    IF v0 IS NOT NULL THEN
        RETURN v0;
    END IF;
    RETURN v1;
END;
$$ LANGUAGE plpgsql;

-- test function
SELECT func_first_value('text'::VARCHAR, NULL), func_first_value(NULL::VARCHAR, NULL), func_first_value(NULL, 'text'::VARCHAR);

CREATE AGGREGATE agg_first_value (anyelement)
(
    sfunc = func_first_value,
    stype = anyelement
);
Run Code Online (Sandbox Code Playgroud)

在您的情况下,您需要查询:

SELECT object_id, agg_first_value(data2) FROM cache GROUP BY object_id;
Run Code Online (Sandbox Code Playgroud)

为了避免扫描所有值,需要提供SORTOPsort_operator作为 Postgres 文档的名称。我没有找到任何如何使用它的示例。MIN此外,编写自己的聚合函数比使用或MAX内置 SQL 聚合慢很多(约 6 倍) 。


Len*_*art 2

这是一种尝试:

SELECT distinct object_id
     , first_value(data2) over (partition by object_id) 
FROM cache
Run Code Online (Sandbox Code Playgroud)

first_value 几乎适用于任何类型。