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

我通过Ruby宝石“续集”使用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

有人知道我哪里做错了吗?

477516 次浏览

PostgreSQL没有定义round(double precision, integer)。由于@Mike Sherrill“回忆猫”在评论中解释的原因,采用精度的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你不需要任何前导空格。

(这是维基!请编辑以增强!))

尝试旧式的类型转换语法,

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
0.666 0.667 0.667 数字 双精度 数字

轮(浮动,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: \df round命令,在psql重载后,将显示类似于此表的内容

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

其中,当你不使用模式时,浮动double precision的同义词,而myschemapublicpg_catalog函数是默认函数,请参见引导内置数学函数

舍入和格式化

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

SELECT round(x::numeric,2), trunc(x::numeric,2), to_char(x, 'FM99.99')
FROM (SELECT 2.0/3) t(x);
trunc to_char
0.67 0.66
人力资源/ > < / div > <

笔记

问题的原因

没有过载在一些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
中返回浮动类型

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

因此,在不同的表示中,我们有一个对数字数的不同解释要舍入。将数字x舍入为一个较短的近似值,“小数”较少;(相对于它原来的d数字),当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


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

解决方案:你需要添加类型转换,然后它将工作

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

根据布莱安的响应,可以这样做来限制查询中的小数。我把km/h转换成m/s,然后用动态图表示,但是用动态图表示的时候看起来很奇怪。在查询中进行计算时看起来很好。这是在postgresql 9.5.1上。

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

试试这个:

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

或者仅仅是:

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

您可以使用下面的函数

 SELECT TRUNC(14.568,2);

结果将显示:

14.56

你也可以将变量转换为期望类型:

 SELECT TRUNC(YOUR_VAR::numeric,2)

试着把你的列转换成一个数字:

SELECT ROUND(cast(some_column as numeric),2) FROM table
SELECT ROUND(SUM(amount)::numeric, 2) AS total_amount
FROM transactions

为:200234.08