如何在PostgreSQL中将平均值四舍五入到2位小数?

332
我正在使用Ruby gem 'sequel'连接PostgreSQL。我试图将数字四舍五入为两位小数。以下是我的代码:
```html

我正在使用Ruby gem 'sequel'连接PostgreSQL。我试图将数字四舍五入为两位小数。以下是我的代码:

```
SELECT ROUND(AVG(some_column),2)    
FROM table

我遇到了以下错误:

PG::Error: ERROR:  function round(double precision, integer) does 
not exist (Sequel::DatabaseError)

运行以下代码时,我没有收到任何错误:

SELECT ROUND(AVG(some_column))
FROM table

有人知道我做错了什么吗?


3
您的错误信息与您问题中的代码不匹配。 - mu is too short
除了语法错误之外,dba.SE上的这个相关问题揭示了在PostgreSQL中舍入双精度数字的一些信息。 - Erwin Brandstetter
@muistooshort,感谢您指出这一点。在“avg”处应该写成“round”。已经修改。 - user1626730
为了搜索结果的缘故,我还从提示中获得了以下提示输出:提示:没有与给定名称和参数类型匹配的函数。您可能需要添加显式类型转换。 - Vzzarr
8个回答

433

PostgreSQL没有定义round(double precision, integer)。正如评论中@Mike Sherrill 'Cat Recall'所解释的那样,接受精度参数的round函数仅适用于numeric类型。

regress=> SELECT round( float8 '3.1415927', 2 );
ERROR:  function round(double precision, integer) does not exist

regress=> \df *round*
                           List of functions
   Schema   |  Name  | Result data type | Argument data types |  Type  
------------+--------+------------------+---------------------+--------
 pg_catalog | dround | double precision | double precision    | normal
 pg_catalog | round  | double precision | double precision    | normal
 pg_catalog | round  | numeric          | numeric             | normal
 pg_catalog | round  | numeric          | numeric, integer    | normal
(4 rows)

regress=> SELECT round( CAST(float8 '3.1415927' as numeric), 2);
 round 
-------
  3.14
(1 row)

(在上述代码中,请注意float8只是double precision的简写别名。您可以看到PostgreSQL在输出中展开它)。

您必须将要四舍五入的值强制转换为numeric,才能使用round的两参数形式。只需添加::numeric进行简写转换,例如round(val::numeric,2)


如果您要为用户格式化显示,请勿使用round。请使用to_char(参见手册中的数据类型格式化函数),该函数允许您指定格式并提供一个text结果,不受客户端语言对numeric值的影响。例如:

regress=> SELECT to_char(float8 '3.1415927', 'FM999999999.00');
    to_char    
---------------
 3.14
(1 row)

to_char会在格式化时对数字进行四舍五入。前缀FM告诉to_char不要使用前导空格进行填充。


当我尝试运行ROUND(CAST(FLOAT8 '3.1415927' AS NUMERIC),2);时,输出结果为'0.314E1'。而且我已经按照ROUND(AVG(val),2)的方式编写了代码,但仍然出现了我在问题中描述的错误。 - user1626730
我刚刚在PgAdmin和Ruby上运行了ROUND(CAST(FLOAT8 '3.1415927' AS NUMERIC),2);。在PgAdmin中,结果是3.14,但使用Ruby(使用Sequel宝石)却得到了'0.314E1'。我想知道为什么会这样... - user1626730
24
由于某种奇怪的原因,只有针对数字的版本才支持四舍五入取精度。浮点数是“有用的近似值”。如果要求代码将浮点数四舍五入到小数点后两位,并返回另一个浮点数,则不能保证最接近“正确”答案的近似值仅有两位小数。数值型数据是有效缩放的整数,它们没有这个问题。 - Mike Sherrill 'Cat Recall'
@Catcall 说得好 - rounddouble 版本需要返回 numeric 或者(呃) text,所以它可以直接接受一个 numeric 参数。 - Craig Ringer
7
想要找@Catcall的评论的人现在可以在“Mike Sherrill 'Cat Recall'”下找到它。 - 18446744073709551615
显示剩余7条评论

162

((这是一个维基百科!请编辑以改进!))

也可以尝试旧的类型转换语法,

SELECT ROUND( AVG(some_column)::numeric, 2 ) FROM table;

它可以与任何版本的PostgreSQL一起使用。
...但作为确定的解决方案,您可以重载ROUND函数。

以重载为转换策略

CREATE FUNCTION ROUND(float,int) RETURNS NUMERIC AS $f$
  SELECT ROUND( CAST($1 AS numeric), $2 )
$f$ language SQL IMMUTABLE;

