AI编程(五十):SQLite入门 — 给数据搬个新家

作者:

在

上一篇把”为什么要数据库”讲透了,今天正式动手:用Python自带的SQLite建一张表(CREATE TABLE)、把价格监控一个月的历史数据搬进去(INSERT)、再取出来看一眼(SELECT)。SQL的第一组动作,比你想象的好上手——因为你只是”把想干的事说清楚”。

一、真实场景:2000条价格历史,该搬新家了

接着第48、49篇的故事讲。你的价格监控工具勤勤恳恳跑了一个月,price_history.csv 里攒了2000多条价格记录。第49篇你已经用实验证明了:数据库查询比CSV快几十倍,而且不怕手滑删行、不怕价格被存成字符串。道理都懂了,今天干一件实实在在的事——给这批数据搬个新家。

搬家清单很简单,就三件事:

  1. 建房子:在数据库里建一张 prices 表,规定好”每条价格记录长什么样”;
  2. 搬东西:把CSV里的2000多条历史一条条搬进表里;
  3. 验收:随便取几条出来看看,确认搬进去了、搬对了。

别小看这三件事。它们正好是SQL世界里最基础、最常用的三个动作——CREATE TABLE(建表)、INSERT(插入)、SELECT(查询)。今天主攻前两个,SELECT先学个”取出来看一眼”的最小用法(正餐在第51篇)。学完今天,你就正式跨进了”会用数据库”的门槛。

二、先”接上头”:Python自带的随身数据库

先说个好消息:SQLite是Python自带的,不用装、不用配、不用启动服务。整个数据库就是一个 .db 文件,但SQL、约束、事务一样不缺。上一篇的对比实验里你已经见过 import sqlite3,今天正式认识它。

# hello_db.py — 跟数据库接上头
import sqlite3

# connect:接上数据库。文件不存在?自动创建一个。
conn = sqlite3.connect("prices.db")

# cursor:游标——"拿着它才能对数据库下指令"
cur = conn.cursor()

# 执行一句最简单的SQL,看看数据库答不答应
cur.execute("SELECT 1+1")
print(cur.fetchone())   # (2,)

conn.close()            # 用完关门

跑完你会发现目录里多了一个 prices.db——这就是你的数据库,此时它还是一座空房子。四个词先混个脸熟:

词 干什么的 类比

|—|———|——|

connect 接上(或创建)数据库 拿钥匙开门
cursor 执行SQL的”手” 拿着它才能递话给管理员
execute 执行一句SQL 向管理员递一张纸条
close 关闭连接 关门走人

中间还有个关键动作 commit(盖章),今天第三节会重点讲——它是新手摔得最疼的一跤。

三、建房子:CREATE TABLE,把”规矩”立在前面

数据库的表不是想塞什么塞什么,建表就是立规矩:有几列、每列装什么类型、哪列是编号。你的价格记录有三个信息:商品名、价格、检查时间。翻译成建表SQL:

cur.execute("""
CREATE TABLE IF NOT EXISTS prices (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    product    TEXT    NOT NULL,
    price      REAL    NOT NULL,
    checked_at TEXT    NOT NULL
)
""")
conn.commit()

这段”咒语”逐行拆给你看:

片段 意思 为什么要它

|——|——|———–|

CREATE TABLE IF NOT EXISTS prices 建一张叫 prices 的表;已存在就跳过,不报错 程序要反复跑,这条让你”跑几遍都安全”
id INTEGER PRIMARY KEY AUTOINCREMENT 整数编号列,自动累加,永不重复 每条记录有唯一”身份证号”(第49篇说的主键),第48篇”分不清谁是谁”的病治好了
product TEXT NOT NULL 文本列,不许空 商品名都不填的记录是废纸,拒收
price REAL NOT NULL 小数列,不许空 价格是数字!治”存成字符串,9>10″的老坑(第26、48篇都踩过)
checked_at TEXT NOT NULL 文本列,不许空 时间先按统一格式存成文本(如 2026-10-27 09:30),够用且直观

三种常见类型先记牢,够你用大半个系列:INTEGER(整数)、REAL(小数)、TEXT(文本)。另外SQLite还有个”宽容”特性:它不会因为你往REAL列塞了文本就当场炸,而是照存——所以”价格列真的存的是数字”要靠你自己守住,后面坑3细说。

建表这件事,是跟AI协作的黄金场景。比如你可以直接把需求甩给AI:

“我要用SQLite存价格监控的历史数据:商品名(文字)、价格(小数)、检查时间(2026-10-27 09:30这种格式)。以后还想加一个’目标价’字段。帮我设计建表SQL,每行加注释解释为什么。”

