导读:本期聚焦于印尼程序员创作的《如何用SQLite打造一个植物浇水提醒系统?从建表到定时提醒全流程实战》,敬请观看详情。家里养了一堆绿植,却总是想不起来浇水?这篇文章带你用SQLite从零搭建一个植物浇水提醒系统。我们会先设计植物信息表和浇水记录表,讲清楚表结构怎么定、字段怎么选,再写好增删改查的基础代码。接着重点解决两个核心问题:一是如何根据不同植物的浇水周期自动计算下次浇水日期,二是如何结合Python定时任务让提醒每天准时触发。文中还分享了如何避免重复提醒、如何处理多肉这类长周期植物的特殊需求,以及数据库文件的备份技巧。全部代码基于Python标准库实现,不需要额外安装数据库服务,适合想在真实场景里练手SQLite的新手,做完这个项目你对建表、索引、日期查询这些知识点会有完全不一样的理解。

养植物最难的不是技术,而是记性。多肉两周浇一次,绿萝五天一次,薄荷夏天可能两天就得补水,一旦记混了,要么旱死要么烂根。市面上的提醒App广告多、还要联网权限,其实我们完全可以自己动手,用Python加SQLite做一个本地运行的植物浇水提醒系统。SQLite是一个文件型数据库,不需要安装任何服务端程序,整个系统就是一个py脚本加一个db文件,双击就能跑,特别适合放在家里那台旧笔记本上常年运行。

如何用SQLite打造一个植物浇水提醒系统?从建表到定时提醒全流程实战

一、数据库表结构设计:两张表搞定核心逻辑

设计表结构之前先想清楚业务流程:我有哪些植物,每种植物多久浇一次水,上次浇水是什么时候,下次该什么时候浇。围绕这几个问题,最简洁的方案是两张表——plants表存植物的基础信息,water_logs表存每一次的浇水记录。

plants表建议包含这些字段:id作为主键自增,name存植物名称,water_cycle_days存浇水周期(单位天),last_watered存最近一次浇水日期,created_at记录录入时间。这里有个设计取舍:last_watered其实可以从water_logs表里查出来,单独冗余一份是为了查询效率——每天跑提醒任务时只需要扫一张表,不用做联表聚合。数据量小的时候差别不明显,但养成这种权衡意识对以后做项目很有帮助。

CREATE TABLE IF NOT EXISTS plants (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    water_cycle_days INTEGER NOT NULL DEFAULT 7,
    last_watered DATE,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS water_logs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    plant_id INTEGER NOT NULL,
    watered_date DATE NOT NULL,
    note TEXT DEFAULT '',
    FOREIGN KEY (plant_id) REFERENCES plants(id)
);

-- 给plant_id建索引,查某株植物的浇水历史时速度快很多
CREATE INDEX IF NOT EXISTS idx_water_logs_plant ON water_logs(plant_id);

water_logs表的作用是留存历史记录。假设你某次出差半个月,回来后想看看哪些植物被旱得最惨,或者想统计某株植物一年浇了多少次水,这些分析都依赖完整的日志表。note字段可以记一些备注,比如“加了营养液”或者“浇透了”,方便日后复盘养护习惯。

二、核心代码:增删改查与到期计算

先写基础操作。Python内置了sqlite3模块,直接import就能用,唯一要注意的是连接数据库后要记得commit,否则数据只存在内存里,程序一退出就丢了。

import sqlite3
from datetime import date, timedelta

DB_NAME = "plants.db"

def get_conn():
    conn = sqlite3.connect(DB_NAME)
    conn.row_factory = sqlite3.Row  # 让查询结果可以按列名取值
    return conn

def add_plant(name, cycle_days):
    with get_conn() as conn:
        conn.execute(
            "INSERT INTO plants (name, water_cycle_days, last_watered) VALUES (?, ?, ?)",
            (name, cycle_days, date.today())
        )

def water_plant(plant_id, note=""):
    today = date.today()
    with get_conn() as conn:
        conn.execute("UPDATE plants SET last_watered = ? WHERE id = ?", (today, plant_id))
        conn.execute(
            "INSERT INTO water_logs (plant_id, watered_date, note) VALUES (?, ?, ?)",
            (plant_id, today, note)
        )

上面的代码里有两个细节值得注意。一是用with语句管理连接,代码块结束时会自动提交事务,比手动调用commit更安全;二是SQL语句全部使用参数化写法,问号占位符传值,这样可以杜绝SQL注入问题——虽然这是自己用的小工具,但好习惯从第一行代码就要养。

