【问题标题】:How to round an average to 2 decimal places in PostgreSQL?如何在 PostgreSQL 中将平均值舍入到小数点后 2 位?
【发布时间】:2012-10-18 06:19:57
【问题描述】:

我正在通过 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

有谁知道我做错了什么?

【问题讨论】:

  • 您的错误信息与您问题中的代码不匹配。
  • 抛开语法错误不谈,closely related question on dba.SE 揭示了在 PostgreSQL 中舍入双精度数字。
  • @muistooshort,感谢您指出这一点。它应该说'round',它说'avg'。已编辑。
  • 为了搜索结果,我也得到这个提示作为提示的输出:HINT: No function matches the given name and argument types. You might need to add explicit type casts.

标签: sql ruby postgresql sequel


【解决方案1】:

PostgreSQL 没有定义round(double precision, integer)。由于 @Mike Sherrill 'Cat Recall' 在 cmets 中解释的原因,需要精度的 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(参见手册中的data type formatting functions),它允许您指定格式并为您提供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),但仍然得到我在问题中描述的错误。
  • 我刚刚在 PgAdmin 和 Ruby 上运行了 ROUND(CAST(FLOAT8 '3.1415927' AS NUMERIC),2);。使用 PgAdmin,我得到 3.14,但使用 Ruby(使用他 Sequel gem)我得到 '0.314E1'。我想知道这是为什么...
  • “出于某种奇怪的原因,采用精度的 round 版本仅适用于数字。” 浮点数是“有用的近似值”。如果您要求代码将浮点数四舍五入到小数点后两位,返回 another 浮点数,则无法保证最接近“正确”答案的近似值只有两位数小数点右边。数字是有效缩放的整数;他们没有这个问题。
  • @Catcall 好点——rounddouble 版本需要返回numeric 或(呃)text,所以它不妨采用numeric 参数。
  • 对于那些试图找到@Catcall 的评论的人:现在是 Mike Sherrill 'Cat Recall'
【解决方案2】:

      ((这是一个 Wiki!请编辑以增强!))

也可以尝试使用旧语法进行转换,

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

适用于任何版本的 PostgreSQL。
...但是,作为最终解决方案,您可以overload 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
0.666 0.667 0.667 numeric double precision numeric

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

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

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:命令\df round,在psql重载后,会显示类似这个表格的东西

架构 |姓名 |结果 |争论 ------------+-------+------------+------------------ 架构 |圆|数字 |浮点数,整数 架构 |圆|浮动 |漂浮,漂浮 pg_catalog |圆|浮动 |漂浮 pg_catalog |圆|数字 |数字 pg_catalog |圆|数字 |数字,整数

其中 floatdouble precision 的同义词,而 myschema 在您不使用架构时是 publicpg_catalog 函数是默认函数,请参阅build-in math functions 指南。

舍入和格式化

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

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 子句对于像这样的代码 sn-ps 非常重要,因为正如指南中所说:“允许优化器在查询使用常量参数调用函数时预先评估函数”

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

结论:以上ROUND(float,int)函数,经过优化,快@CraigRinger的回答;它将编译为(完全)相同的内部表示。因此,虽然它不是 PostgreSQL 的标准,但它可以成为您项目的标准,通过一个集中且可重用的“sn-ps 库”,例如pg_pubLib


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

有些人认为 PostgreSQL 对浮点数数据类型进行四舍五入是没有意义的,因为 float 是二进制表示,它需要对位数或其十六进制表示进行四舍五入。

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

  • 'dec' 用于“十进制表示”,
  • 'bin' 用于“二进制”表示和
  • 'hex' 用于十六进制表示。

因此,在不同的表示中,我们对要四舍五入的位数有不同的解释。将数字 x 舍入一个近似较短的值,使用较少的“小数位”(其原始 d 位),当 d 时会更短正在计算二进制数字而不是十进制或十六进制。

没有C++,使用“纯SQL”并不容易,但是这段代码sn-ps将说明并且可以用作解决方法:

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

【解决方案3】:

试试这个:

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

或者简单地说:

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

【讨论】:

  • 我发现这是我对这个问题的日常回答更加简洁和严格的前进方式。 :弓:
  • 这里也一样!非常简短且有用的解决方案。
  • 这不是代数解,仅对打印报告有效。
【解决方案4】:

你可以使用下面的函数

 SELECT TRUNC(14.568,2);

结果会显示:

14.56

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

 SELECT TRUNC(YOUR_VAR::numeric,2)

【讨论】:

  • 同样的问题,需要转换为数字并返回数字
【解决方案5】:

尝试将您的列转换为类似的数字:

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

【讨论】:

  • 在 pg10 中工作,无需使用数值进行转换。
  • @SergioBelevskij ERROR: function round(double precision, integer) does not exist,需要转换为数字。
  • 如果您的 pg 版本低于 10,请尝试将值转换为数字:round (cast(doublecolumn as numeric),2)
【解决方案6】:
SELECT ROUND(SUM(amount)::numeric, 2) AS total_amount
FROM transactions

给:200234.08

【讨论】:

    【解决方案7】:

    根据Bryan's 的回复,您可以这样做以限制查询中的小数。我将 km/h 转换为 m/s 并将其显示在 dygraphs 中,但是当我在 dygraphs 中进行转换时,它看起来很奇怪。在查询中进行计算时看起来不错。这是在 postgresql 9.5.1 上。

    select date,(wind_speed/3.6)::numeric(7,1) from readings;
    

    【讨论】:

      【解决方案8】:

      错误:函数round(双精度,整数)不存在

      解决方案:您需要添加类型转换才能工作

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-08-14
        • 1970-01-01
        • 2014-06-28
        • 1970-01-01
        • 2021-12-01
        • 1970-01-01
        相关资源
        最近更新 更多