在業務方得到一個需求,需要統計每個組織所包含的人數。如下圖所示 ,整個集團上百家公司 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); } }