AI给你的方案通常比你自己拍脑袋的更周全(比如提醒你目标价应该单开一张 products 表,而不是每条价格记录里重复存——这个思路第54篇多表关联会展开)。学习法升级:不是”背SQL”,而是”说需求→读懂AI给的SQL→改到贴合自己”。

四、搬东西:INSERT,把2000条历史请进门

表建好了,往里放数据用 INSERT。先搬一条试试水:

cur.execute(
    "INSERT INTO prices (product, price, checked_at) VALUES (?, ?, ?)",
    ("机械键盘", 172.0, "2026-10-27 09:30"),
)
conn.commit()

两行各有讲究:

  1. 列名清单 (product, price, checked_at):明确告诉数据库”这三个值分别放哪一列”。好处是不怕列顺序记错,将来加了新列(比如目标价)老代码也不会搬错位置;
  2. 三个 ? 占位符:值不直接写进SQL里,而是放在后面的元组里”按位入座”。这是铁律,不是风格偏好——为什么,坑2告诉你。

一条条搬2000条太慢,executemany 一次搬一批,配合上CSV读取,就是完整的搬家脚本:

# move_to_db.py — 把 price_history.csv 的历史搬进 SQLite
import csv
import sqlite3

# 1. 接上数据库(文件不存在会自动创建)
conn = sqlite3.connect("prices.db")
cur = conn.cursor()

# 2. 建表(IF NOT EXISTS:重复运行不报错)
cur.execute("""
CREATE TABLE IF NOT EXISTS prices (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    product    TEXT    NOT NULL,
    price      REAL    NOT NULL,
    checked_at TEXT    NOT NULL
)
""")

# 3. 把CSV里的历史读出来,攒成"元组列表"
with open("price_history.csv", encoding="utf-8") as f:
    rows = [
        (r["商品"], float(r["价格"]), r["时间"])
        for r in csv.DictReader(f)
    ]
print(f"从CSV读到 {len(rows)} 条记录")

# 4. 批量搬进数据库
cur.executemany(
    "INSERT INTO prices (product, price, checked_at) VALUES (?, ?, ?)",
    rows,
)

# 5. 盖章确认——不执行这行,前面全白干(坑1)
conn.commit()

# 6. 验收:随便取5条出来看一眼
print("--- 验收:数据库里的前5条 ---")
for row in cur.execute("SELECT product, price, checked_at FROM prices LIMIT 5"):
    print(row)

print(f"共搬入 {cur.execute('SELECT COUNT(*) FROM prices').fetchone()[0]} 条")
conn.close()

注意第3步里那个 float(r["价格"]):CSV里的价格是文本,搬进REAL列之前先转成小数——搬家正好是给数据”正名”的时机,第29篇数据清洗的功夫在这里用上了。

跑完用任意SQLite查看工具(比如免费的 DB Browser for SQLite,或者VS Code的SQLite插件)打开 prices.db,你能像看Excel一样看到这张表——2000条”裸奔”的CSV数据,从此住进了带管理员、带编号、带门禁的新房子。

五、三个必踩的坑(先打疫苗)

坑1:忘了 conn.commit(),数据”明明存了却查不到”。这是SQLite新手摔得最惨的一跤:INSERT执行完没报错、没异常,程序跑得好好的,结果重开程序一查——空的!原因:SQLite的修改默认待在”暂存区”,commit()(提交)才是盖章入库;不提交就 close(),等于把整批改动扔进垃圾桶。更迷惑的是:同一个连接里当场SELECT能看到数据(暂存区可见),给人一种”存进去了”的错觉。疫苗:养成肌肉记忆——凡是 INSERT/UPDATE/DELETE,紧跟一句 conn.commit()。反过来,如果哪天你想”试一下不保存”,可以 conn.rollback() 撤销未提交的改动——这个”后悔药”是数据库给你的,CSV可没有。

坑2:用字符串拼SQL。比如把商品名直接拼进语句:

# 反面教材:别这么写!
name = "阿迪达斯's 拖鞋"          # 名字里带个撇号
cur.execute(f"INSERT INTO prices VALUES ('{name}', 99.0, '2026-10-27')")
#                       ↑ 撇号提前收尾,SQL语法直接爆炸

轻则报错(syntax error),重则SQL注入——如果名字来自用户输入,恶意用户可以输入 '); DROP TABLE prices; -- 这种东西,一句”名字”让你整张表消失。疫苗:永远用 ? 占位符传值(第三节的写法)。顺手还能少踩一个坑:数字和文本也不用操心加不加引号了,占位符通通帮你处理对。记住一句话:SQL是”结构”,数据是”内容”,两者永远不混在同一个字符串里。

