【系统设计】统计组织架构中,各层级的人数的方案。
在业务方得到一个需求,需要统计每个组织所包含的人数。如下图所示 ,整个集团上百家公司 4000+组织。大致结构如下图所示:
首先,这是一个树形结构,所以要统计必然要用递归的方式。
分析一下表结构,然后用最简单的方法来思考一下,假如这个树从上到下只有一条路径 那么人数如图所示:
OrgId (主键Id) | ParentId (父组织Id) | count 当前组织人数 | sumCount 包含下级组织
人数是通过员工表 对 组织ID进行分组查询搜索出来的结果。
OK 如图所示 就统计出来了,但如果这个组织层级极其复杂的话,就没有这么容易了,比如想要算OrgId = 5 的组织人数,要把parentId = 5的子组织全找出来,然后这些子组织要继续找下级组织,直到扩散到叶子节点(指没有下级的节点)。这样计算效率低的离谱,所以需要优化一下。
我们这里引入一个深度的字段概念!这个东西最好在添加组织的时候就保存下来。然后就计算最大深度(比如10)的所有组织人数,再递归计算9、8、7......深度的组织,直到最顶层。
这样做 统计深度6的所有组织的时候,已经把7的数据都汇总完毕了,直接parentId = 深度7的组织Id,然后把数量汇总就可以了,不用继续递归,因为7已经统计完了 8 9 10深度的所有人数,以此类推
员工表1W+人 , 组织表4000+,查询时间大概是10秒左右。建议统计完所有直接把数据放入缓存中,涉及组织变更、调动、添加员工、入职、离职、都要清除缓存,下次要用的时候再重新计算。
代码如下(C#):
/// <summary> /// 获取各组织数量 /// </summary> public async Task<List<OrgCountCache>> GetOrgCountCache() { return (await _orgCountCache.GetOrAddAsync(CacheKyes.OrgCount, async () => { var orgs = await GetOrganizationCache(); // 计算出有人在其下的各组织节点数据 var hasCount = await _employeeRepository.GetOrgCount(); if (hasCount == null || !hasCount.Any()) { return new List<OrgCountCache>(); } // 所有组织节点数据 var allOrgCount = new List<OrgCountCache>(); foreach(var org in orgs) { var orgCountCache = new OrgCountCache(); orgCountCache.OrgId = org.OrgId; orgCountCache.ParentId = org.ParentId; orgCountCache.OrganizationLevel = (int)org.OrganizationLevel; // 找出4000+个组织中挂了人的组织 var hasCountOrg = hasCount.FirstOrDefault(x => x.OrgId == org.OrgId); if (hasCountOrg != null) { orgCountCache.Count = hasCountOrg.Count; orgCountCache.SumCount = hasCountOrg.Count; } allOrgCount.Add(orgCountCache); } GetOrgCountRecursion(allOrgCount, 10); return allOrgCount; })).Clone(); } /// <summary> /// 递归查询每个组织的数量 先查最深的,然后递归查深度其次的 /// </summary> public void GetOrgCountRecursion(List<OrgCountCache> allOrgCount, int organizationLevel) { if (organizationLevel == 0) // 单独组织,没有父级,没有子集 { foreach (var orgCount in allOrgCount) { if (orgCount.OrganizationLevel != organizationLevel) continue; orgCount.SumCount = orgCount.Count; } } else if (organizationLevel < 0) // 计算完毕,跳出循环 { return; } else { foreach (var orgCount in allOrgCount) { if (orgCount.OrganizationLevel != organizationLevel) continue; // 计算总和SumCount = 本身Count + 所有下一层深度节点的SumCount orgCount.SumCount = orgCount.Count + allOrgCount.Where(x => x.ParentId == orgCount.OrgId).Select(x => x.SumCount).Sum(); } // 递归调用,深度-1 GetOrgCountRecursion(allOrgCount, organizationLevel - 1); } }