血压和血糖是反映人体健康状况的两项核心指标,对于高血压、糖尿病等慢性病患者来说,长期、持续地记录这些数据能够帮助医生更准确地评估病情变化和治疗效果。SQLite作为一款轻量级的嵌入式关系型数据库,不需要单独的服务器进程,整个数据库就是一个文件,部署简单、迁移方便,非常适合用来构建个人健康数据管理系统。本文将以一个完整的血压血糖记录项目为例,从表结构设计、数据操作到统计分析,带你全面掌握SQLite在实际项目中的应用。

一、数据库表结构设计与创建
设计合理的表结构是整个项目的基础。血压血糖记录涉及的核心数据包括:收缩压(高压)、舒张压(低压)、心率、血糖值、测量时间、测量状态(空腹或餐后)以及备注信息。这些字段需要根据实际使用场景仔细规划数据类型和约束条件,既要保证数据完整性,又要兼顾查询效率。
首先,我们需要一张主表来存储所有测量记录。每条记录应包含唯一标识ID、测量日期时间、收缩压值、舒张压值、心率、血糖值、测量类型(血压、血糖或两者都有)以及备注。其中收缩压和舒张压的单位通常是mmHg,血糖值的单位是mmol/L,这些数值字段应使用REAL类型以支持小数。测量时间建议用TEXT类型存储ISO 8601格式的日期时间字符串,这样既可读又便于排序和范围查询。
除了主记录表,还可以设计一张用户信息表,存储用户的姓名、性别、出生日期、基础疾病等基本信息,方便后续扩展多用户功能。同时,为了提高查询效率,需要在测量时间字段上创建索引,因为绝大多数查询都会按时间范围进行筛选。如果后续需要按测量类型频繁筛选,也可以在该字段上建立索引。
-- 创建血压血糖记录主表
CREATE TABLE health_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
record_date TEXT NOT NULL, -- 测量时间,格式:YYYY-MM-DD HH:MM:SS
systolic_pressure REAL, -- 收缩压(高压),单位:mmHg
diastolic_pressure REAL, -- 舒张压(低压),单位:mmHg
heart_rate INTEGER, -- 心率,单位:次/分钟
blood_glucose REAL, -- 血糖值,单位:mmol/L
glucose_type TEXT, -- 血糖测量类型:fasting(空腹) / postprandial(餐后)
notes TEXT, -- 备注信息
created_at TEXT DEFAULT (datetime('now', 'localtime')) -- 记录创建时间
);
-- 创建用户信息表
CREATE TABLE user_profile (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
gender TEXT CHECK(gender IN ('male', 'female')),
birth_date TEXT,
medical_conditions TEXT, -- 基础疾病信息
created_at TEXT DEFAULT (datetime('now', 'localtime'))
);
-- 在测量时间字段上创建索引,加速按时间范围查询
CREATE INDEX idx_record_date ON health_records(record_date);
-- 在血糖测量类型上创建索引
CREATE INDEX idx_glucose_type ON health_records(glucose_type);
上面的表结构设计中,有几个关键点值得注意。AUTOINCREMENT关键字确保ID自动递增且不会复用已删除的ID,这在健康数据管理中很重要,因为每条记录都应具有唯一且不可篡改的标识。CHECK约束用于限制性别字段只能取特定值,防止脏数据写入。DEFAULT子句利用SQLite内置的datetime()函数自动填充记录创建时间,省去了应用程序中手动设置时间的麻烦。
关于数据类型的选择,SQLite采用动态类型系统,这意味着你声明的类型更多是一种类型亲和性建议而非强制约束。例如,声明为REAL的字段仍然可以存储整数或文本,但SQLite会尽量将其转换为浮点数。在实际项目中,建议在应用层也做数据校验,双重保障数据质量。此外,notes字段使用TEXT类型且没有长度限制,SQLite的TEXT类型可以存储任意长度的字符串,非常适合存放用户自由填写的备注内容。
二、数据插入与查询操作
表结构创建完成后,接下来就是数据的增删改查操作。这是日常使用中最频繁的操作,需要熟练掌握各种SQL语句的编写技巧。对于血压血糖记录系统来说,插入操作通常发生在用户完成测量后,查询操作则用于查看历史记录和生成报告。
插入数据时,建议使用参数化查询而非字符串拼接,这样可以有效防止SQL注入攻击,同时也能处理包含特殊字符的备注信息。在Python中操作SQLite时,可以使用问号占位符配合元组传参的方式实现参数化插入。下面展示几种常见的数据插入场景,包括同时记录血压和血糖、只记录血压、只记录血糖等情况。
-- 插入一条完整的血压血糖记录
INSERT INTO health_records
(record_date, systolic_pressure, diastolic_pressure, heart_rate, blood_glucose, glucose_type, notes)
VALUES
('2024-01-15 08:30:00', 125.0, 82.0, 72, 5.6, 'fasting', '早晨空腹测量,感觉良好');
-- 只插入血压记录(血糖字段为空)
INSERT INTO health_records
(record_date, systolic_pressure, diastolic_pressure, heart_rate)
VALUES
('2024-01-15 20:00:00', 130.0, 85.0, 75);
-- 只插入血糖记录(血压字段为空)
INSERT INTO health_records
(record_date, blood_glucose, glucose_type, notes)
VALUES
('2024-01-15 14:00:00', 8.2, 'postprandial', '午餐后两小时测量');
查询操作是系统的核心功能。最基本的查询是按时间倒序获取所有记录,方便用户查看最新的测量数据。更实用的查询包括:按日期范围筛选、按测量类型筛选、查找异常值等。对于血压数据,正常范围是收缩压90到140mmHg、舒张压60到90mmHg;空腹血糖正常范围是3.9到6.1mmol/L,餐后两小时血糖正常范围是3.9到7.8mmol/L。利用这些医学标准,可以编写查询语句来找出所有异常记录,提醒用户关注健康风险。
-- 查询最近10条记录,按时间倒序排列 SELECT * FROM health_records ORDER BY record_date DESC LIMIT 10; -- 查询指定日期范围内的记录 SELECT * FROM health_records WHERE record_date >= '2024-01-01 00:00:00' AND record_date <= '2024-01-31 23:59:59' ORDER BY record_date ASC; -- 查找血压异常记录(收缩压大于140 或 舒张压大于90) SELECT record_date, systolic_pressure, diastolic_pressure, notes FROM health_records WHERE systolic_pressure > 140 OR diastolic_pressure > 90 ORDER BY record_date DESC; -- 查找空腹血糖异常记录(大于6.1 mmol/L) SELECT record_date, blood_glucose, notes FROM health_records WHERE glucose_type = 'fasting' AND blood_glucose > 6.1 ORDER BY record_date DESC; -- 更新指定记录的备注信息 UPDATE health_records SET notes = '补充备注:测量前刚运动完' WHERE id = 1; -- 删除指定ID的记录 DELETE FROM health_records WHERE id = 1;
在实际应用中,更新和删除操作需要格外谨慎。健康数据一旦记录,通常不建议删除,因为每一条数据都是病史的一部分。更合理的做法是增加一个is_deleted字段实现软删除,或者增加is_modified标记来追踪修改历史。如果确实需要物理删除,务必在删除前做好数据备份。
另外,对于批量数据导入场景,比如从其他系统迁移历史数据,可以使用SQLite的事务机制来提升插入性能。默认情况下,每条INSERT语句都会自动提交事务,这在批量插入时会产生大量磁盘I/O。通过显式开启事务,将多条插入语句包裹在一个事务中,可以显著提升写入速度,通常能快几十倍甚至上百倍。在Python中,使用connection.commit()来提交事务,在批量插入完成后统一调用即可。
三、数据统计与趋势分析
单纯的记录数据只是第一步,真正有价值的是对数据进行统计分析,从中发现健康趋势和潜在风险。SQLite提供了丰富的聚合函数和日期处理函数,足以支撑日常的统计分析需求。通过合理的SQL查询,我们可以计算平均值、最大值、最小值,按天、按周、按月分组统计,甚至实现简单的趋势分析。
首先来看基本的统计聚合查询。我们可以计算某段时间内的平均血压、平均血糖,以及最高值和最低值。这些指标能够帮助用户和医生快速了解这段时间内的整体控制情况。SQLite的strftime()函数可以提取日期的各个部分,配合GROUP BY子句就能实现按天、按周、按月的分组统计,非常灵活。
-- 统计2024年1月的平均血压和血糖
SELECT
COUNT(*) AS total_records,
ROUND(AVG(systolic_pressure), 1) AS avg_systolic,
ROUND(AVG(diastolic_pressure), 1) AS avg_diastolic,
ROUND(AVG(heart_rate), 0) AS avg_heart_rate,
MAX(systolic_pressure) AS max_systolic,
MIN(systolic_pressure) AS min_systolic,
ROUND(AVG(blood_glucose), 2) AS avg_glucose,
MAX(blood_glucose) AS max_glucose,
MIN(blood_glucose) AS min_glucose
FROM health_records
WHERE record_date >= '2024-01-01'
AND record_date < '2024-02-01';
-- 按天分组统计每日平均血压
SELECT
strftime('%Y-%m-%d', record_date) AS measure_date,
COUNT(*) AS record_count,
ROUND(AVG(systolic_pressure), 1) AS avg_systolic,
ROUND(AVG(diastolic_pressure), 1) AS avg_diastolic,
ROUND(AVG(blood_glucose), 2) AS avg_glucose
FROM health_records
GROUP BY strftime('%Y-%m-%d', record_date)
ORDER BY measure_date DESC
LIMIT 30;
-- 按月统计空腹和餐后血糖平均值
SELECT
strftime('%Y-%m', record_date) AS month,
glucose_type,
COUNT(*) AS count,
ROUND(AVG(blood_glucose), 2) AS avg_glucose
FROM health_records
WHERE glucose_type IS NOT NULL
GROUP BY strftime('%Y-%m', record_date), glucose_type
ORDER BY month DESC, glucose_type;
趋势分析是健康数据管理的高级功能。通过对比不同时间段的平均值,可以判断血压和血糖是否呈现上升或下降趋势。一个实用的做法是使用窗口函数或自连接来计算环比变化率。SQLite从3.25.0版本开始支持窗口函数,这为趋势分析提供了强大的工具。我们可以用LAG()函数获取前一行的值,然后计算与当前行的差值,从而得出变化趋势。
-- 使用窗口函数计算每日平均血压的环比变化
WITH daily_avg AS (
SELECT
strftime('%Y-%m-%d', record_date) AS measure_date,
ROUND(AVG(systolic_pressure), 1) AS avg_systolic,
ROUND(AVG(diastolic_pressure), 1) AS avg_diastolic
FROM health_records
WHERE systolic_pressure IS NOT NULL
GROUP BY strftime('%Y-%m-%d', record_date)
)
SELECT
measure_date,
avg_systolic,
avg_diastolic,
LAG(avg_systolic, 1) OVER (ORDER BY measure_date) AS prev_systolic,
ROUND(avg_systolic - LAG(avg_systolic, 1) OVER (ORDER BY measure_date), 1) AS systolic_change,
CASE
WHEN avg_systolic > LAG(avg_systolic, 1) OVER (ORDER BY measure_date) THEN '上升'
WHEN avg_systolic < LAG(avg_systolic, 1) OVER (ORDER BY measure_date) THEN '下降'
ELSE '持平'
END AS trend
FROM daily_avg
ORDER BY measure_date DESC
LIMIT 14;
-- 统计连续高于正常范围的次数(血压偏高预警)
WITH abnormal_records AS (
SELECT
record_date,
systolic_pressure,
diastolic_pressure,
ROW_NUMBER() OVER (ORDER BY record_date) AS row_num
FROM health_records
WHERE systolic_pressure > 140 OR diastolic_pressure > 90
)
SELECT
MIN(record_date) AS first_abnormal,
MAX(record_date) AS last_abnormal,
COUNT(*) AS abnormal_count,
ROUND(AVG(systolic_pressure), 1) AS avg_systolic,
ROUND(AVG(diastolic_pressure), 1) AS avg_diastolic
FROM abnormal_records;
上面的查询中,CTE(公共表表达式)的使用让复杂查询变得清晰可读。先将每日平均值计算出来作为临时结果集,再在外层查询中使用窗口函数进行环比计算,逻辑分明、易于维护。CASE WHEN语句将数值变化转换为直观的文字描述,让非技术用户也能快速理解趋势方向。这种设计思路在实际项目中非常实用,尤其是面向普通用户的健康管理系统。
对于需要导出数据生成报告的场景,SQLite的GROUP_CONCAT函数可以将多行数据拼接成一个字符串,方便生成CSV格式的导出数据。同时,也可以利用strftime函数对日期进行格式化,使输出更加友好。如果项目需要可视化展示,可以在应用层读取SQLite查询结果,再通过图表库渲染折线图、柱状图等,直观呈现血压血糖的变化趋势。
最后需要提醒的是,随着数据量增长,查询性能可能成为瓶颈。定期执行ANALYZE命令更新统计信息、使用VACUUM命令整理数据库碎片、合理创建复合索引等手段都能有效保持查询效率。对于个人健康数据来说,数据量通常不会太大,一年约几千条记录,SQLite的性能完全足够。但如果需要管理多用户的大量数据,也可以考虑迁移到MySQL或PostgreSQL等更强大的数据库系统,届时表结构和SQL语句的迁移成本也相对较低,因为标准SQL语法在各大数据库之间是高度兼容的。