查询的基本形状:先挑列(SELECT)、再挑行(WHERE)、最后排序(ORDER BY)。
计算列是「查的时候算」,不存盘;改了源数据,下次查结果立刻变。
SELECT INTO 把结果存成新表,比 INSERT INTO ... SELECT 快——但它只带走数据,
不带约束和索引。
第 6、7 章讲的是「语言本身」:语句怎么组织、函数怎么算。
从这一节开始讲「查数据」——这是 SQL 用得最多的一块,也是最容易写错的一块。
教材 8.1a 分三小节:SELECT...FROM 子句、列别名与计算列、INTO 子句。
前两节是基础,第三节是个实用技巧——做临时备份、做中间表都用它。
演示里真跑了一批商品数据:挑列、起别名、算金额、再存成一张新表。
SELECT INTO 是本沙箱跑不了的(它是 SQL Server 语法),
用 CREATE TABLE AS SELECT 等价复刻。
一、最基本的形状
所有查询都是这个形状的变体:
SELECT 列清单
FROM 表
[WHERE 条件]
[ORDER BY 排序列]
方括号里的是可选的。 但实际写的时候,
ORDER BY 建议都带上——不写它,返回的顺序不保证。 这一点后面 8.1b 会细说。
SELECT * 看着方便,但生产代码里不建议。 两个理由:一是列顺序一变,依赖它的程序就出错;
二是它会把大字段一起取出来——比如存图片的 varbinary 列,白白多传几兆。
要挑列就写清楚列名。 这多打几个字,
换的是「表结构改了这句还能跑」——而且看代码的人一眼知道你要什么。
二、别名与计算列
别名用 AS,AS 可以省略,但建议写上。 少一个字符,
多一分清楚——尤其是别名跟前一个词连在一起的时候。
SELECT gname AS 商品,
price AS 单价,
price * stock AS 金额,
ROUND(price * 0.9, 2) AS 九折价
FROM tb_goods
后两行是计算列:查询的时候现算,不存盘。好处是永远跟源数据一致——改了 price,
下次查金额就变了;代价是每次查都要算一遍,数据量大、算得复杂的时候会拖慢。
如果这个计算很常用、数据量又大,可以考虑建「计算列」把它存起来。那是另一回事——
建的时候要权衡「存的空间」和「算的时间」。
中文别名不用加引号。 因为它是合法的 Unicode 标识符——6.2 讲命名规则时说过。
只有别名里带空格才要方括号:AS [单 价]。单引号不行,那是字符串不是标识符。
三、SELECT INTO:把结果存成表
SELECT id, gname, price
INTO tb_goods_bak
FROM tb_goods
WHERE price > 100
这是 SQL Server 的写法,本沙箱跑不了。
SQLite 的等价写法是 CREATE TABLE tb_bak AS SELECT ...——思路一样:拿查询结果建一张新表。
新表的结构是照「查询结果」建的,不是照原表。上面只 SELECT 了三列,
新表就只有三列——原表的主键、默认值、索引一个都不会跟过来。
所以它适合「临时备份」和「做中间表」,不适合当正式表。要建正式表,
还是老老实实写 CREATE TABLE——把类型、约束、索引都定清楚。
SELECT INTO 比 INSERT INTO ... SELECT 快,
因为它不记完整日志——它是「建表 + 插入」一次完成,属于批量操作。 数据量大的时候差别明显。
四、三个容易忽略的细节
| 细节 | 说明 |
|---|---|
| 别名只在客户端 | price AS 单价 之后,表里还是 price |
| 计算列不存盘 | 每次查都重算,改了源数据立刻变 |
INTO 只带数据 |
约束、索引、默认值都不跟过来 |
第一条经常被误解。 有人以为起了别名就改了列名,
结果在 WHERE 里写 WHERE 单价 > 100 报错——因为 WHERE 执行时别名还不存在。
第二条在性能上要留心。 一个查询里算五个计算列,
每个都要扫一遍数据——能用一次算完的,就别拆成五次。
第三条是数据安全上的考虑。 拿 SELECT INTO 备份出来的表,
没有主键、没有非空约束——往里插脏数据它照收。临时用没问题,长期用要补约束。
五、动手 Demo:挑列、起别名、算金额、存成新表
这一段用七行商品数据演四件事:最基本的筛行、列别名与计算列、中文别名要不要加引号、
以及把查询结果存成一张新表(SELECT INTO 的等价写法)。
import sqlite3
c = sqlite3.connect(u":memory:")
c.execute(u"CREATE TABLE tb_goods (id INTEGER PRIMARY KEY, gname TEXT,"
u" cat TEXT, price REAL, stock INT)")
c.executemany(u"INSERT INTO tb_goods VALUES (?,?,?,?,?)", [
(1, '笔记本', '电脑', 4999.0, 12), (2, '键盘', '电脑', 199.0, 40),
(3, '鼠标', '电脑', 89.5, 0), (4, '显示器', '电脑', 1299.0, 7)])
# 别名 + 计算列
q = u"""SELECT gname AS 商品, price AS 单价,
price * stock AS 金额,
ROUND(price * 0.9, 2) AS 九折价
FROM tb_goods WHERE stock > 0"""
for r in c.execute(q):
print(r)
# SELECT INTO 的等价写法
c.execute(u"CREATE TABLE tb_bak AS SELECT id, gname, price "
u"FROM tb_goods WHERE price > 100")
print(u"新表列数:", len(c.execute(u"PRAGMA table_info(tb_bak)").fetchall()))
print(u"新表行数:", c.execute(u"SELECT COUNT(*) FROM tb_bak").fetchone()[0])
实跑输出:
--------------------------------------------------------------------
SELECT...FROM 与 INTO
--------------------------------------------------------------------
① 最基本的形状
--------------------------------------------------------------------
SELECT 列清单 FROM 表 [WHERE 条件] [ORDER BY 排序列]
id gname price
1 笔记本 4999.0
2 键盘 199.0
4 显示器 1299.0
这是 SQL 里用得最多的形状:先挑列,再挑行,最后排序。
SELECT * 看似方便,但生产代码里不建议——列顺序一变,依赖它的程序就出错。
而且 * 会把大字段一起取出来(比如存图片的 varbinary),白白多传几兆。
② 列别名与计算列
--------------------------------------------------------------------
商品 单价 库存 金额 九折价
笔记本 4999.00 12 59988.00 4499.10
键盘 199.00 40 7960.00 179.10
显示器 1299.00 7 9093.00 1169.10
打印纸 25.00 200 5000.00 22.50
订书机 18.50 60 1110.00 16.65
文件夹 6.00 150 900.00 5.40
别名用 AS,AS 可以省略但建议写上——少一个字符,多一分清楚。
别名只在客户端显示,不改变表结构。所以 price AS 单价 之后,表里还是叫 price。
计算列是「查询时算出来的」,不存盘。每次查都重算,改了 price 结果立刻变。
如果这个计算很常用、而且数据量很大,可以考虑建「计算列」存起来——那是另一回事。
③ 中文别名要不要加引号
--------------------------------------------------------------------
| 写法 | 能用吗 |
|---|---|
| AS 单价 | 能 |
| AS [单 价] | 能 |
| AS '单价' | 不能 |
中文别名不加引号就能用,因为它是合法的 Unicode 标识符(6.2 讲过命名规则)。
空白别名才需要方括号:[单 价]。单引号不行——那是字符串,不是标识符。
④ SELECT INTO:把结果存成一张新表
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| SELECT id, gname, price
| INTO tb_goods_bak
| FROM tb_goods
| WHERE price > 100
这是 SQL Server 的写法,本沙箱跑不了;SQLite 的等价写法是 CREATE TABLE AS SELECT。
CREATE TABLE tb_bak AS SELECT ... 之后:
新表 3 行,源查询 3 行
新表只有三列——因为 SELECT 里只写了三列。结构是照查询结果建的,不是照原表。
约束、索引、默认值都不会跟过来。所以它适合「临时备份」和「做中间表」,不适合当正式表。
SELECT INTO 比 INSERT INTO ... SELECT 快,因为它是「建表 + 插入」一次完成,不记完整日志。
第一段四行里,金额 和 九折价 都是算出来的。 注意 金额 用的是 price * stock,
那两列原本不在查询结果里——计算列可以从任意列算,不用先选出来。
鼠标的库存是 0,被 WHERE stock > 0 筛掉了。 所以只有三行——
这是「先筛行再挑列」的顺序,虽然写的时候 SELECT 在前面。
第二段是两个数:列数 3、行数 3。 列数是 3,因为 SELECT 里只写了三列;
行数是 3,因为价格大于 100 的有笔记本、键盘、显示器三个——鼠标 89.5 不够。
新表里没有主键、没有索引、也没有非空约束。 这就是「INTO 只带数据」的意思——
它能当临时表,不能当正式表。
30 秒实验
- 把你常查的一张表,
SELECT *改成写全列名,看返回的字节数少了多少。 如果表里有大字段,差别会很明显。 - 写一个计算列,比如
price * 0.9,然后UPDATE一下源表的price,再查一次。 计算列的值跟着变了——这就是「不存盘」的意思。 - 想一想:为什么
WHERE里不能用SELECT里起的别名? 因为执行顺序是WHERE在前、SELECT在后。 别名还没算出来,WHERE就已经要用了。 但ORDER BY能用别名——它在SELECT之后。
常见误区
- 「
SELECT *方便,反正后面会挑」。列顺序一变,依赖它的程序就崩;大字段会被整列取出来。 写清楚列名,多打几个字换稳定。 - 「起了别名就是改了列名」。别名只在查询结果里生效,表结构一点没变。
而且
WHERE里用不了它——因为那时候别名还不存在。 - 「
SELECT INTO备份出来的表能当正式表用」。它只有数据,没有主键、索引、非空约束。 往里插脏数据它照收——临时用可以,长期用得补约束。
自测三问(点开对答案)
1. `SELECT INTO` 建出来的新表,结构是照什么建的? —— **照「查询结果」建的,不是照原表。** `SELECT` 里写了几列,新表就有几列; **约束、索引、默认值一个都不会跟过来。** 2. 计算列存在磁盘上吗? —— **不存。** 它是查询时现算的——改了源数据,下次查结果立刻变。 **好处是永远一致,代价是每次都算一遍。** 3. 为什么 `WHERE 单价 > 100` 会报错,而 `ORDER BY 单价` 可以? —— **因为执行顺序不同。** `WHERE` 在 `SELECT` 之前执行,那时候别名还不存在; `ORDER BY` 在 `SELECT` 之后,别名已经算出来了。下一篇 8.1b 讲 WHERE、GROUP BY、HAVING、ORDER BY 四个子句。
重点是它们的执行顺序——那张顺序表能解释「为什么这里能用别名、那里不能」。
- ☐ (本篇完,下一篇 8.1b WHERE、GROUP BY、HAVING 与 ORDER BY)