现在您的指令将正常工作,请尝试进行完整比较:
SELECT trunc(n,3), round(n,3) n_round, round(f,3) f_round,
       pg_typeof(n) n_type, pg_typeof(f) f_type, pg_typeof(round(f,3)) f_round_type
FROM (SELECT 2.0/3.0, 2/3::float) t(n,f);
trunc n_round f_round n_type f_type f_round_type
截断 四舍五入(保留整数部分) 四舍五入(保留小数点后第三位) 数字类型 双精度浮点型 数字类型

ROUND(float,int)函数的f_round返回一个(十进制)NUMERIC数据类型,对于某些应用程序来说这是可以接受的:问题解决了!

在其他应用程序中,我们需要将结果作为浮点数返回。一种替代方法是使用round(f,3)::float或创建一个round_tofloat()函数。 另一种替代方法是再次重载ROUND函数,并在定义准确性时使用浮点数的所有范围accuracy-precision(请参见IanKenney的回答),以返回浮点数。

CREATE FUNCTION ROUND(
  input float,     -- the input number
  accuracy float   -- accuracy, the "counting unit"
) RETURNS float AS $f$
   SELECT ROUND($1/accuracy)*accuracy
$f$ language SQL IMMUTABLE;

尝试

  SELECT round(21.04, 0.05);     -- 21.05 float!
  SELECT round(21.04, 5::float); -- 20
  SELECT round(1/3., 0.0001);    -- 0.3333
  SELECT round(2.8+1/3., 0.5);   -- 3.15
  SELECT round(pi(), 0.0001);    -- 3.1416

PS:在重载后,psql命令中的\df round将显示如下表格:

 Schema     |  Name | Result  | Argument  
------------+-------+---------+------------------
 myschema   | round | numeric | float, int
 myschema   | round | float   | float, float
 pg_catalog | round | float   | float            
 pg_catalog | round | numeric | numeric   
 pg_catalog | round | numeric | numeric, int          

其中floatdouble precision的同义词,而myschema是当您不使用模式时的public。默认情况下使用pg_catalog函数,请参阅内置数学函数指南。

四舍五入和格式化

to_char函数在内部应用round过程,因此,当您的目的仅是在终端上显示最终结果时,您可以使用FM修饰符作为数字格式模式的前缀:

SELECT round(x::numeric,2), trunc(x::numeric,2), to_char(x, 'FM99.99')
FROM (SELECT 2.0/3) t(x);
round trunc to_char
0.67 0.66 .67

注意事项

问题原因

某些PostgreSQL函数缺乏重载,为什么(?):我认为“这是一种缺陷”,但@CraigRinger、@Catcall和PostgreSQL团队对“pg的历史原理”达成了共识。

关于性能和重用的说明

内置函数(如pg_catalog中的ROUND)可以进行重载而不会有性能损失,与直接强制类型转换相比。当实现用户定义的高性能转换函数时,必须采取两个预防措施:

  • IMMUTABLE子句在像这样的代码片段中非常重要,因为正如指南中所说:“允许优化器在查询使用常量参数调用函数时对函数进行预评估”

  • PLpgSQL是首选语言,除了“纯SQL”外。对于JIT优化(有时还包括并行性),language SQL可以获得更好的优化。就像复制/粘贴小段代码而不是使用函数调用一样。

结论:进行优化后,上述ROUND(float,int)函数比@CraigRinger的答案快得多;它将编译为(完全相同的)相同的内部表示形式。因此,尽管这对于PostgreSQL来说不是标准的,但它可以成为您的项目的标准,通过一个集中和可重用的“片段库”,例如pg_pubLib


舍入到第n位或其他数字表示法

有人认为,在PostgreSQL中对浮点数据类型进行舍入并没有意义,因为浮点是二进制表示法,需要将位数或其十六进制表示舍入。

好吧,我们来添加一个奇特的建议来解决这个问题...这里的目的是返回另一个重载的float类型函数,
 ROUND(float, text, int) RETURNS float
其中text是为了提供选择:

  • 'dec'表示“十进制表示法”
  • 'bin'表示“二进制表示法”
  • 'hex'表示十六进制表示。
因此,在不同的表示法中,我们对需要四舍五入的数字位数有不同的解释。使用一个近似较短值(比其原始的d个数字少"小数位")来四舍五入一个数字x,当d计算二进制而非十进制或十六进制时,将会更短。
在没有C++的情况下,仅使用“纯SQL”并不容易,但是以下代码片段可以说明并且可以用作解决方法:
-- Looking for a round_bin() function! this is only a workaround:
CREATE FUNCTION trunc_bin(x bigint, t int) RETURNS bigint AS $f$
    SELECT ((x::bit(64) >> t) << t)::bigint;
