SQLite实战:如何用Python打造一个宠物喂养日程管理系统?

来源:Vuejs社区作者:马来西亚程序员头衔:程序员
导读:本期聚焦于马来西亚程序员创作的《SQLite实战:如何用Python打造一个宠物喂养日程管理系统?》,敬请观看详情。家里养了猫狗之后,喂食时间总是记不住?本文带你用SQLite和Python动手做一个宠物喂养日程管理工具。文章从数据库表结构设计讲起,介绍pets和feeding_records两张核心表的建表方式,再演示增删改查的完整代码,包括录入宠物信息、添加喂养记录、查询当日待喂清单等功能。随后深入讲解如何用日期函数统计喂养频次、如何通过索引优化查询速度,以及备份数据库和处理并发写入的实用技巧。整个项目不需要额外安装数据库服务,单文件即可运行,适合Python初学者练手,也适合想了解SQLite在实际场景中落地应用的开发者参考。

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

SQLite实战:如何用Python打造一个宠物喂养日程管理系统?

一、设计数据库表结构:先想清楚要存什么

动手写代码之前,先把需求拆解清楚。这个系统的核心对象有两个:宠物本身,以及每一条喂养记录。宠物需要记录名字、品种、生日、体重等基础信息;喂养记录则要关联到具体哪只宠物,记录喂养时间、喂的食物类型、份量,以及是谁喂的——最后这一项在很多家庭的真实场景里非常关键,能有效避免重复投喂。

据此设计两张表: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的入门实战项目再合适不过。后续还可以在此基础上加图形界面或定时提醒,把它扩展成真正顺手的家庭工具。

SQLite宠物喂养管理Python数据库修改时间:2026-09-09 03:48:36

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260909/53171.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。