9、联系人分组:Groups表结构、创建分组、将联系人加入分组、查询分组内联系人
联系人分组这个功能,说白了就是把一堆联系人归类管理。你想想看,手机里几百个联系人,如果没有分组,找个人得翻半天。我早期做联系人模块时,第一版就没做分组,结果被用户吐槽“连个群组功能都没有”。嗯,后来老老实实补上了。
分组功能的核心,其实就三件事:建组、加人、查人。咱们一个一个来拆。
9.1 Groups表结构设计
先看表结构。我个人习惯把分组信息单独放一张表,叫groups。这样结构清晰,扩展也方便。
CREATE TABLE groups (
group_id INTEGER PRIMARY KEY AUTOINCREMENT,
group_name TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now','localtime')),
updated_at TEXT DEFAULT (datetime('now','localtime'))
);
这张表很简单,就四个字段:
- group_id:主键,自增。每个分组唯一标识。
- group_name:分组名称。比如“家人”、“同事”、“球友”。
- created_at:创建时间。我习惯用本地时间字符串,方便直接展示。
- updated_at:更新时间。每次修改分组信息时更新这个字段。
那联系人和分组怎么关联?需要一个中间表。因为一个联系人可以属于多个分组,一个分组也可以有多个联系人——典型的多对多关系。
CREATE TABLE contact_group_map (
map_id INTEGER PRIMARY KEY AUTOINCREMENT,
contact_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
added_at TEXT DEFAULT (datetime('now','localtime')),
FOREIGN KEY (contact_id) REFERENCES contacts(contact_id) ON DELETE CASCADE,
FOREIGN KEY (group_id) REFERENCES groups(group_id) ON DELETE CASCADE
);
这里我加了一个added_at字段,记录联系人加入分组的时间。你可能觉得没必要,但我在项目中遇到过用户问“我什么时候把张三加到同事组的?”——有这个字段就能直接查出来。
核心思路:分组和联系人是多对多关系,必须用中间表解耦。不要试图在联系人表里加一个“分组ID”字段,那会把自己坑死。
小提示:外键约束建议加上ON DELETE CASCADE。这样删除分组时,中间表里的关联记录会自动清理,不会留下脏数据。
9.2 创建分组
创建分组很简单,就是往groups表里插一条记录。但要注意:分组名称不能重复。我早期没加这个限制,结果用户创建了三个“家人”分组,自己都搞混了。
public long createGroup(String groupName) {
// 先检查分组名是否已存在
String checkSql = "SELECT group_id FROM groups WHERE group_name = ?";
Cursor cursor = db.rawQuery(checkSql, new String[]{groupName});
if (cursor.getCount() > 0) {
cursor.close();
return -1; // 分组已存在
}
cursor.close();
// 插入新分组
ContentValues values = new ContentValues();
values.put("group_name", groupName);
return db.insert("groups", null, values);
}
这段代码里,我先查了一下同名分组是否存在。如果存在就返回-1,上层可以提示用户“该分组已存在”。
注意:不要直接用INSERT OR REPLACE来处理重复问题。那样会把已有的分组覆盖掉,数据就丢了。我曾经踩过这个坑,后来老老实实先查后插。
9.3 将联系人加入分组
把联系人加入分组,本质就是往contact_group_map表里插一条关联记录。但有个细节:同一个联系人不能重复加入同一个分组。
public boolean addContactToGroup(long contactId, long groupId) {
// 检查是否已经在该分组中
String checkSql = "SELECT map_id FROM contact_group_map " +
"WHERE contact_id = ? AND group_id = ?";
Cursor cursor = db.rawQuery(checkSql,
new String[]{String.valueOf(contactId), String.valueOf(groupId)});
if (cursor.getCount() > 0) {
cursor.close();
return false; // 已在分组中
}
cursor.close();
// 插入关联记录
ContentValues values = new ContentValues();
values.put("contact_id", contactId);
values.put("group_id", groupId);
long result = db.insert("contact_group_map", null, values);
return result != -1;
}
这里我做了重复检查。如果联系人已经在分组里了,直接返回false。上层可以提示“该联系人已在此分组中”。
小技巧:如果你想让代码更简洁,可以在contact_group_map表上建一个联合唯一索引:UNIQUE(contact_id, group_id)。这样数据库层面就杜绝了重复,代码里可以省掉查询步骤。
9.4 查询分组内联系人
查询分组内的联系人,需要做三表联查:groups、contact_group_map、contacts。听起来复杂,其实写起来很直接。
public List<Contact> getContactsInGroup(long groupId) {
String sql = "SELECT c.contact_id, c.name, c.phone, c.email " +
"FROM contacts c " +
"INNER JOIN contact_group_map m ON c.contact_id = m.contact_id " +
"WHERE m.group_id = ? " +
"ORDER BY c.name ASC";
Cursor cursor = db.rawQuery(sql, new String[]{String.valueOf(groupId)});
List<Contact> contactList = new ArrayList<>();
while (cursor.moveToNext()) {
Contact contact = new Contact();
contact.setId(cursor.getLong(0));
contact.setName(cursor.getString(1));
contact.setPhone(cursor.getString(2));
contact.setEmail(cursor.getString(3));
contactList.add(contact);
}
cursor.close();
return contactList;
}
这个查询返回的是分组内所有联系人的详细信息。注意我用了INNER JOIN,只返回有有效关联的记录。如果某个联系人在中间表里有记录但联系人表里被删了,这个查询会自动过滤掉。
重点:联查时一定要用INNER JOIN,不要用LEFT JOIN。因为我们要的是“分组内确实存在的联系人”,不是“可能不存在的联系人”。
9.5 知识体系总览
下面这张图把分组功能的核心逻辑串起来了。你可以看到数据是怎么流动的:
9.6 避坑指南
最后分享几个我踩过的坑:
- 不要用联系人表里的字段存分组信息。我见过有人直接在
contacts表里加一个group_name字段,结果一个联系人只能属于一个分组,用户骂翻了。 - 删除分组时要同步清理中间表。如果忘了删
contact_group_map里的关联记录,下次查询时会查出一些“幽灵数据”。加上ON DELETE CASCADE就能自动处理。 - 分组名要加唯一约束。不然用户创建了“家人”和“家人 ”(带空格),自己都分不清哪个是哪个。
分组功能看起来简单,但设计得好不好,直接影响用户体验。把表结构设计清楚,操作逻辑做严谨,后面扩展起来就轻松多了。
公众号:蓝海资料掘金营,微信deep3321