坑3:表结构拍脑袋,三周后发现”装不下”。比如把价格建成TEXT(老坑复发)、把时间按”2026/10/27″和”2026-10-27 09:30″两种格式混着存、或者压根忘了以后要存”目标价”,三周后2万条数据进去了才想加字段——SQLite改表相当别扭。疫苗:建表前花五分钟”设计”,两个动作:①拿3条真实样例数据检验”每列能不能装下、类型对不对”;②把需求(包括”以后可能还要存什么”)甩给AI让它帮你设计+挑毛病。表是地基,地基歪了楼越盖越疼——而改地基的成本,跟数据量成正比。

六、跟AI协作的正确姿势(SQLite入门特供版)

  1. 说需求,不背语法。“我要把价格监控的CSV历史(商品、价格、时间三列)搬进SQLite,以后每天还要追加新数据,帮我写建表+批量插入的Python代码,关键处加注释。”——需求说清楚,AI给的代码直接能跑;你负责读懂每一行,不懂就追问”executemany为什么比循环execute快?”
  2. 报错整段贴,别只贴一半。SQL报错信息(比如 table prices has no column named product)又短又准,整段贴给AI,它能立刻定位是拼写错了列名、还是表结构跟你脑子里想的不一致——顺带把你的表结构 SELECT sql FROM sqlite_master WHERE name='prices'; 的输出一起贴上,效率翻倍。
  3. 让AI当”监理”,审查你的地基。“这是我的建表SQL和3条样例数据,我以后还想加’目标价’和’备注’字段,帮我挑毛病、给改进版。”这是本系列”说需求→读懂→改进”学习法在数据库上的典型用法:你出需求和现状,AI出方案,你读懂方案里的取舍(为什么目标价该单开一张表?),下一次你就自己会了。
  4. 玩”报错自考”。“围绕’INSERT和commit’出3道’这段代码错在哪’的题,先别给答案。”比如故意漏掉commit、忘加占位符、把价格写成带引号的文本——你先诊断再对答案,坑就变成了疫苗。

七、今日小练习

练习1:跑通搬家脚本,当一次”搬家工”。把第四节的 move_to_db.py 存下来跑一遍(price_history.csv 可以用第48篇的工具攒出来,也可以让AI帮你生成20条假数据练手)。跑完回答两个问题:①把脚本原样再跑一遍,会发生什么?为什么?②把 conn.commit() 那行注释掉再跑,数据库里有数据吗?

提示:①不会报错、也不会翻倍——想想 IF NOT EXISTS 保护了什么、没保护什么(插入可没去重!想一想为什么这次没出事);②打开 prices.db 确认是空的——这就是坑1的现场复现,亲手摔一次比读十遍都牢。

练习2:给自己的数据”建个家”。选一样你自己的数据(读书记录、健身打卡、游戏战绩都行),先用自然语言写下:每条记录包含哪几个信息、分别是什么类型(文字/整数/小数)、哪个适合当编号。然后让AI把它翻译成 CREATE TABLE,你读懂每一行后,插入3条真实记录并用 SELECT * FROM 表名 LIMIT 3 取出来看。

提示:翻译完别急着跑,先自己对着第三节的”逐行拆解表”口头翻译一遍SQL,翻不动的地方就是没懂的地方。SELECT * 里的 * 是”所有列”的意思,正餐第51篇讲。

练习3(概念自考):先自答,再让AI批卷。不看文章回答三个问题:①INSERT执行完没报错,为什么数据可能根本没存住?②为什么INSERT里的值要用 ? 占位符,而不直接拼进SQL字符串?③PRIMARY KEY AUTOINCREMENT 解决的是什么问题?答完连同问题发给AI:”帮我批改,指出理解偏差。”

提示:②答”防止语法错误”只对了一半,另一半是安全(想想坑2里那句 DROP TABLE);③联想第49篇”Excel手滑删重”的病——身份证号治的就是它。

下篇预告

下一篇(第51篇):查询数据 — SELECT+WHERE,让数据库自己把答案端上来。房子建好了、东西搬进去了,该享受”图书馆”的待遇了:把”上周三到今天机械键盘降了几次”这种人话,翻译成 SELECT ... WHERE ...,让数据库替你翻完2000条记录、只把答案交给你。查询是数据库的看家本领,也是最有成就感的一课——因为从这一天起,你问问题,程序答问题。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注