简介客户管理系统ER图文档面向数据库设计学习者、软件工程专业学生及系统分析人员用于快速理解客户管理业务中的数据实体、属性及其关联关系。文档基于标准ER模型给出客户、订单、产品三类核心实体并定义了客户编号、订单编号、产品价格等关键属性以及客户与订单、订单与产品之间的一对多关系可作为数据库概念设计、软件需求建模和业务流程梳理的参考底稿。资料为单个doc文件压缩包整体仅184KB轻量易用便于直接阅读、标注或打印。目前已有604人学习浏览适合正在做课程设计、毕业设计或准备数据库相关认证的读者参考借鉴。借助ER图可快速提取客户管理系统的核心数据模型框架辅助后续逻辑结构设计与数据库建表。1. 拿到“客户管理系统ER图(1).doc”后先别急着改图你手里只有一份“客户管理系统ER图(1).doc”双击打开满屏是Word自选图形拼出来的矩形和箭头。这就是很多客户管理系统数据库设计的起点图是画给评审看的建表才是真正的战场。反直觉的一点是这份doc最值钱的不是那张图而是图背后没写出来的实体边界——客户和联系人是两张表还是塞在一张里商机要不要独立建模跟进记录留不留历史。客户管理系统ER图要做的事就是把客户、联系人、商机、订单、跟进这些业务对象之间的血缘关系在写第一行建表SQL之前用实体关系模型定死。吃透它你才知道外键往哪放、唯一索引怎么加、哪些值只能进字典表。这篇适合正在搭CRM、以及接手一份来历不明的doc就得开始建库表的开发看。2. 实体怎么拆客户管理系统的六个实体边界ER图第一步不是打开画图工具而是把有哪些实体这件事定下来。客户管理系统看着业务简单实际上实体边界特别容易画混客户和联系人是不是一回事、商机要不要单飞、跟进记录算不算日志——这些问题没定清楚后面的外键和索引全是错的。2.1 先回答六个业务问题再决定画几张图我画ER图前会先列一张业务问题清单让每个实体都能回答至少一个真实问题。回答不上的实体砍掉回答不了的问题就是缺失的实体或字段。对客户管理系统我一般先从六个问题起步。业务问题对应实体关键字段这家客户叫什么、归哪个销售客户主表 customer客户编号、名称、类型、归属销售客户下面有哪几个对接人联系人表 contact姓名、职位、电话、是否主联系人这个客户有几个在谈项目商机表 opportunity商机名称、金额、阶段、预计成交时间销售最近对客户做了什么跟进记录表 follow_up_record跟进方式、内容、下次跟进时间哪些销售能看能改这个客户客户共享表 customer_share客户、被共享人、权限类型客户来源、等级这些值从哪来数据字典表 sys_dict字典编码、字典值、排序每一行从问题倒推字段字段再从问题倒推必要性。比如客户归哪个销售这个问题如果系统允许一个客户由多人协作就不能只放一个owner_user_id得额外拆共享表如果允许客户被回收再分配还得再加一个公海池相关的状态字段。问题清单决定了实体粒度也决定库表结构稳定程度这一步省掉后面建表必返工。2.2 客户和联系人为什么必须拆表最常见的草图错误是把联系人画进客户实体里。客户在建模上是登记主体可以是一家公司也可以是一个自然人而联系人是能打电话找到的具体的人。一家企业客户通常同时有老板、财务、业务对接人三个人如果联系人只是客户实体的几个属性客户表里就只放得下一组phone、wechat第二个联系人出现时数据直接无处可写。联系人应该作为客户下的从实体存在客户与联系人是1:N关系。我在联系人表里加一个is_main_contact字段标识默认联系人用于报表里取数。归属销售owner_user_id放客户表不放联系人表因为客户归属是客户维度的属性换了联系人不应改变归属关系。判断一对属性要不要拆表的经验就一条同一套属性在业务上能否重复出现能重复就拆子表。2.3 商机、跟进记录、字典表三块容易画错的区域商机独立成表是第二道坎。同一个客户一年可能同时推三个项目每个项目金额不同、阶段不同A项目谈崩了B项目还活着。如果把amount、stage直接挂到客户表就只剩最后一组值历史过程全部丢失。客户和商机是1:N商机再对产品做明细就是又一个1:NER图上这两层最好都画出来。跟进记录是典型的弱实体没有客户就没有跟进它的存在完全依赖父实体。但弱不等于可以塞进客户表它是流水是销售过程分析的数据源。最近30天没跟进的高意向客户这类报表全靠follow_up_record的create_time和next_follow_time来筛。还有一类容易被忽略的是字典表客户来源、等级、行业这些值在ER图里不要画成客户属性下的一堆可选值单独画一个sys_dict实体用编码关联。枚举写死在代码里改一次值就要发一次版字典表能让这部分代价降到零。3. 把ER图画出来工具选型与三遍画法实体边界想清楚才轮到画图。很多同学拿到doc后就地修改在Word里拖矩形、拉箭头改到一半发现图比SQL还难维护。这个环节的关键不是画得好看而是画完能不能改、能不能交给下一个人接着维护。3.1 工具选型别把Word自选图形当建模工具打开那份doc你会看到每个矩形都是一个独立的文本框箭头是单独画的线条。想改一个字段名得先按Ctrl逐个点选改完还要担心文字溢出不换行。这就是自选图形做ER图的死穴它没有结构化信息图里的几个方块只是一堆散装图形机器读不懂人也很难维护。工具是否免费能否自动检查适合场景Word自选图形随办公套件附带否给文档配插图不适合建模draw.io免费弱仅网格对齐快速重画、导出方便我首选MySQL Workbench免费有正向/逆向工程直接从库表生成ER图PowerDesigner商业授权强企业级建模团队统一标准我的习惯是拒绝在doc里直接改图。客户要求必须交Word版那就用draw.io画完导出到Word源文件保留成可编辑的建模文件。draw.io免费、无安装负担、导出格式多重画这份客户管理系统的ER图半小时足够。3.2 三遍画法第一遍粗实体、第二遍填属性、第三遍标基数重画不要想着一遍到位我习惯分三遍。第一遍只放实体矩形和连线线不用箭头先把实体数量和关系骨架摆出来。第二遍往矩形里填字段每个字段都要标PK、UK、FK。第三遍专门处理关系在连线上标基数1:1、1:N、N:M并且把外键字段名直接写在关系线旁边不许只画一条线然后全靠猜。客户表 rectangle [PK] id BIGINT UNSIGNED customer_no VARCHAR(32) UK customer_name VARCHAR(128) owner_user_id BIGINT FK - sys_user.id deleted TINYINT create_time DATETIME字段排列约定主键第一行业务唯一键第二行外键用FK - 表.字段注明。这段文本既是画图依据也顺手变成后续建表SQL的草稿。第三遍的基数标注不能偷懒比如customer和contact的关系线上必须写1:N外键在contact.customer_id写清楚之后建表时外键方向不会搞反。3.3 从doc里把图抄出来一张手抄表解决黑匣子收到的doc如果里面全是自选图形没有工具能直接把它转成模型常见做法是手工抄一遍。别嫌原始抄录过程本身就是一次字段梳理。先把原文件另存为docx备份然后双击每个矩形复制文字按下面两张表填。实体名字段名类型主键唯一外键指向必填说明customeridBIGINT是否无是逻辑主键customercustomer_noVARCHAR(32)否是无是客户编号contactcustomer_idBIGINT否否customer.id是所属客户关系表单独抄一份来源实体、目标实体、基数、外键所在实体、关联字段、业务含义。抄完对照doc里的线条逐条核一遍确认没有漏画的关系。如果doc里的ER图已经是一张图片没有可复制的文本直接照着重画更快不用纠结解析。抄录表才是后面的后悔药任何时候字段对不上回来翻表不看图。4. 从ER图到建表SQL翻译规则与核心DDL图画完真正的落地动作是把模型翻译成建表SQL。翻译规则固定、机械但细节里全是经验主键用自增id还是业务编号做主键外键建不建物理约束逻辑删除字段和唯一索引怎么共存每一步都影响后续开发。4.1 四句翻译规则ER图元素SQL对象说明实体一张表表名单数、全小写下划线属性一个字段字段名与表名同规则1:N关系N端表加外键字段外键字段名一般为表名单数_idM:N关系拆中间表中间表放两个外键加业务字段主键我通常用BIGINT UNSIGNED自增id业务编号单独加唯一索引。原因是客户编号customer_no这类字段会随业务流程变化比如编号规则调整、前缀重排用业务字段做主键以后改起来极其痛苦。外键方向按基数决定1:N外键一定放在N端比如联系人的customer_idM:N拆中间表后两个外键都放中间表。生产环境我一般只建索引不建物理外键约束外键关系由应用层保证理由在避坑章节细说。4.2 核心三张表的DDL客户表、联系人表、跟进记录表下面以MySQL语法为例给出客户管理系统最核心的三张表。这套结构可以直接套用也适合当作评审别人设计时的对照基线。-- 客户主表一个客户是一个登记主体公司或个人 CREATE TABLE customer ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 逻辑主键, customer_no VARCHAR(32) NOT NULL COMMENT 客户编号业务唯一标识, customer_name VARCHAR(128) NOT NULL COMMENT 客户名称, customer_type TINYINT NOT NULL DEFAULT 1 COMMENT 1企业客户 2个人客户, owner_user_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 归属销售指向sys_user.id0未分配, source_type VARCHAR(32) DEFAULT NULL COMMENT 来源指向字典表编码如adv/visit/referral, level TINYINT NOT NULL DEFAULT 1 COMMENT 客户等级 1/2/3, phone VARCHAR(32) DEFAULT NULL COMMENT 登记电话仅作总机, remark VARCHAR(512) DEFAULT NULL COMMENT 备注, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 0正常 1已删除, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_customer_no (customer_no), KEY idx_owner_user_id (owner_user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT客户主表;owner_user_id默认0而不是NULL是因为NULL在WHERE条件、JOIN关联里容易产生意外结果用0表示未分配语义更干净。customer_no单独唯一索引业务上保证编号不重复与逻辑主键id解耦。逻辑删除标记deleted默认0所有查询都带deleted0条件这个字段和唯一索引的兼容问题在第5章展开。-- 联系人表客户下的对接人1:N的N端 CREATE TABLE contact ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 逻辑主键, customer_id BIGINT UNSIGNED NOT NULL COMMENT 所属客户FK - customer.id, contact_name VARCHAR(64) NOT NULL COMMENT 联系人姓名, position VARCHAR(64) DEFAULT NULL COMMENT 职位, phone VARCHAR(32) DEFAULT NULL COMMENT 手机号, wechat VARCHAR(64) DEFAULT NULL COMMENT 微信号, is_main_contact TINYINT NOT NULL DEFAULT 0 COMMENT 1主联系人, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_customer_id (customer_id), UNIQUE KEY uk_customer_phone (customer_id, phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT联系人表;联系人表把customer_id放第一索引位因为绝大多数查询是查某客户下的联系人。唯一索引(customer_id, phone)防止同一客户下手机号重复录入但MySQL对唯一索引中的NULL值不做去重因此phone允许为空会造成多个空手机号行业务上要通过程序约束至少填一个联系方式。-- 跟进记录表销售过程流水弱实体 CREATE TABLE follow_up_record ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 逻辑主键, customer_id BIGINT UNSIGNED NOT NULL COMMENT 被跟进的客户, contact_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 本次对接的联系人0未指定, follow_type VARCHAR(32) NOT NULL COMMENT 方式: phone/visit/wechat, content VARCHAR(1000) NOT NULL COMMENT 跟进内容, next_follow_time DATETIME DEFAULT NULL COMMENT 计划下次跟进时间, follow_user_id BIGINT UNSIGNED NOT NULL COMMENT 跟进人, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_customer_id (customer_id), KEY idx_follow_user_id (follow_user_id), KEY idx_next_follow_time (next_follow_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT跟进记录表;这张表不需要update_time因为跟进记录是流水业务上不允许修改历史内容。next_follow_time单独上索引支撑今天该跟进谁这类待办查询。create_time用数据库默认值而不是应用传值避免各服务器时钟不一致造成时间混乱这个习惯后面还会用到。4.3 多对多关系拆表客户协作共享表一个客户多个销售协作一个销售负责多个客户这是典型的M:N。ER图上直接连线会让物理表无从下手常见做法是拆中间表。客户共享场景下中间表不只是两个外键还要带权限类型解决谁能看、谁能改。-- 客户共享表customer和sys_user的多对多拆分子表 CREATE TABLE customer_share ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 逻辑主键, customer_id BIGINT UNSIGNED NOT NULL COMMENT 被共享的客户, user_id BIGINT UNSIGNED NOT NULL COMMENT 被共享人, perm_type TINYINT NOT NULL DEFAULT 1 COMMENT 1只读 2编辑, share_user_id BIGINT UNSIGNED NOT NULL COMMENT 共享发起人, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 共享时间, PRIMARY KEY (id), UNIQUE KEY uk_customer_user (customer_id, user_id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT客户共享表;唯一索引(customer_id, user_id)保证同一客户对同一人只共享一次重复共享直接报错由程序提示已共享。查询客户详情时要展示所有有权限的销售走customer_id索引查询某销售名下能看的客户走user_id索引。M:N拆表后中间表里的业务字段就是这段关系自己的属性比如perm_type千万不要把这类字段放到customer表里否则同一个人不同客户不同权限就完全没法表达。4.4 把字段手抄表批量转成DDL草稿有了第3章的字段抄录表又嫌一张张写DDL慢可以写个小脚本生成草稿。前提是抄录表导出为TSV或CSV列固定为实体、字段名、类型、主键、唯一、外键、必填、默认、说明。如果doc里的字段本身就是Word表格先另存为HTML再用表格工具转CSV比手敲快。import csv import sys def generate(csv_path): 读取字段抄录表输出CREATE TABLE草稿只负责字段和主键 with open(csv_path, encodingutf-8-sig) as f: rows list(csv.DictReader(f)) tables {} for r in rows: tables.setdefault(r[实体].strip(), []).append(r) for table, cols in tables.items(): print(fCREATE TABLE {table} () fields [] for c in cols: nullable NULL if c[必填].strip() 否 else NOT NULL default if c[默认].strip(): default f DEFAULT {c[默认].strip()} comment c[说明].strip().replace(, ) fields.append( f {c[字段名].strip()} {c[类型].strip()} f{nullable}{default} COMMENT {comment} ) pk_cols [c[字段名] for c in cols if c[主键].strip() 是] if pk_cols: fields.append(f PRIMARY KEY ({, .join(pk_cols)})) else: fields.append( -- 警告: 缺少主键) print(,\n.join(fields) \n);\n) if __name__ __main__: generate(sys.argv[1])运行方式python gen_ddl.py fields.csv。脚本只处理字段、必填、默认、主键这几项外键、唯一索引、联合索引全部不生成。这是有意为之脚本输出是草稿用来省去重复打字的体力活设计决策必须人工review后再补索引和外键。真实落地时我往往把唯一索引和外键直接写进Review注释而不是让脚本自动生成因为这种自动化最容易把ER图上标错的基数原样带进数据库。5. 客户管理系统ER图避坑五个高频坑的现象、原因与处理这部分是社区里反复出现的翻车场景每一条我都见过不止一次。现象描述、根因分析、处理方式按顺序写方便你直接对照排查。5.1 联系人字段进客户表一张表想装两套人的数据现象客户表里躺着contact_name、contact_phone、contact_position三列业务方反馈第二个联系人加不进去只能把第一个人的信息覆盖掉。原因建模时把客户和联系人当成同一个对象认为一个客户对应一个人就够了。等客户是家30人的公司时表结构直接卡死。处理拆出contact表客户表只保留总机phone之类的登记电话。已上线数据用INSERT SELECT把客户表里的联系人字段迁移到contact表customer_id回填is_main_contact设为1。迁移后客户表这三列删除后续所有联系人查询走contact表。设计期判断方法在ER图关系线上写清楚1:N别画成属性。5.2 逻辑删除和唯一索引打架删掉的数据堵住新增的路现象客户A被逻辑删除后再录一个同名同编号的客户B插入报错Duplicate entry错误码1062。原因deleted只是把行标记为1行还在表里唯一索引uk_customer_no依然生效。逻辑删除是给查询用的软删除数据库约束可不知道这层业务语义。处理方案一是唯一索引改成(customer_no, deleted)但同一customer_no删除两次会再次冲突方案二是删除时改写业务编号比如把customer_no更新为原值加_del_id后缀这样原编号释放真实数据又保留痕迹。我推荐方案二因为系统里客户编号一旦可释放后续导入、重录都简单。另外deleted字段不要加入所有唯一索引只加在真正需要保留历史编号的业务字段上。5.3 商机被当作客户的一排字段阶段和金额互相覆盖现象客户表出现opportunity_amount、opportunity_stage、expected_close_time三列销售在系统里只能记录当前一个商机历史商机全部丢失管理层看不到转化率。原因设计者把客户当前状态和客户的商机流水混为一谈。客户状态可以冗余在customer表但商机本身必须独立。状态是当前值商机是过程数据性质完全不同。处理拆opportunity表customer_id关联客户amount用DECIMAL(18,2)stage用字典编码或状态机。统计这个季度成交金额哪些商机推进超过60天还没关单全部基于opportunity表。customer表里的owner_user_id仍然保留它表示客户的归属销售和商机表里的负责人不冲突。5.4 在doc里直接改ER图改一个字段牵动整个版式现象想给联系人加一个birthday字段双击文本框、输入文字、回车结果矩形变大整个图错位后面的箭头全歪了。改完保存下次打开还是同一个维护黑洞。原因doc里的ER图是一堆自选图形没有结构约束。它连字段属于哪个实体这个信息都不存在只是文字恰好被放在矩形内部机器完全无法识别。处理这份doc只当存档不再当编辑对象。把实体、字段、关系抄到表格里作为唯一事实来源用draw.io重画画完导出图片或PDF用于汇报。如果需要交付可编辑的Word版draw.io的导出结果可以嵌入docx但源建模文件必须另存绝不能把Word当模型维护。顺便说一句收到(1)这种带括号编号的文件名第一件事是核对版本别拿旧图画新表。5.5 金额用浮点、时间靠手传报表数据翻车是玄学现象多个订单金额SUM出来是99999.9差了0.1同一条记录的create_time在不同服务器查出来差8小时。原因金额字段用了FLOAT或DOUBLE浮点数二进制无法精确表示十进制小数累加误差相乘后会放大。时间字段用了TIMESTAMP或让应用代码传值前者有时区转换问题后者依赖应用服务器时钟。处理金额一律DECIMAL(18,2)涉及折扣、税率再按业务扩大精度。所有时间字段统一DATETIME创建时间用CURRENT_TIMESTAMP默认值不让应用手传。这个习惯要从ER图的字段类型标注开始画图时就把类型写死别到建表才拍脑袋。数据和预期对不上时先查字段类型八成是浮点。6. 验证ER图的三把尺子覆盖度、一致性、可演进性画完图、抄完表、建完草稿先别急着转文档用三把尺子量一遍。尺子不够图看着再整齐也是纸糊的。第一把尺子叫覆盖度。把第2章的业务问题清单重新拿出来逐条问ER图。比如最近30天没跟进的高意向客户有哪些这张图能不能答上来客户有level和owner_user_id跟进记录有create_time和next_follow_time两个实体JOIN一下就能查说明覆盖到位。如果这个问题需要左拼右凑还缺字段那就是图不完整。第二把尺子叫一致性。逐个关系核对基数和外键方向customer和contact之间画了1:N外键就必须在contact表里写着customer_idcustomer和sys_user画了共享的M:N就必须存在中间表。关系线上不写外键字段名的图直接打回重画。第三把尺子叫可演进性。想想下周产品要加客户年营业额字段要动几张表下个月要支持公海池划分是加字段还是加表。改动落到一张表内是正常落到三张表以上说明实体边界画错了。检查项通过标准覆盖度每条业务问题都有实体和字段承接不缺不冗一致性所有1:N外键在N端所有M:N有中间表演进性新增一个客户属性不需要动三张以上的表命名表名、字段名全小写下划线主键统一id我曾为了赶进度跳过第三遍基数标注直接建表结果把contact和customer的外键方向画反上线后客户详情页查不出联系人排查了大半天。后来给自己定了条规矩关系线上不写外键字段名和外键方向的ER图不算画完。你拿到任何一份某某ER图.doc优先做的永远是把它变成字段表和关系表图只是个视图数据模型才是活的东西。希望帮到你。本文还有配套的精品资源点击获取