表里没有的列,可以当场算出来再交给你,这叫计算字段——它不占存储,只在查询时存在。 这一章干三件事:拼接字符串、做算术、给算出来的列起名字。 Demo 在六家供应商上拼出六行完整地址,在 20005 这张单上算出 4 行小计、合计 149.87; 两个没留邮箱的客户提醒了我们:NULL 一旦进了算术,整列都变 NULL。
前面六章都在「取现有的列」:选列、排序、筛行。从这一章开始造新列。 这也是数据库第一次参与「加工」而不是单纯「搬运」,后面第 12 章的聚集、第 14 章的子查询, 都是这条线上的。
一、计算字段:表里没有这一列
存进表里的是原始数据,交给你的时候未必要保持原样。供应商表里名字、国家分两列,
可你要的是 ACME (USA) 这样一个字符串。
两种做法:把两列取回来在应用里拼,或者让数据库拼好再返回。
计算字段就是后一种——它在 SELECT 的列位置写一个表达式,结果占一列,但表结构一点没变。
记住三条:不落库、不占空间、每次查询重算。
重算意味着它永远是最新的,也意味着它不能建索引(第 8 章那个代价的同款)。
二、拼接字段:CONCAT 与双竖线
教材用 CONCAT(vend_name, ' (', vend_country, ')'):MySQL 的拼接函数,参数不限个数,
NULL 参与就整体给 NULL。
SQLite 没有 CONCAT,用双竖线 ||:vend_name || ' (' || vend_country || ')',结果完全一样。
双竖线在 MySQL 里默认是逻辑或——'a' || 'b' 会给 0(假),
除非启动时开了 PIPES_AS_CONCAT。 这就是教材绕道用函数的原因:
同一个符号,两个引擎两种意思。
教材还强调先 RTRIM 再拼:MySQL 的 CHAR 是定长,短值会被补空格补齐,
拼起来就是 ACME (USA)。
本课的列是 TEXT,不补空格,所以 Demo 里 rtrim 前后长度一样——换个字段类型,
这条建议就从必须降级成可以不写。
三、别名:AS 不只是好看
不写别名,列名就是整段表达式:vend_name || ' (' || vend_country || ')' 会原样变成表头。
程序里取值要照抄这一长串,改一个空格就取不到。
三种写法:AS vend_title(推荐)、vend_title(省 AS,能跑但别省)、
AS "vend title"(别名含空格时加双引号)。
别名只改表头,不动数据,也不改表结构。 它的真正价值是给调用方一个稳定的名字——
表达式以后怎么改,接口不变。
四、执行算术计算
算术运算符和别的语言一样:加、减、乘、除,乘除先算,括号优先。
quantity * item_price 就是小计,四行加起来就是这张单的金额。
除法要小心:SQLite 里两个整数相除,结果还是整数(7 / 2 给 3);
MySQL 默认给 3.5000。 要小数就把一边写成小数:7.0 / 2 才是 3.50。
取余写 %(有的数据库写 MOD)。
这一脚换库时最容易踩:同一句求平均单价,一个先截掉小数再往下算,结果能差出几个百分点。
五、NULL 会传染
NULL 不是 0,也不是空字符串,它是「不知道」。
任何值跟它做运算,结果还是「不知道」——所以 LENGTH(NULL) 给 NULL,NULL + 1 也给 NULL。
本课两个客户没留邮箱,邮箱长度那一列就空着两个 NULL。
这不是 bug,是 SQL 的三值逻辑(真/假/不知道)。
要兜底用 COALESCE:COALESCE(cust_email, '(未填)') 有值就用值,NULL 就用第二个参数。
算钱之前先问一句「这列允许空吗」,比事后查半天强。
六、小结
教材小结:计算字段在 SELECT 里现算,拼接用 CONCAT,算术直接用运算符,别名用 AS。
本课补三条:一是 SQLite 没有 CONCAT,用 ||;二是整数相除会截断,要小数先写小数;
三是 NULL 参与运算结果仍是 NULL,用 COALESCE 兜。
七、动手 Demo:从两列拼成一列,再算出一列钱
Demo 六段:先看原始两列,再拼成一列、起别名、查尾空格,然后算 20005 这张单的小计, 最后看 NULL 怎么把整列染成 NULL。 每一段的数字都是当场跑出来的,你可以把 20005 换成别的单号再试。
import sqlite3
con = sqlite3.connect(":memory:")
# 拼接:MySQL 写 CONCAT(...),SQLite 写双竖线
con.execute("SELECT vend_name || ' (' || vend_country || ')' AS vend_title "
"FROM vendors ORDER BY vend_name")
# 不写别名,列名就变成整段表达式
con.execute("SELECT vend_name || ' (' || vend_country || ')' FROM vendors LIMIT 2")
# RTRIM 去尾空格:本课的 TEXT 列看不出差别,用 LENGTH 证明
con.execute("SELECT vend_name, LENGTH(vend_name) AS len_raw, "
"LENGTH(RTRIM(vend_name)) AS len_trim FROM vendors")
# 算术计算:单价乘数量
con.execute("SELECT prod_id, quantity, item_price, "
"quantity * item_price AS expanded_price "
"FROM orderitems WHERE order_num = 20005")
# 整数相除会截断,要小数就把一边写成小数
con.execute("SELECT 7 / 2 AS a, 7.0 / 2 AS b, 7 % 2 AS c")
# NULL 会传染,用 COALESCE 兜底
con.execute("SELECT cust_name, LENGTH(cust_email) AS email_len FROM customers")
con.execute("SELECT cust_name, COALESCE(cust_email, '(未填)') AS email FROM customers")
实跑输出:
--------------------------------------------------------------------
第十课:创建计算字段
--------------------------------------------------------------------
【一】先把原始列摆出来:没有「完整地址」这一列
SQL> SELECT vend_name, vend_country FROM vendors ORDER BY vend_name
vend_name vend_country
--------------------------------
ACME USA
Anvils R Us USA
Furball Inc. USA
Jet Set England
Jouets Et Ours France
LT Supplies USA
(6 行)
**六家供应商,名字和国家分成两列,地址得在应用里自己拼。**
**计算字段就是让数据库把这步算完再交出来**——它不存进表,每次查询现算。
【二】拼接:MySQL 用 CONCAT,SQLite 用双竖线
SQL> SELECT vend_name || ' (' || vend_country || ')' AS vend_title FROM vendors ORDER BY vend_name
vend_title
------------------------
ACME (USA)
Anvils R Us (USA)
Furball Inc. (USA)
Jet Set (England)
Jouets Et Ours (France)
LT Supplies (USA)
(6 行)
**这时结果里多了一列 vend_title,表里根本没有这一列。**
**教材原码用的是 `CONCAT(vend_name, ' (', vend_country, ')')`**,MySQL 的拼接函数;
SQLite 用双竖线 `||`,两者结果一样。
**双竖线在 MySQL 里默认是逻辑或**(除非开了 PIPES_AS_CONCAT),
所以教材才绕道用 CONCAT——**同一个符号,两个引擎两种意思。**
【三】别名:不写会怎样
SQL> SELECT vend_name || ' (' || vend_country || ')' FROM vendors LIMIT 2
vend_name || ' (' || vend_country || ')'
-----------------------------------------
Anvils R Us (USA)
LT Supplies (USA)
(2 行)
**列名直接变成了整段表达式**——程序里取值时得照抄这一长串,容易错。
**别名三种写法**:`AS vend_title`、`vend_title`(省 AS,可以但别省)、
`AS "vend title"`(含空格时加引号)。**AS 只影响列名,不改数据。**
【四】RTRIM:教材为什么反复强调去尾空格
SQL> SELECT vend_name, LENGTH(vend_name) AS len_raw,
LENGTH(RTRIM(vend_name)) AS len_trim FROM vendors ORDER BY vend_name
vend_name len_raw len_trim
-----------------------------------------
ACME 4 4
Anvils R Us 11 11
Furball Inc. 12 12
Jet Set 7 7
Jouets Et Ours 14 14
LT Supplies 11 11
(6 行)
**两列一模一样,因为本课的 vend_name 是 TEXT,不补空格。**教材强调 RTRIM,
是因为 MySQL 的 CHAR 定长列会把短值补空格补齐——
**那次拼接会拼出一堆看不见的尾空格**,所以先 RTRIM 再拼。
**换个字段类型,这条建议就从「必须」降级成「可以不写」。**
【五】算术计算:单价乘数量
SQL> SELECT prod_id, quantity, item_price,
quantity * item_price AS expanded_price FROM orderitems WHERE order_num = 20005
prod_id quantity item_price expanded_price
-------------------------------------------------------
ANV01 10 5.99 59.90
ANV02 3 9.99 29.97
TNT2 5 10.00 50.00
FB 1 10.00 10.00
(4 行)
**四行的 expanded_price 加起来是 149.87**,就是 20005 这张单的金额。
**算术运算符和 Python 一样**:加、减、乘、除,乘除先算,括号优先。
**除法要小心**:SQLite 里两个整数相除,结果还是整数;MySQL 默认给小数。
**要小数就把一边写成小数。**
SQL> SELECT 7 / 2 AS a, 7.0 / 2 AS b, 7 % 2 AS c
a b c
-----------------
3 3.50 1
(1 行)
**`7 / 2` 给 3、`7.0 / 2` 给 3.50**——差别只在那个 `.0`。
**`%` 是取余**(有的数据库写 MOD),`7 % 2` 给 1。
**这一脚换库时最容易踩**:同一句求平均单价,SQLite 先截掉小数再往下算,
结果能和 MySQL 差出几个百分点。
【六】NULL 会传染:算进算术就全变 NULL
SQL> SELECT cust_name, cust_email,
LENGTH(cust_email) AS email_len FROM customers ORDER BY cust_id
cust_name cust_email email_len
------------------------------------------------------
Coyote Inc. [email protected] 15
Mouse House NULL NULL
Wascals [email protected] 19
Yosemite Place [email protected] 16
E Fudd NULL NULL
(5 行)
**两个客户没留邮箱,`LENGTH(NULL)` 给的不是 0,是 NULL。**
**任何值跟 NULL 做运算,结果都是 NULL**——
**所以算钱、算长度之前先想一句:这一列允许空吗?**空的那几行要不要 `COALESCE(列, 0)` 兜一下。
SQL> SELECT cust_name, COALESCE(cust_email, '(未填)') AS email FROM customers ORDER BY cust_id
cust_name email
---------------------------------------
Coyote Inc. [email protected]
Mouse House (未填)
Wascals [email protected]
Yosemite Place [email protected]
E Fudd (未填)
(5 行)
第一段只摆原始列。 六家供应商,名字与国家分两列,要拼成地址得自己动手。
第二段拼接。 六行 ACME (USA) 这样的结果,表里没有 vend_title 这一列,是当场算的。
第三段故意不写别名。 表头变成了整段表达式,这就是为什么每条表达式都该有个名字。
第四段量尾空格。 两列一模一样(4 对 4、11 对 11),因为本课用的是 TEXT;
教材那次是 CHAR 定长补空格,才非 RTRIM 不可。
第五段算钱。 四行小计 59.90、29.97、50.00、10.00,加起来 149.87,就是 20005 这张单的金额。
第六段 NULL。 LENGTH(cust_email) 在两个空邮箱上行给 NULL,换上 COALESCE 之后变成 (未填),
5 行全都有值了。
30 秒实验
- 把拼接改成
vend_country || ' - ' || vend_name,看六行变成什么样。 - 用
quantity * item_price算 20009 那张单的金额,和 149.87 比一比。 - 把
COALESCE(cust_email, '(未填)')换成cust_email,观察空邮箱那两行的区别。
常见误区
- 以为计算字段会存进表。它只在查询结果里存在,表结构一个字都没改。
- 在 MySQL 里直接用
||拼字符串。默认它算逻辑或,得用 CONCAT 或开 PIPES_AS_CONCAT。 - 以为 NULL 参与算术等于 0。NULL 加任何数还是 NULL,要兜底得写 COALESCE。
自测三问(点开对答案)
1. 计算字段和表里的列,本质区别在哪? —— 表里的列有存储、有类型、能建索引;计算字段只在查询时算出来,不落库、不占空间、每次重算。 2. 为什么教材拼字符串前要先 RTRIM? —— 因为 MySQL 的 CHAR 定长列会把短值补空格补齐,直接拼会拼进一串看不见的尾空格; 换成 TEXT 或 VARCHAR 就不补,RTRIM 可以不写。 3. `SELECT 7 / 2` 在 SQLite 和 MySQL 上分别给什么? —— SQLite 给 3(整数除整数还是整数),MySQL 给 3.5000;要小数得把一边写成 7.0。能拼能算了,但写法还绑在具体引擎上:拼接用函数还是竖线、取年份用什么, 换个库就得改。 下一章专讲这块最不可移植的地方——函数。
- ☐ (本章完,下一章 第11章 使用数据处理函数)