数据库就是「有组织地存数据的容器」,里面装着表,表里是行和列,每一行靠主键被唯一指认。 Demo 从零建了一张 products 表,插三行进去,然后故意违两次规——主键插重复值被拒、 必填字段插 NULL 被拒,报错都是数据库当场拦下来的,不是纸上规定。 最后数出来:3 行、5 个字段、价格合计 25.98。
这是全书第一课,也是唯一不讲 SQL 的一章。教材只用五页把五个词说完:数据库、表、列、行、主键。 这一章跟 15 课数据库基础 有重叠,但侧重不同。 15 课讲的是「为什么这么设计」——范式、E-R 图、三种数据模型; 这一章只讲「这几个词各指什么」,讲完就进 SQL。两本对照着读效果最好。
一、数据库基础:五个词,一条线
数据库(database):一个有组织的容器,专门用来存数据。 注意「有组织」三个字——码成一堆的文件也叫存数据,但那不叫数据库。 表(table):数据库里的一个清单,存某一类东西。products 表只存产品,customers 表只存顾客。 同一个库里表名不能重名,跨库可以。 列(column):表中的一个字段。 每个列都有自己的数据类型,prod_price 存小数,prod_name 存文本,混不进去。 列名在表里也不能重名。 行(row):表里的一条记录。 一行就是一整套信息:一个产品编号、一个供货商编号、一个名字、一个价格、一段描述。 行没有顺序——你不写 ORDER BY,数据库返回的顺序就是「它高兴的顺序」。 主键(primary key):一个或一组列,用来唯一指认一行。 它有四个条件:值不重复、值不为空、每一行都有、值尽量不改。 products 用 prod_id(ANV01、FB 这种编号)当主键, orderitems 用「订单号 + 行号」两个字段合起来当主键——都行,只要能唯一指认。 这五个词的关系是层层套的:数据库里装表,表里装列和行,主键给行编号。记住这一句, 后面 29 章都在这个框子里转。
二、什么是SQL
SQL 是 Structured Query Language,结构化查询语言。名字里带 Query(查询), 但它能干的不止查询——建表、删表、改数据、授权都归它管。 SQL 最反直觉的一点: 它是声明式的,不是过程式的。 你写的是「我要什么」(把这些产品的名字给我), 不是「怎么拿」(打开表、从头往后读、找到名字就打印)。 怎么拿由数据库的查询优化器决定,你只负责说清要什么。 这一条决定了 SQL 的学习方式:别背执行过程,背表达方式。写惯了 C++ 或 Python 的人, 头几天总想「循环一下」,但 SQL 里没有循环(游标是例外,第 24 章讲),只有一句一句的集合操作。 按用途分,SQL 语句五类: 查询(SELECT)/增删改(INSERT、UPDATE、DELETE)/定义(CREATE、DROP、ALTER)/控制(GRANT、 REVOKE)/事务(COMMIT、ROLLBACK)。前两类日常用得最多,后三类是管理和运维的事。 还有三个格式约定,教材第一章就交代了:语句以分号结尾(一条命令结束)、 关键字不分大小写(SELECT 和 select 一样,习惯上大写)、 字符串值分大小写('Bird' 和 'bird' 是两个值)。
三、动手实践:教材让你装 MySQL,本课用现成的
教材第 1 章最后一节叫「动手实践」, 附录 A 讲了完整的安装步骤——那套步骤针对的是 2009 年前后的 MySQL 5.x,装法、界面、 默认值都和现在不一样。 本课的沙箱没有 MySQL 服务端,也装不了。所以全部 Demo 跑在 Python 自带的 SQLite 3.53 上。 它是真数据库引擎:表是真建的、数是真插的、约束是真拦的, 只是它把「客户机」和「服务器」压成了一层。 这对学 SQL 几乎没有损失,因为九成的语法两边一样:SELECT、WHERE、ORDER BY、GROUP BY、JOIN、 子查询、UNION、视图、触发器、事务,写出来几乎逐字相同。 不一样的地方我逐条标出来,主要这几处:SHOW 系列命令(SQLite 没有,改查系统表)、 AUTO_INCREMENT(SQLite 用 INTEGER PRIMARY KEY 自增)、 存储过程与游标(SQLite 根本不支持)、用户与权限(SQLite 没有这套)、 FULLTEXT 索引(SQLite 用 FTS5 虚拟表)。 如果你要跟着教材在真 MySQL 上敲,那更好——装个 MySQL 8.4 LTS,把本课的 SQL 原样贴进去, 结果应当一致。
四、小结
教材的小结只有一句: SQL 是数据库语言,不是编程语言;它声明要什么,不说怎么做。再加三条本课的补充: 一是五个词先认全(数据库、表、列、行、主键),后面每章都会用到; 二是约束不是纸上的规定,是数据库真正会拒你的东西——Demo 里两次报错就是证据; 三是别怕装不上 MySQL,SQL 的骨架两边一样,先在本课把骨架练熟。
五、动手 Demo:从零建一张表,然后故意违两次规
这段代码做了四件事: 建库、建表、插三行、数一数。 建表语句里带了两个约束(PRIMARY KEY 和 NOT NULL), 所以后面能故意违规两次,看数据库怎么拒绝。 跑完你会发现:所有报错都是数据库给的,不是我编的。
import sqlite3
con = sqlite3.connect(":memory:") # 建一个空库(本沙箱没有 MySQL)
con.execute("""CREATE TABLE products (
prod_id TEXT PRIMARY KEY, -- 主键:一行一个,不许重复
vend_id INTEGER NOT NULL, -- 不许空
prod_name TEXT NOT NULL,
prod_price REAL NOT NULL,
prod_desc TEXT
""")
con.execute("INSERT INTO products VALUES ('ANV01', 1001, '.5 ton anvil', 5.99, '0.5 吨铁砧')")
con.execute("INSERT INTO products VALUES ('ANV02', 1001, '1 ton anvil', 9.99, '1 吨铁砧')")
con.execute("INSERT INTO products VALUES ('FB', 1003, 'Bird seed', 10.00, NULL)")
print(con.execute("SELECT COUNT(*), SUM(prod_price) FROM products").fetchone())
实跑输出:
--------------------------------------------------------------------
第一课:数据库、表、列、行、主键
--------------------------------------------------------------------
从零造一个库:一张产品表,五个字段
SQL> CREATE TABLE products ( prod_id TEXT PRIMARY KEY, vend_id INTEGER NOT NULL,
prod_name TEXT NOT NULL, prod_price REAL NOT NULL, prod_desc TEXT)
完成
插三行进去,再整表读出来
SQL> INSERT INTO products VALUES ('ANV01', 1001, '.5 ton anvil', 5.99, '0.5 吨铁砧')
影响 1 行
SQL> INSERT INTO products VALUES ('ANV02', 1001, '1 ton anvil', 9.99, '1 吨铁砧')
影响 1 行
SQL> INSERT INTO products VALUES ('FB', 1003, 'Bird seed', 10.00, NULL)
影响 1 行
SQL> SELECT prod_id, prod_name, prod_price FROM products
prod_id prod_name prod_price
----------------------------------------
ANV01 .5 ton anvil 5.99
ANV02 1 ton anvil 9.99
FB Bird seed 10.00
(3 行)
主键是硬约束:同一个值插第二次,数据库直接拒绝
SQL> INSERT INTO products VALUES ('ANV01', ...)
报错:UNIQUE constraint failed: products.prod_id
**这条报错就是主键在干活:它保证一行能被唯一指认。**
列有类型,也有「不许空」
SQL> INSERT INTO products VALUES ('X1', 1001, NULL, 1.00, NULL)
报错:NOT NULL constraint failed: products.prod_name
**prod_name 声明了 NOT NULL,所以空值进不来。**
数一数:现在几行、几个字段、价格合计多少
行数 3 / 字段数 5 / 价格合计 25.98
**「几行」「几个字段」「合计多少」——这三问就是 SQL 存在的理由:
数据一多,靠眼睛数不过来,得让数据库自己算。**
第一段(建表)与「五个词」对应。 CREATE TABLE 后面跟表名,括号里一行一个列:列名、类型、约束。 prod_id 是 TEXT 加 PRIMARY KEY,vend_id 是 INTEGER 加 NOT NULL。这五行就是一张表的全部定义。 第二段插三行,注意第三行的最后一个值是 NULL。 prod_desc 没写 NOT NULL,所以允许空; 换成 prod_name 插 NULL 就会被拒,这是下面第二个报错。 「允许空」和「不许空」是列定义时就定下来的,不是插的时候决定的。 第一个报错是主键在干活。 再插一个 prod_id 等于 ANV01 的行,数据库报 UNIQUE constraint failed。 注意它的措辞是「约束失败」,不是「你写错了」——约束是数据库自己的规则,违反规则它就不收。 第二个报错是 NOT NULL 在干活。 prod_name 插 NULL 被拒。 这两个报错合起来说明一件事: 数据类型管「长什么样」,约束管「合不合规」,两道关都在数据库这一侧,程序绕不过去。 最后一段数出来:3 行、5 个字段、价格合计 25.98。 这三问正是 SQL 存在的理由——数据一万行时, 你没法用眼睛数,得让数据库自己算。
30 秒实验
- 把 prod_desc 也加上 NOT NULL,再插一行不带描述的产品,看报错和这次有什么不同。
- 把主键从 prod_id 换成 (prod_id, vend_id) 两个字段的联合主键, 然后插两行 prod_id 相同但 vend_id 不同的数据——能插进去吗?为什么?
- 用 SELECT * FROM products 看一下,行是按什么顺序返回的?再插两行,顺序变了吗?
常见误区
- 以为「数据库」就是「一张 Excel 表」。表只是数据库里的一层,库里还有视图、索引、存储过程、 用户权限。把库当表,后面会一直别扭。
- 以为主键必须是有意义的编号。主键的唯一职责是「唯一指认一行」,所以自增整数、 UUID 都能当主键。拿身份证号当主键,改一次号就得动全表。
- 以为不写 ORDER BY 也有固定顺序。数据库返回顺序不保证,小表看着「一直是这个顺序」纯属巧合。 要顺序就明写 ORDER BY(第 5 章)。
自测三问(点开对答案)
1. 表、列、行、主键各是什么?用一句话说清。 —— 表是存一类数据的清单;列是表里的字段(有类型);行是一条完整记录; 主键是能唯一指认一行的那个(或那组)列。 2. SQL 是声明式语言,这句话的实际含义是什么? —— 你只描述「要什么结果」,不说「一步步怎么取」。 具体怎么扫描、用不用索引,由数据库的优化器决定。 3. 违反主键约束和违反 NOT NULL 约束,报错有什么共同点? —— 都是数据库在执行时报的「约束失败」,都不是语法错。 共同点是:**拒绝入库**,程序侧没法绕过(除非改表结构)。第一课只认了词、建了表。 下一章讲这套东西的产品——MySQL 是什么、它为什么分成客户机和服务器两半、 今天的版本号又是什么样。
- ☐ (本章完,下一章 第2章 MySQL简介)