养宠物的人大多遇到过同一个问题:明明上午已经喂过猫粮,家人回家后又喂了一次,结果猫咪撑得直吐;或者按时该给狗狗驱虫了,却怎么也想不起上次喂药是哪天。与其靠脑子记或者贴便签,不如写一个小工具来管理。SQLite作为嵌入式数据库,不需要安装服务端,一个文件就是一个库,配合Python自带的sqlite3模块,几十行代码就能搭出一个可用的宠物喂养日程管理系统。本文完整走一遍从建库建表到查询统计的全过程。

一、设计数据库表结构:先想清楚要存什么
动手写代码之前,先把需求拆解清楚。这个系统的核心对象有两个:宠物本身,以及每一条喂养记录。宠物需要记录名字、品种、生日、体重等基础信息;喂养记录则要关联到具体哪只宠物,记录喂养时间、喂的食物类型、份量,以及是谁喂的——最后这一项在很多家庭的真实场景里非常关键,能有效避免重复投喂。
据此设计两张表:pets表作为主表,feeding_records表作为从表,通过外键pet_id关联。建表语句如下:
-- 宠物信息表
CREATE TABLE IF NOT EXISTS pets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
species TEXT NOT NULL, -- 物种:猫/狗/兔子等
breed TEXT, -- 品种
birth_date DATE,
weight REAL, -- 体重,单位千克
daily_feed_count INTEGER DEFAULT 2, -- 每天计划喂养次数
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 喂养记录表
CREATE TABLE IF NOT EXISTS feeding_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pet_id INTEGER NOT NULL,
feed_time DATETIME NOT NULL,
food_type TEXT, -- 食物类型:猫粮/狗粮/罐头/药物
amount REAL, -- 份量,单位克
feeder TEXT, -- 喂养人
note TEXT,
FOREIGN KEY (pet_id) REFERENCES pets(id)
);
几个设计细节值得说明。daily_feed_count字段看起来可有可无,但它决定了后续“今日还差几顿没喂”的判断逻辑,属于业务字段提前预留。外键约束在SQLite中默认是关闭的,需要每次连接后执行PRAGMA foreign_keys = ON才会生效,这一点很多人踩过坑——删除一只宠物时,它的喂养记录不会被级联清理,时间一长会留下大量孤儿数据。如果想自动清理,可以把外键定义改为ON DELETE CASCADE。
二、封装数据库操作:连接管理与增删改查
Python标准库中的sqlite3模块开箱即用,不需要pip安装任何东西。建议把连接逻辑封装成上下文管理器,这样代码退出时能自动提交或回滚事务,避免写了一半的数据残留在库里。
import sqlite3
from contextlib import contextmanager
DB_PATH = "pets.db"
@contextmanager
def get_conn():
conn = sqlite3.connect(DB_PATH)
conn.execute("PRAGMA foreign_keys = ON")
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def init_db():
with get_conn() as conn:
conn.executescript(open("schema.sql", encoding="utf-8").read())
def add_pet(name, species, breed="", birth_date=None, weight=None, daily_feed_count=2):
with get_conn() as conn:
cur = conn.execute(
"INSERT INTO pets (name, species, breed, birth_date, weight, daily_feed_count) "
"VALUES (?, ?, ?, ?, ?, ?)",
(name, species, breed, birth_date, weight, daily_feed_count)
)
return cur.lastrowid
def add_feed_record(pet_id, food_type, amount, feeder, note=""):
with get_conn() as conn:
conn.execute(
"INSERT INTO feeding_records (pet_id, feed_time, food_type, amount, feeder, note) "
"VALUES (?, datetime('now', 'localtime'), ?, ?, ?, ?)",
(pet_id, food_type, amount, feeder, note)
)
注意SQL语句里全部使用了占位符?而不是字符串拼接,这是防SQL注入的基本习惯,哪怕是自己用的小工具也不该偷懒。另外datetime('now', 'localtime')这个写法会把时间转换为本地时区,如果直接用datetime('now')存的是UTC时间,统计“今天喂了几次”时就会差八个小时,这是中文环境下特别常见的坑。
查询当日记录时,用SQLite的日期函数做筛选:
def get_today_records():
sql = """
SELECT p.name, r.feed_time, r.food_type, r.amount, r.feeder
FROM feeding_records r
JOIN pets p ON p.id = r.pet_id
WHERE date(r.feed_time) = date('now', 'localtime')
ORDER BY r.feed_time DESC
"""
with get_conn() as conn:
return conn.execute(sql).fetchall()
def get_pending_pets():
"""查询今天还没喂够次数的宠物"""
sql = """
SELECT p.name, p.daily_feed_count,
COUNT(r.id) AS fed_count
FROM pets p
LEFT JOIN feeding_records r
ON p.id = r.pet_id
AND date(r.feed_time) = date('now', 'localtime')
GROUP BY p.id
HAVING fed_count < p.daily_feed_count
"""
with get_conn() as conn:
return conn.execute(sql).fetchall()
这里用LEFT JOIN配合HAVING统计每只宠物今日已喂次数,不足计划次数的直接列出来。早上打开程序跑一下,今天该喂谁、还差几顿一目了然。
三、统计报表与性能优化
基础功能跑起来之后,可以再加一些统计能力,比如按周统计每只宠物的进食总量,方便观察食欲变化:
def weekly_report(pet_id):
sql = """
SELECT date(feed_time) AS day,
COUNT(*) AS times,
SUM(amount) AS total_amount
FROM feeding_records
WHERE pet_id = ?
AND feed_time >= date('now', 'localtime', '-7 days')
GROUP BY date(feed_time)
ORDER BY day
"""
with get_conn() as conn:
return conn.execute(sql, (pet_id,)).fetchall()
数据量小的时候这套查询毫无压力,但喂养记录是持续累积的,养三只宠物一年下来轻松上万条。当发现查询变慢时,最直接的手段是加索引。针对高频查询条件建一个复合索引:
CREATE INDEX IF NOT EXISTS idx_feed_pet_time
ON feeding_records (pet_id, feed_time);
这个索引同时覆盖了“按宠物查历史”和“按日期查当日”两类查询。可以用EXPLAIN QUERY PLAN验证效果:建索引前查询会走全表扫描(SCAN TABLE),建索引后变成索引查找(SEARCH TABLE USING INDEX)。不过索引也不是越多越好,每加一个索引都会拖慢写入速度,像本项目的量级,两三个针对性索引足够。
最后还有两个实用建议。第一,定期备份:SQLite单文件的特性让备份变得极其简单,直接复制pets.db文件即可,但前提是没有写入正在进行,稳妥的做法是用conn.execute("VACUUM INTO 'backup.db'")在线导出。第二,如果家庭成员同时在各自的电脑上记录,建议把数据库文件放在共享目录或NAS上,但要注意SQLite在网络文件系统上的锁机制并不可靠,多端并发写入更稳妥的方案是部署一个简单的接口服务统一读写,或者干脆切换到MySQL、PostgreSQL这类客户端服务器数据库。
到这里,一个具备信息管理、日程提醒、周报统计的宠物喂养系统就完成了,核心代码不到两百行。它或许简陋,但覆盖了建库、建表、事务、索引、备份这一整套数据库操作的完整闭环,作为SQLite的入门实战项目再合适不过。后续还可以在此基础上加图形界面或定时提醒,把它扩展成真正顺手的家庭工具。