Oracle 库重建/重排后 videos/people 的 id 跳变(2303 段→3408/4600+ 段), 旧 upsert 按 Oracle id 作主键插入时与已存在同 filename/label 行在 UNIQUE 键 上二次冲突报 1062,整批中止、游标不推进。 - upsert_sync_videos: 改为以 filename 业务键去重,命中就地 UPDATE 保留 NAS 原 id(防 sync_events/model_calls/identity_map 的 video_id 外键失效),Oracle id 落 oracle_id 列溯源;未命中插入优先用 Oracle id 对齐子表引用,主键冲突回退自增 - upsert_sync_people: 同模式,以 label 业务键去重(people.id 无外键引用) - ddl.sql: sync_videos/sync_people 的 id 改为 NAS 本地自增主键 + 新增 oracle_id 列 - NAS 现网已迁移(加列/回填/改自增)并重启验证:单批补拉 videos+348 events+1017 people+9 model_calls+500 identity_map+238,游标 09-02 13:55:21 → 09-03 08:51:11
241 lines
13 KiB
SQL
241 lines
13 KiB
SQL
-- ============================================================
|
||
-- Sentinel Home AI - 数据库 DDL
|
||
-- 版本: v1.0
|
||
-- 日期: 2026-08-19
|
||
-- 数据库: MariaDB (MySQL 兼容)
|
||
-- ============================================================
|
||
|
||
-- 创建数据库(如不存在)
|
||
CREATE DATABASE IF NOT EXISTS sentinel_home_ai
|
||
CHARACTER SET utf8mb4
|
||
COLLATE utf8mb4_unicode_ci;
|
||
|
||
USE sentinel_home_ai;
|
||
|
||
-- ============================================================
|
||
-- 1. 任务表 process_tasks
|
||
-- ============================================================
|
||
CREATE TABLE IF NOT EXISTS process_tasks (
|
||
task_id INT AUTO_INCREMENT PRIMARY KEY,
|
||
video_path VARCHAR(500) NOT NULL COMMENT '视频在 NAS 上的绝对路径',
|
||
video_url VARCHAR(500) NOT NULL COMMENT 'Video-Server 提供的下载 URL',
|
||
status ENUM('PENDING','PROCESSING','SUCCESS','FAILED') DEFAULT 'PENDING',
|
||
retry_count INT DEFAULT 0,
|
||
max_retries INT DEFAULT 3,
|
||
next_retry_at DATETIME NULL COMMENT '下次重试时间(退避策略)',
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
error_message TEXT NULL,
|
||
failure_stage ENUM('download','extract','vlm_visual','vlm_fusion','callback','process') NULL COMMENT '失败阶段(process=Edge 消费者处理异常)',
|
||
heartbeat_at DATETIME NULL COMMENT '心跳时间戳(首期预留,不写入)',
|
||
INDEX idx_status (status),
|
||
INDEX idx_created (created_at),
|
||
INDEX idx_next_retry (next_retry_at)
|
||
) ENGINE=InnoDB COMMENT='视频分析任务表';
|
||
|
||
-- ============================================================
|
||
-- 2. 事件表 monitor_events
|
||
-- ============================================================
|
||
CREATE TABLE IF NOT EXISTS monitor_events (
|
||
event_id INT AUTO_INCREMENT PRIMARY KEY,
|
||
task_id INT NOT NULL,
|
||
event_start_time DATETIME NOT NULL COMMENT '事件开始时间',
|
||
event_end_time DATETIME NOT NULL COMMENT '事件结束时间',
|
||
camera_name VARCHAR(50) COMMENT '摄像头名称/位置',
|
||
global_summary TEXT COMMENT 'VLM 生成的整体摘要',
|
||
entities_json JSON NOT NULL COMMENT '实体列表 JSON 数组',
|
||
compute_provider JSON NOT NULL COMMENT '模型来源数组,如 ["ollama","gemini"]',
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX idx_event_start (event_start_time),
|
||
INDEX idx_compute_provider (compute_provider),
|
||
FOREIGN KEY (task_id) REFERENCES process_tasks(task_id) ON DELETE CASCADE
|
||
) ENGINE=InnoDB COMMENT='监控事件聚合表';
|
||
|
||
-- ============================================================
|
||
-- 3. 每日摘要表 daily_summaries(预留)
|
||
-- ============================================================
|
||
CREATE TABLE IF NOT EXISTS daily_summaries (
|
||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||
target_date DATE NOT NULL UNIQUE COMMENT '目标日期',
|
||
summary_text TEXT COMMENT '当日摘要文本',
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
) ENGINE=InnoDB COMMENT='每日摘要表(首期预留)';
|
||
|
||
-- ============================================================
|
||
-- 4. 事件明细表 event_details
|
||
-- 每关键帧一条,由 VLM 输出 frame_details 拆分落库
|
||
-- ============================================================
|
||
CREATE TABLE IF NOT EXISTS event_details (
|
||
detail_id INT AUTO_INCREMENT PRIMARY KEY,
|
||
event_id INT NOT NULL COMMENT '关联 monitor_events.event_id',
|
||
task_id INT NOT NULL COMMENT '冗余,便于查询',
|
||
frame_index INT NOT NULL COMMENT '关键帧序号(1-8)',
|
||
frame_timestamp DATETIME NOT NULL COMMENT '该帧对应的绝对时间点',
|
||
camera_name VARCHAR(50) COMMENT '摄像头位置(如"客厅")',
|
||
person VARCHAR(50) NOT NULL COMMENT '成员名或"人物A/B/C"或"未知访客"',
|
||
action VARCHAR(200) NOT NULL COMMENT '动作描述(AI 自由生成,无枚举)',
|
||
clothing VARCHAR(100) COMMENT '衣着描述',
|
||
is_attention_event BOOLEAN DEFAULT FALSE COMMENT 'AI 自行判断是否为关注事件',
|
||
source_providers JSON NOT NULL COMMENT '识别到该明细的模型来源数组',
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX idx_event_id (event_id),
|
||
INDEX idx_frame_timestamp (frame_timestamp),
|
||
INDEX idx_person (person),
|
||
INDEX idx_queried_date (frame_timestamp),
|
||
FOREIGN KEY (event_id) REFERENCES monitor_events(event_id) ON DELETE CASCADE
|
||
) ENGINE=InnoDB COMMENT='事件明细表(每关键帧一条)';
|
||
|
||
-- ============================================================
|
||
-- 5. 对话记录表 chat_history
|
||
-- ============================================================
|
||
CREATE TABLE IF NOT EXISTS chat_history (
|
||
chat_id INT AUTO_INCREMENT PRIMARY KEY,
|
||
user_question TEXT NOT NULL COMMENT '用户问题',
|
||
ai_answer TEXT NOT NULL COMMENT 'AI 回答',
|
||
context_summary TEXT COMMENT '问答时使用的上下文摘要',
|
||
queried_date DATE COMMENT '查询的日期范围',
|
||
queried_person VARCHAR(50) COMMENT '查询的人物',
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX idx_created (created_at),
|
||
INDEX idx_queried_date (queried_date),
|
||
INDEX idx_queried_person (queried_person)
|
||
) ENGINE=InnoDB COMMENT='用户与 AI 问答记录表';
|
||
|
||
-- ============================================================
|
||
-- 6. 家庭成员表 family_members
|
||
-- 交互式命名:VLM 自动按特征提取"人物A/B/C",用户命名为真实名字
|
||
-- ============================================================
|
||
CREATE TABLE IF NOT EXISTS family_members (
|
||
member_id INT AUTO_INCREMENT PRIMARY KEY,
|
||
abstract_label VARCHAR(20) NOT NULL UNIQUE COMMENT 'VLM 首次提取的抽象标识,如"人物A"',
|
||
real_name VARCHAR(50) COMMENT '用户命名的真实名字,如"汤圆";NULL 表示未命名',
|
||
feature_description TEXT COMMENT 'VLM 提取的特征描述',
|
||
first_seen_at DATETIME COMMENT '首次被提取的时间',
|
||
named_at DATETIME NULL COMMENT '用户命名的时间',
|
||
named_by VARCHAR(50) COMMENT '命名人',
|
||
is_active BOOLEAN DEFAULT TRUE,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
INDEX idx_real_name (real_name),
|
||
INDEX idx_abstract_label (abstract_label),
|
||
INDEX idx_is_active (is_active)
|
||
) ENGINE=InnoDB COMMENT='家庭成员表(交互式命名)';
|
||
|
||
-- ============================================================
|
||
-- 7. 甲骨文同步镜像表(新架构 v2,2026-08-21)
|
||
-- NAS 每 30 分钟从甲骨文 FAM-Edge 拉增量,镜像到本地,仅作展示
|
||
-- 字段对齐 Oracle 端 SQLite 库(oracle_db.py)
|
||
-- ============================================================
|
||
|
||
-- 7.1 视频会话表(Oracle videos 镜像)
|
||
-- v2 (2026-09-03):id 改为 NAS 本地自增主键(不再直接等于 Oracle id,因 Oracle 库
|
||
-- 重建/重排会复用 id,导致 filename UNIQUE 二次冲突 1062);Oracle 的 videos.id 仅
|
||
-- 落 oracle_id 列溯源。业务唯一键仍为 filename。子表 video_id 引用仍用 Oracle id
|
||
-- (新视频插入时 id=oracle_id;旧视频原地更新保留原 id,外键不失效)。
|
||
CREATE TABLE IF NOT EXISTS sync_videos (
|
||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT 'NAS 本地自增主键',
|
||
oracle_id INT COMMENT 'Oracle videos.id(仅溯源参考)',
|
||
drive_file_id VARCHAR(255) COMMENT 'Google 硬盘文件 ID',
|
||
filename VARCHAR(500) NOT NULL UNIQUE COMMENT '视频文件名(唯一)',
|
||
camera_name VARCHAR(50) COMMENT '摄像头名称/位置',
|
||
duration_sec DOUBLE DEFAULT 0 COMMENT '视频时长(秒)',
|
||
event_start_time VARCHAR(32) COMMENT '视频开始时间(文本)',
|
||
status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/done/failed',
|
||
summary_json LONGTEXT COMMENT '全局摘要文本',
|
||
events_json LONGTEXT COMMENT '事件列表 JSON 数组(冗余,便于查询)',
|
||
people_json LONGTEXT COMMENT '人物列表 JSON 数组',
|
||
compute_provider VARCHAR(255) COMMENT '模型来源,如 gemini / nvidia',
|
||
created_at VARCHAR(32),
|
||
updated_at VARCHAR(32),
|
||
processed_at VARCHAR(32),
|
||
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '最近一次同步写入时间',
|
||
INDEX idx_processed (processed_at),
|
||
INDEX idx_status (status)
|
||
) ENGINE=InnoDB COMMENT='甲骨文视频会话镜像表';
|
||
|
||
-- 7.2 事件明细表(Oracle events 镜像)
|
||
CREATE TABLE IF NOT EXISTS sync_events (
|
||
id INT PRIMARY KEY COMMENT 'Oracle events.id',
|
||
video_id INT NOT NULL COMMENT '关联 sync_videos.id',
|
||
ts VARCHAR(32) COMMENT '事件时间点(文本)',
|
||
description TEXT COMMENT '事件描述',
|
||
person_list_json LONGTEXT COMMENT '涉及人物 JSON 数组(字符串或标签)',
|
||
person_appearances_json LONGTEXT COMMENT '该时刻人物结构化特征 JSON([{uid,features,action}])',
|
||
is_attention_event TINYINT(1) DEFAULT 0 COMMENT 'AI 判断是否为关注事件',
|
||
updated_at VARCHAR(32),
|
||
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX idx_video (video_id),
|
||
INDEX idx_ts (ts)
|
||
) ENGINE=InnoDB COMMENT='甲骨文事件镜像表';
|
||
|
||
-- 7.3 人物规范表(Oracle people 镜像)
|
||
-- v2 (2026-09-03):同 7.1,id 改为 NAS 本地自增主键,Oracle people.id 落 oracle_id
|
||
-- 列溯源;业务唯一键仍为 label。people.id 无外键引用,仅作展示排序。
|
||
CREATE TABLE IF NOT EXISTS sync_people (
|
||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT 'NAS 本地自增主键',
|
||
oracle_id INT COMMENT 'Oracle people.id(仅溯源参考)',
|
||
label VARCHAR(100) NOT NULL UNIQUE COMMENT '抽象标识,如"人物A"',
|
||
canonical_name VARCHAR(100) COMMENT '规范名(用户命名或 LLM 合并),NULL 表示未命名',
|
||
first_seen VARCHAR(32) COMMENT '首次出现时间',
|
||
appearances INT DEFAULT 0 COMMENT '出现次数',
|
||
source VARCHAR(20) DEFAULT 'llm' COMMENT 'llm / manual(manual 优先不被覆盖)',
|
||
features_json LONGTEXT COMMENT '人物结构化特征 JSON(性别/年龄/身形/发型/衣着/面部/辨识)',
|
||
display_uid VARCHAR(100) COMMENT '大模型给的人物 UID(与 label 一致时冗余)',
|
||
updated_at VARCHAR(32),
|
||
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX idx_label (label),
|
||
INDEX idx_canonical (canonical_name)
|
||
) ENGINE=InnoDB COMMENT='甲骨文人物规范镜像表';
|
||
|
||
-- 7.4 同步游标表
|
||
CREATE TABLE IF NOT EXISTS sync_cursor (
|
||
`key` VARCHAR(50) PRIMARY KEY,
|
||
`value` VARCHAR(64) COMMENT '上次成功拉取到的 server_time(ISO 文本)'
|
||
) ENGINE=InnoDB COMMENT='同步游标表';
|
||
|
||
-- 7.5 人物对应关系表(Oracle person_identity_map 镜像,2026-08-22 新增)
|
||
-- 闭集人物识别:某视频里 Gemini 给的原始 uid 与解析出的规范名(爷爷/爸爸/媳妇/
|
||
-- 汤圆)之间的映射,供事件时间轴"纠错"按钮定位、追溯识别来源
|
||
-- v2 (2026-08-29):id 改为 NAS 本地自增主键,业务键 (video_id, raw_uid) 唯一;
|
||
-- Oracle 的 person_identity_map.id 只落 oracle_id 列溯源。原以 Oracle id 为主键,
|
||
-- 一旦 Oracle 库重建/复用 id 会引发 uq_video_raw_uid 二次冲突 (1062)。
|
||
CREATE TABLE IF NOT EXISTS sync_identity_map (
|
||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT 'NAS 本地自增主键',
|
||
oracle_id INT COMMENT 'Oracle person_identity_map.id(仅溯源参考)',
|
||
video_id INT NOT NULL COMMENT '关联 sync_videos.id',
|
||
raw_uid VARCHAR(100) COMMENT '该视频里 Gemini 给的原始 uid',
|
||
canonical_name VARCHAR(100) COMMENT '当前生效的规范名',
|
||
source VARCHAR(20) COMMENT 'rule / auto_id / manual(manual 优先不被覆盖)',
|
||
updated_at VARCHAR(32),
|
||
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE KEY uq_video_raw_uid (video_id, raw_uid),
|
||
INDEX idx_video (video_id)
|
||
) ENGINE=InnoDB COMMENT='甲骨文人物对应关系镜像表';
|
||
|
||
-- 8.1 云端模型调用统计镜像表(Oracle model_calls 镜像)
|
||
CREATE TABLE IF NOT EXISTS sync_model_calls (
|
||
id INT PRIMARY KEY COMMENT 'Oracle model_calls.id',
|
||
provider VARCHAR(50) COMMENT 'gemini / nvidia',
|
||
model VARCHAR(100) COMMENT '具体模型名(如 gemini-flash-latest)',
|
||
video_id INT COMMENT '关联视频 id',
|
||
filename VARCHAR(500) COMMENT '视频文件名',
|
||
started_at VARCHAR(32) COMMENT '请求开始时间',
|
||
duration_sec DOUBLE COMMENT '请求耗时(秒)',
|
||
success TINYINT(1) DEFAULT 0 COMMENT '1=成功 0=失败',
|
||
error VARCHAR(500) COMMENT '失败原因(429_quota/timeout/http_xxx 等)',
|
||
created_at VARCHAR(32) COMMENT '记录入库时间',
|
||
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX idx_provider (provider),
|
||
INDEX idx_created (created_at),
|
||
INDEX idx_success (success)
|
||
) ENGINE=InnoDB COMMENT='云端模型调用统计镜像表';
|
||
|
||
-- ============================================================
|
||
-- 验证
|
||
-- ============================================================
|
||
SELECT 'DDL 执行完成' AS message;
|
||
SELECT TABLE_NAME, TABLE_COMMENT
|
||
FROM information_schema.TABLES
|
||
WHERE TABLE_SCHEMA = 'sentinel_home_ai'
|
||
ORDER BY TABLE_NAME;
|