在业务系统里,运营常常要看某类订单占总订单的比例,或者各个渠道的转化占比。如果把这些数据拉到表格软件里手动算,既慢又容易错。其实关系型数据库本身就能在一条SELECT语句里完成加减乘除、取余、四舍五入等数学运算,直接算出百分比字段并返回给前端。

一、SQL中的基础数学运算符
标准SQL支持加(+)、减(-)、乘(*)、除(/)、取模(%)等运算符,它们的优先级和日常数学一致,乘除高于加减,必要时用括号改变顺序。在SELECT列表里,你可以直接写表达式,数据库会逐行计算并产生一个新列。
需要注意的是,不同数据库对整数除法的处理不同。以MySQL为例,两个INT相除如果除不尽,会自动转为DECIMAL并保留小数;而PostgreSQL里两个整数相除只保留整数部分,例如5/2得到2。因此做百分比时,一定要先把其中一个操作数转成小数,否则算出来的比例永远是0或者整数,导致结果严重失真。
-- PostgreSQL中整数除法会截断小数 SELECT 5 / 2 AS int_div; -- 结果为2 -- 乘以1.0或使用CAST转为数值类型 SELECT (5 * 1.0) / 2 AS float_div; -- 结果为2.5 SELECT CAST(5 AS NUMERIC) / 2 AS num_div; -- 结果为2.5
二、快速计算字段百分比的常用写法
最典型的场景是统计某状态的记录数占全部记录数的百分比。我们可以用COUNT配合条件表达式,或者先SUM再除。下面以统计“已支付”订单占比为例,展示两种数据库的写法。
在MySQL中,可以直接用COUNT(条件)这种技巧,因为条件为真时返回1,假时返回0,合计就是该状态的记录数。乘100.0再除以总COUNT,就能得到百分比数值。用ROUND控制到两位小数,最后用CONCAT拼上百分号,前端拿到的是可读字符串。
-- MySQL:已支付订单占比(带百分号)
SELECT
CONCAT(
ROUND(
COUNT(CASE WHEN status = 'paid' THEN 1 END) * 100.0 / COUNT(*),
2
),
'%'
) AS paid_rate
FROM orders;
PostgreSQL没有COUNT(条件)的简写,但可以用SUM加CASE把符合条件的记为1再求和。下面这段等效代码同样先转浮点后乘100,并用ROUND保留两位小数。如果总笔数为0,除法会报错,因此用NULLIF规避零除。
-- PostgreSQL:已支付订单占比(数值型)
SELECT
ROUND(
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) * 100.0
/ NULLIF(COUNT(*), 0),
2
) AS paid_rate
FROM orders;
三、处理NULL与缺失值的坑
当参与运算的字段可能为NULL时,任何数和NULL做数学运算结果都是NULL,这会使得整行百分比消失。实际数据里,退款金额、优惠券抵扣等字段经常为空,直接相除会让报表出现空白。
使用COALESCE函数把NULL转成0或者合适的默认值,可以保证计算继续进行。下例计算平均客单价时,把NULL的discount当成0处理,避免分子变NULL。同时注意分母也要用COALESCE或NULLIF保护,防止除以NULL或0。
-- 计算净销售额占比,NULL折扣视为0
SELECT
ROUND(
SUM(amount - COALESCE(discount, 0)) * 100.0
/ NULLIF(SUM(amount), 0),
2
) AS net_rate
FROM orders;
四、在查询中执行更复杂的数学运算
除了百分比,SQL还能做加权得分、年化收益率等运算。比如电商要给商品算综合分,点击率占三成,转化率占七成,就可以在SELECT里直接写加权平均。这种计算下推到数据库,比应用层遍历要快得多。
下面的示例用子查询先算出每个商品的点击数和下单数,再在外层用浮点运算得到综合分。注意用1.0强制浮点,否则整数乘法会截断。借助这种写法,运营筛选高分商品时无需额外ETL任务。
SELECT
product_id,
ROUND(
(clk * 1.0 / pv) * 0.3 + (pay * 1.0 / clk) * 0.7,
4
) AS score
FROM (
SELECT
product_id,
COUNT(*) AS pv,
SUM(CASE WHEN event = 'click' THEN 1 ELSE 0 END) AS clk,
SUM(CASE WHEN event = 'pay' THEN 1 ELSE 0 END) AS pay
FROM track_log
GROUP BY product_id
) t
WHERE clk > 0;
五、性能与可读性建议
把数学运算放在数据库层,能减少数据传输量,但也要避免对大表做重复除法。如果同一比例在多个地方用到,可以用子查询或CTE先计算一次,再在外层引用,这样既清晰又方便数据库优化器重用。
另外,给参与运算的字段建好索引并不一定加速计算,但能加速COUNT和SUM的扫描。写复杂表达式时,适当用列别名和格式化让SQL更易维护。掌握这些后,你完全可以在一条查询里完成绝大部分报表所需的数学加工。