$f$ language SQL IMMUTABLE;
 
CREATE FUNCTION ROUND(
   x float, 
   xtype text,  -- 'bin', 'dec' or 'hex'
   xdigits int DEFAULT 0
) 
RETURNS FLOAT AS $f$
    SELECT CASE
       WHEN xtype NOT IN ('dec','bin','hex') THEN 'NaN'::float
       WHEN xdigits=0 THEN ROUND(x)
       WHEN xtype='dec' THEN ROUND(x::numeric,xdigits)
       ELSE (s1 ||'.'|| s2)::float
      END
    FROM (
      SELECT s1,
             lpad( 
               trunc_bin( s2::bigint, CASE WHEN xd<bin_bits THEN bin_bits - xd ELSE 0 END )::text,
               l2,
               '0'
             ) AS s2
      FROM (
        SELECT *, 
             (floor( log(2,s2::numeric) ) +1)::int AS bin_bits, -- most significant bit position
             CASE WHEN xtype='hex' THEN xdigits*4 ELSE xdigits END AS xd
        FROM (
          SELECT s[1] AS s1, s[2] AS s2, length(s[2]) AS l2
          FROM (SELECT regexp_split_to_array(x::text,'\.')) t1a(s)
        ) t1b
      ) t1c
    ) t2
$f$ language SQL IMMUTABLE;

尝试

 SELECT round(1/3.,'dec',4);     -- 0.3333 float!
 SELECT round(2.8+1/3.,'dec',1); -- 3.1 float!
 SELECT round(2.8+1/3.,'dec');   -- ERROR, need to cast string 
 SELECT round(2.8+1/3.,'dec'::text); -- 3 float
 SELECT round(2.8+1/3.,'dec',0); -- 3 float

 SELECT round(2.8+1/3.,'hex',0); -- 3 float (no change)
 SELECT round(2.8+1/3.,'hex',1); -- 3.1266
 SELECT round(2.8+1/3.,'hex',3); -- 3.13331578486784

 SELECT round(2.8+1/3.,'bin',1);  -- 3.1125899906842625
 SELECT round(2.8+1/3.,'bin',6);  -- 3.1301821767286784
 SELECT round(2.8+1/3.,'bin',12); -- 3.13331578486784

而且\df round还有以下功能:

 Schema     |  Name | Result  | Argument  
------------+-------+---------+---------------
 myschema   | round | float   | x float, xtype text, xdigits int DEFAULT 0


请参阅新的重复内容 “是否有直接舍入浮点数的函数?” - Peter Krauss

49

试试这个:

SELECT to_char (2/3::float, 'FM999999990.00');
-- RESULT: 0.67

或者简单地说:

SELECT round (2/3::DECIMAL, 2)::TEXT
-- RESULT: 0.67

7
我觉得这是一个更为简明扼要、直截了当的回答,可以让我继续我的日常事务。 :鞠躬: - craastad
3
我也是!非常简短而实用的解决方案。 - Alexey Shabramov
这不是一个代数解,它仅适用于打印报告。 - Peter Krauss

22

您可以使用以下函数

 SELECT TRUNC(14.568,2);

结果将显示:

14.56

您还可以将变量转换为所需类型:

 SELECT TRUNC(YOUR_VAR::numeric,2)

同样的问题,需要将其转换为数字并返回数字。 - Peter Krauss
1
这不是四舍五入,准确来说。您的建议输出为14.56,而如果您将该数字四舍五入,它将变为14.57。 - Reza Keshavarz

12
SELECT ROUND(SUM(amount)::numeric, 2) AS total_amount
FROM transactions

提供:200234.08


10

尝试将您的列转换为数字,例如:

SELECT ROUND(cast(some_column as numeric),2) FROM table

1
在pg10中使用数字值而无需进行强制转换。 - Sergio Belevskij
@SergioBelevskij 错误:函数round(double precision, integer)不存在,需要将其转换为numeric类型。 - questionto42
尝试将值转换为数字:如果您的pg版本低于10,则使用round(cast(doublecolumn as numeric),2) - Sergio Belevskij

4
根据Bryan的回应,您可以在查询中限制小数位数。我将km/h转换为m/s,并在dygraphs中显示它,但是在dygraphs中进行计算时看起来很奇怪。在查询中进行计算时看起来正常。这是在postgresql 9.5.1上进行的。
select date,(wind_speed/3.6)::numeric(7,1) from readings;

3

错误:函数round(double precision, integer)不存在

解决方法:需要添加类型转换,然后它将正常工作

例如:round(extract(second from job_end_time_t)::integer,0)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接