4、客户管理模块表结构设计:客户表(Customer)字段设计、客户分类与标签设计、客户地址表设计

客户管理模块,说白了就是CRM系统的地基。你想想看,连客户是谁、在哪儿、怎么分类都没搞清楚,后面做销售漏斗、做客户画像,那都是空中楼阁。

我这些年接手过的CRM项目,少说也有十几个。每次做客户表设计,我都会先问自己一个问题:这张表要撑住未来三年的业务量吗? 嗯,这个问题很关键。因为客户表一旦上线,后面改字段的成本高得吓人。

4.1 客户表(Customer)字段设计

先说说客户表的核心字段。我个人的习惯是,把字段分成三组:基础信息、业务属性、系统控制

字段分组 字段名 类型 说明
基础信息 customer_id BIGINT 主键,自增或雪花ID
基础信息 customer_name VARCHAR(100) 客户名称(个人/企业)
基础信息 phone VARCHAR(20) 手机号,建议加唯一索引
基础信息 email VARCHAR(100) 邮箱,可选
业务属性 customer_type TINYINT 客户类型:1个人 2企业
业务属性 source VARCHAR(30) 来源渠道:官网、转介绍、广告
业务属性 industry VARCHAR(50) 所属行业
系统控制 status TINYINT 状态:0潜在 1正式 2流失
系统控制 created_at DATETIME 创建时间
系统控制 updated_at DATETIME 更新时间
我的小建议: phone 字段一定要加唯一索引。我在项目中遇到过,因为没加唯一索引,同一个客户被录入了三次,后面做营销活动时短信发重了,客户直接投诉。嗯,这种坑踩一次就够了。

这里有个容易忽略的点:客户ID用自增还是雪花ID? 我个人倾向用雪花ID。为什么?因为自增ID在分库分表时特别痛苦,而且容易暴露业务量。你想想看,如果客户ID是连续的,竞争对手一看就知道你每天新增多少客户。

4.2 客户分类与标签设计

客户分类和标签,这是CRM系统里最能体现业务深度的地方。很多新手会把分类和标签混为一谈,其实它们完全是两码事。

分类是树形的,标签是扁平的。 分类讲究层级关系,比如「VIP客户 → 黄金VIP → 年度消费10万+」。标签则是打散的,比如「高意向」「近期活跃」「投诉过」。一个客户只能属于一个分类,但可以挂多个标签。

4.2.1 客户分类表设计

CREATE TABLE customer_category (
    category_id   INT PRIMARY KEY AUTO_INCREMENT,
    category_name VARCHAR(50) NOT NULL,
    parent_id     INT DEFAULT 0,
    level         TINYINT DEFAULT 1,
    sort_order    INT DEFAULT 0,
    created_at    DATETIME
);

这个表的核心是 parent_id 字段。通过它,我们可以实现无限级分类。我记得有一次,业务方要求支持五级分类,从「行业 → 规模 → 区域 → 等级 → 标签组」。如果不用 parent_id 这种设计,光建表就得建五张。

避坑指南: 我曾经见过有人用 path 字段存路径(比如 "1,3,7")来实现分类查询。这种做法在数据量小的时候还行,一旦超过10万条,like查询直接让数据库崩溃。老老实实用 parent_id + 递归查询,或者用闭包表,别走捷径。

4.2.2 客户标签设计

标签设计,我推荐用KV结构。什么意思?就是标签名和标签值分开存。

CREATE TABLE customer_tag (
    tag_id    INT PRIMARY KEY AUTO_INCREMENT,
    tag_name  VARCHAR(30) NOT NULL UNIQUE,
    tag_type  TINYINT DEFAULT 0,  -- 0系统标签 1自定义标签
    color     VARCHAR(10)         -- 展示颜色
);

CREATE TABLE customer_tag_rel (
    id          BIGINT PRIMARY KEY AUTO_INCREMENT,
    customer_id BIGINT NOT NULL,
    tag_id      INT NOT NULL,
    created_at  DATETIME,
    UNIQUE KEY uk_customer_tag (customer_id, tag_id)
);

为什么用关联表?因为一个客户可能有几十个标签。如果直接在客户表里加一个 tags 字段存逗号分隔的ID,查询时你会哭的。我接手过一个遗留系统,就是用逗号分隔存的标签,每次查「所有带'高意向'标签的客户」都要用 FIND_IN_SET,那性能,啧啧。

4.3 客户地址表设计

地址这块,很多人觉得简单——不就是省市区加详细地址吗?其实坑不少。

我建议把地址拆成两张表:地址字典表客户地址表

4.3.1 地址字典表

CREATE TABLE region (
    region_id   INT PRIMARY KEY AUTO_INCREMENT,
    region_name VARCHAR(50) NOT NULL,
    parent_id   INT DEFAULT 0,
    level       TINYINT,  -- 1省 2市 3区
    zip_code    VARCHAR(6)
);

这张表的好处是,你可以直接用它做地址选择器,而且数据是标准化的。我在项目中遇到过,业务方要求统计「华东地区的客户数量」,如果没有标准化的地址字典,你光靠「上海市」「江苏省」这种字符串去匹配,很容易漏掉「上海」「江苏」这种简写。

4.3.2 客户地址表

CREATE TABLE customer_address (
    address_id    BIGINT PRIMARY KEY AUTO_INCREMENT,
    customer_id   BIGINT NOT NULL,
    region_id     INT,           -- 关联到region表
    detail_addr   VARCHAR(200),  -- 详细地址
    is_default    TINYINT DEFAULT 0,  -- 是否默认地址
    addr_type     TINYINT DEFAULT 0,  -- 0收货地址 1发票地址 2办公地址
    created_at    DATETIME
);
重点来了: is_default 字段的实现有个小技巧。你不能简单设成 0 或 1,因为一个客户可能有多个地址。正确的做法是:在应用层保证每个客户只有一个默认地址。我一般会在插入新默认地址时,先把该客户所有地址的 is_default 置为 0,再设置新的。用事务包起来,不会出问题。

另外,addr_type 这个字段很多人会忽略。其实很有用。比如做发票管理时,你直接查 addr_type = 1 就能拿到发票地址,不用再去问客户「您的发票地址是哪个?」。

4.4 表关系总结

最后,我把这几张表的关系梳理一下:

  • Customer 是主表,一条记录代表一个客户
  • Customer 1:N Customer_Address —— 一个客户可以有多个地址
  • Customer N:M Customer_Tag —— 通过关联表实现多对多
  • Customer 1:1 Customer_Category —— 一个客户只能属于一个分类

嗯,这套设计我用了好几年,从单机MySQL到分库分表,基本没大改过。你如果现在要做一个CRM系统,照着这个结构来,至少未来两年不用重构客户模块。

下一章我会讲销售线索管理,那是客户进来的第一道门,设计得好不好直接影响转化率。到时候咱们再细聊。