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 查询分组内联系人

查询分组内的联系人,需要做三表联查groupscontact_group_mapcontacts。听起来复杂,其实写起来很直接。

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 知识体系总览

下面这张图把分组功能的核心逻辑串起来了。你可以看到数据是怎么流动的:

联系人分组核心逻辑 groups 表 group_id (主键) group_name created_at / updated_at contact_group_map 表 map_id (主键) contact_id (外键) group_id (外键) contacts 表 contact_id (主键) name / phone / email 其他字段... 1:N N:1 操作流程 ① 创建分组 INSERT INTO groups ② 加入联系人 INSERT INTO contact_group_map ③ 查询分组内联系人 SELECT ... JOIN ... WHERE 多对多关系:一个分组包含多个联系人,一个联系人可属于多个分组 核心:中间表解耦,先查后插防重复

9.6 避坑指南

最后分享几个我踩过的坑:

  • 不要用联系人表里的字段存分组信息。我见过有人直接在contacts表里加一个group_name字段,结果一个联系人只能属于一个分组,用户骂翻了。
  • 删除分组时要同步清理中间表。如果忘了删contact_group_map里的关联记录,下次查询时会查出一些“幽灵数据”。加上ON DELETE CASCADE就能自动处理。
  • 分组名要加唯一约束。不然用户创建了“家人”和“家人 ”(带空格),自己都分不清哪个是哪个。

分组功能看起来简单,但设计得好不好,直接影响用户体验。把表结构设计清楚,操作逻辑做严谨,后面扩展起来就轻松多了。


公众号:蓝海资料掘金营,微信deep3321