接下来是整个系统的核心:计算哪些植物今天该浇水。逻辑很简单,用last_watered加上water_cycle_days得到下次浇水日,和今天比较即可。这个计算既可以放在Python里做,也可以直接交给SQL完成。我更推荐SQL方案,一条查询语句就把活干了:

def get_due_plants():
    """查出今天及之前就该浇水的植物"""
    sql = """
        SELECT id, name, water_cycle_days, last_watered,
               date(last_watered, '+' || water_cycle_days || ' days') AS next_date
        FROM plants
        WHERE date(last_watered, '+' || water_cycle_days || ' days') <= date('now', 'localtime')
    """
    with get_conn() as conn:
        return conn.execute(sql).fetchall()

def show_reminder():
    due = get_due_plants()
    if not due:
        print("今天所有植物都不缺水,放心吧!")
        return
    for p in due:
        overdue = (date.today() - (
            date.fromisoformat(p["next_date"]))).days
        print(f"[{p['name']}] 该浇水了!周期{p['water_cycle_days']}天,"
              f"下次浇水日是 {p['next_date']},已超期 {overdue} 天")

这里用到了SQLite的一个实用函数date(),它可以对日期做加减运算,表达式里的双竖线是SQL的字符串拼接符号。另外要提醒一点:date('now')默认返回UTC时间,和北京时间差8小时,所以一定要加上'localtime'修饰,否则晚上八点以后跑脚本,日期会差一天,这是新手最容易踩的坑之一。

三、让提醒自动跑起来:定时任务的三种方案

有了查询逻辑,还得让它每天自动执行。方案主要有三种,各有适用场景。

第一种是程序内部死循环加time.sleep,简单粗暴但不可靠,一旦程序崩溃或电脑重启,提醒就中断了。第二种是Windows下用任务计划程序,Linux或Mac下用crontab,让操作系统每天固定时间调用脚本,这是我最推荐的方式,稳定且不占常驻内存。以Linux为例,执行crontab -e后添加一行0 9 * * * python3 /home/user/plants/reminder.py,就表示每天早上九点执行一次。第三种是在Python里用schedule库做进程内调度,适合脚本本身还要提供交互功能的场景。

无论哪种方案,都建议在脚本里加一个防重复提醒的小机制:建一张reminders表,记录每次提醒的日期,当天已经提醒过就不再发。这个逻辑用SQLite实现只需要几行代码:

def should_remind_today():
    today = date.today().isoformat()
    with get_conn() as conn:
        row = conn.execute(
            "SELECT COUNT(*) AS c FROM reminders WHERE remind_date = ?", (today,)
        ).fetchone()
        if row["c"] == 0:
            conn.execute("INSERT INTO reminders (remind_date) VALUES (?)", (today,))
            return True
    return False

最后再说说提醒的触达方式。如果在局域网内跑,可以用一个叫pushplus或者Server酱的微信推送服务,往它的接口发个HTTP请求就能收到微信消息,代码里用requests.post即可;如果不想联网,Windows下弹个消息框、Linux下发封邮件也完全够用。

四、进阶优化与数据安全

基础版跑起来之后,还有几个方向可以继续打磨。首先是季节差异:多肉夏天休眠要控水,冬天半个月浇一次也没问题,可以在plants表加一个cycle_summercycle_winter字段,查询时根据当前月份选择对应周期,代码里判断一下月份再拼接SQL就行。

其次是数据备份。SQLite虽然是单文件数据库,但如果程序正在写入时直接复制文件,有可能拿到损坏的副本。正确的做法是用它自带的.backup命令,或者在Python里调用conn.backup()方法,这是官方提供的在线备份接口,写入过程中也能安全复制:

import shutil

def backup_db():
    src = sqlite3.connect(DB_NAME)
    dst = sqlite3.connect(f"plants_backup_{date.today().isoformat()}.db")
    with dst:
        src.backup(dst)
    src.close()
    dst.close()

把这个备份函数也挂到定时任务里,每周跑一次,你的浇水数据就万无一失了。做完这个项目你会发现,SQLite的建表、索引、日期函数、事务这些零散知识点,在真实需求里串起来之后理解会深刻得多。接下来你还可以给它加上命令行交互菜单、导出浇水统计报表,甚至接一个温湿度传感器实现全自动判断,这些扩展都能在这个小小的db文件上继续生长。

SQLite植物浇水提醒Python定时任务修改时间:2026-09-13 00:08:48

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