pandas 的 read_sql 能把任何数据库连接变成 DataFrame,一句就够。 连接串里 charset 必须写 utf8mb4,否则中文全是问号。 sqlite3 只有 integer、text、real、blob 四种类型,日期得存成文本。
这一篇干两件事:把数据搬进来,把 MySQL 和 sqlite3 的语法差异摊开。 教材的 pymysql 代码原样贴出,标注「本沙箱不执行」;真跑的是 sqlite3。
一、课本的连法
四行:导入驱动、建连接、写 SQL、read_sql 读成 DataFrame。
host 是服务器地址,user 和 password 是账号,database 是库名。
关键只有一句:pd.read_sql(sql, conn) 把任何连接变成 DataFrame。
连接用完要 close()。不关的话连接数会被占满,跑几百次就再也连不上。
二、charset 那一行不能省
charset="utf8mb4" 是命令 MySQL「按四个字节的 UTF-8 说话」。
省了它,默认可能是 latin1 或者三字节的 utf8,中文立刻出问题。
utf8mb4 比 utf8 多一个 mb4,意思是要能存四个字节的字符——表情符号就属于这类。
这个坑不会报错,只会让「张三」变成「??」,或者干脆变成一堆乱码。
三、沙箱里换成 sqlite3
sqlite3 不用装,import sqlite3 就有,connect(路径) 直接建库。
它的类型只有四种:integer、text、real、blob。没有 varchar,没有 datetime。
所以建表时日期字段写 text,值照旧写 2025-03-14 09:20:00。
这样存的好处是:字典序等于时间序,排序、切片、比大小全都照常。 坏处是:算「相差几天」得先转成真正的时间类型,不能直接减。
四、两边的语法差异
| 要做的事 | MySQL | sqlite3 |
|---|---|---|
| 取年份 | YEAR(reg_time) | strftime 里写 %Y |
| 取月份 | MONTH(reg_time) | strftime 里写 %m |
| 日期常量 | date 后跟字符串 | 直接写字符串 |
| 限制行数 | limit 5 | limit 5 |
同一句 where,两边写法不同。照 MySQL 的书抄到 sqlite 上,报错能查半天。 这次 2025 年有 92 条记录,两边算出来必须一样,不一样就说明口径错了。
五、读进来之后先做什么
看形状、看头五行、看类型。这是第 3 章的老规矩,换什么数据源都不变。 这次读进来是 300 行 5 列,字段类型全是 object(文本)。 从 read_sql 返回的那一刻起,MySQL 就退场了。后面全是 pandas 的活。
这一步交接要清楚:取数的问题归数据库,分析的问题归 pandas,别混着查。
六、动手 Demo:导入 MySQL 数据
教材的 pymysql 连接代码原样列出并标注不执行。 真正跑的是 sqlite3:建表、插 300 条、read_sql 读成 DataFrame。
import sqlite3
import numpy as np
import pandas as pd
rng = np.random.default_rng(2026)
plan = [(2021, 30), (2022, 42), (2023, 58), (2024, 78), (2025, 92)]
w = np.array([0.5, 0.6, 1.5, 1.0, 0.9, 0.8, 0.9, 1.1, 1.4, 1.0, 0.9, 1.0])
w = w / w.sum()
chan = ["搜索引擎", "社交媒体", "广告投放", "自然访问", "老用户推荐"]
cw = [0.32, 0.26, 0.18, 0.15, 0.09]
city = ["北京", "上海", "广州", "深圳", "杭州", "成都", "武汉", "其他"]
dw = [0.16, 0.14, 0.11, 0.12, 0.10, 0.09, 0.08, 0.20]
rows = []
uid = 1
for year, n in plan:
for m in rng.choice(np.arange(1, 13), size=n, p=w):
day = int(rng.integers(1, 29))
hh = int(rng.integers(0, 24))
mm = int(rng.integers(0, 60))
rows.append((uid, "用户%03d" % uid,
"%04d-%02d-%02d %02d:%02d:00" % (year, m, day, hh, mm),
str(rng.choice(chan, p=cw)), str(rng.choice(city, p=dw))))
uid += 1
conn = sqlite3.connect(":memory:")
conn.execute("create table users(user_id integer primary key, user_name text, "
"reg_time text, channel text, city text)")
conn.executemany("insert into users values (?, ?, ?, ?, ?)", rows)
conn.commit()
df = pd.read_sql("select * from users order by user_id", conn)
print("形状 %s" % (df.shape,))
print(df.head(3).to_string(index=False))
print("2025 年条数 %d"
% pd.read_sql("select count(*) as n from users "
"where strftime('%Y', reg_time) = '2025'", conn)["n"][0])
conn.close()
实跑输出:
--------------------------------------------------------------------
导入 MySQL 数据
--------------------------------------------------------------------
① 课本的连法
--------------------------------------------------------------------
[平台原样 · 本沙箱不执行]
| import pymysql
| conn = pymysql.connect(host="localhost", user="root",
| password="123456", database="user_db",
| charset="utf8mb4")
| sql = "select * from users"
| df = pd.read_sql(sql, conn)
| conn.close()
host/user/password 换成你自己的;charset 别省
关键就一句:pandas 的 read_sql 能把任何 DBAPI 连接变成 DataFrame。
连接串里 charset 一定要写 utf8mb4,否则中文全是问号。
② 沙箱里换成 sqlite3
--------------------------------------------------------------------
表建好了:users,300 条记录。
sqlite3 没有 varchar、datetime 这些类型,只有 integer、text、real、blob 四种。
日期就存成文本,格式照旧写 2025-03-14 09:20:00,照样能排序、能切片。
③ 读成 DataFrame
--------------------------------------------------------------------
读进来是 DataFrame,形状 300 行 5 列。
| user_id | user_name | reg_time | channel | city |
|---|---|---|---|---|
| 1 | 用户001 | 2021-03-28 05:56:00 | 自然访问 | 杭州 |
| 2 | 用户002 | 2021-09-25 04:20:00 | 老用户推荐 | 杭州 |
| 3 | 用户003 | 2021-07-17 10:25:00 | 搜索引擎 | 成都 |
| 4 | 用户004 | 2021-05-26 09:18:00 | 搜索引擎 | 成都 |
| 5 | 用户005 | 2021-05-12 05:32:00 | 广告投放 | 上海 |
从这一步起,MySQL 就退场了。后面全是 pandas 的活。
! 读进来先看形状和头几行,这是第 3 章的老规矩,换什么数据源都不变。
④ 两边的语法差异
--------------------------------------------------------------------
| 要做的事 | MySQL | sqlite3 |
|---|---|---|
| 取年份 | YEAR(reg_time) | strftime('%Y', reg_time) |
| 取月份 | MONTH(reg_time) | strftime('%m', reg_time) |
| 字符串日期 | date '2025-01-01' | 直接写 '2025-01-01' |
| 限制行数 | limit 5 | limit 5 |
2025 年这一年有 92 条注册记录。
换成 MySQL 就写成 where YEAR(reg_time) = 2025。同一个意思,两种写法。
! 这些差异不是小事。照着 MySQL 的书抄到 sqlite 上,报错能查半天。
executemany 一次插一批,比循环单条 insert 快得多。
pd.read_sql 的第一个参数是字符串 SQL,不是表名。要全表就读 select * from 表名。
换成 MySQL 只需把 sqlite3.connect 换成 pymysql.connect(...),后面一个字不用改。
30 秒实验
- 把 strftime 那句改成
reg_time like '2025%',看结果一不一样。 - 把 read_sql 换成
pd.read_sql_table(sqlite 用不了,想想为什么)。 - 想一想:为什么 sqlite3 不提供 datetime 类型,日期却照样能排序?
常见误区
- 连接串不写 charset:中文全变问号,还不报错。
- read_sql 传表名:它要的是 SQL 语句,不是表名。
- 忘了 close:连接数被占满,跑几百次就再也连不上。
- 照抄 MySQL 的 YEAR():sqlite 没这个函数,要写 strftime。
自测三问(点开对答案)
1. `pd.read_sql` 两个参数分别是什么? —— 一句 SQL 字符串,和一个数据库连接对象。返回 DataFrame。 2. 为什么 charset 要写 utf8mb4? —— 省了它默认字符集可能存不下中文和表情,会变问号或乱码,而且不报错。 3. sqlite3 没有 datetime 类型,日期怎么存的? —— 存成 text,格式照旧写年-月-日 时:分:秒,字典序就等于时间序。数据进来了,但真实的库不会这么干净。下一篇:缺失、重复、异常,三步洗一遍。
- ☐ (本篇完,下一篇 9.3 数据准备与检测)