Aşağıdaki güncelleme
Bir hesap hiyerarşisini temsil etmek için tipik bir acct / üst hesap mimarisine sahip bir hesap tablosu var (SQL Server 2012). Hiyerarşiyi ortaya çıkarmak için CTE kullanarak bir GÖRÜNÜM oluşturdum ve genel olarak güzel ve amaçlandığı gibi çalışıyor. Hiyerarşiyi herhangi bir düzeyde sorgulayabilir ve şubeleri kolayca görebilirim.
Hiyerarşinin bir işlevi olarak döndürülmesi gereken bir iş mantığı alanı vardır. Her hesap kaydındaki bir alan, işletmenin boyutunu açıklar (buna CustomerCount diyeceğiz). Rapor etmem gereken mantığın CustomerCount'u tüm şubeden toplaması gerekiyor. Başka bir deyişle, bir hesap verildiğinde, o hesap için müşteri hesabı değerlerini ve hiyerarşi boyunca hesabın altındaki her daldaki her çocukla birlikte toplamam gerekiyor.
CTT içinde oluşturulmuş, acct4.acct3.acct2.acct1 gibi görünen bir hiyerarşi alanı kullanarak alanı başarıyla hesapladım. Karşılaştığım sorun, hızlı çalışmasını sağlamak. Bu hesaplanan alan olmadan, sorgu ~ 3 saniye içinde çalışır. Hesaplanan alana eklediğimde, 4 dakikalık bir sorguya dönüşür.
İşte doğru sonuçları döndürdüğüm en iyi sürüm. Performanstan bu kadar büyük fedakarlık etmeden AS VIEW'ı nasıl yeniden yapılandırabileceğim konusunda fikirler arıyorum.
Bunun yavaş gitmesinin nedenini anlıyorum (nerede maddesinde bir yüklemin hesaplanmasını gerektirir), ancak onu yapılandırmanın başka bir yolunu düşünemiyorum ve yine de aynı sonuçları elde edemiyorum.
İşte bir tablo oluşturmak ve CTE'yi tam olarak benim ortamımda çalıştığı gibi yapmak için bazı örnek kod.
Use Tempdb
go
CREATE TABLE dbo.Account
(
Acctid varchar(1) NOT NULL
, Name varchar(30) NULL
, ParentId varchar(1) NULL
, CustomerCount int NULL
);
INSERT Account
SELECT 'A','Best Bet',NULL,21 UNION ALL
SELECT 'B','eStore','A',30 UNION ALL
SELECT 'C','Big Bens','B',75 UNION ALL
SELECT 'D','Mr. Jimbo','B',50 UNION ALL
SELECT 'E','Dr. John','C',100 UNION ALL
SELECT 'F','Brick','A',222 UNION ALL
SELECT 'G','Mortar','C',153 ;
With AccountHierarchy AS
( --Root values have no parent
SELECT
Root.AcctId AccountId
, Root.Name AccountName
, Root.ParentId ParentId
, 1 HierarchyLevel
, cast(Root.Acctid as varchar(4000)) IdHierarchy --highest parent reads right to left as in id3.Acctid2.Acctid1
, cast(replace(Root.Name,'.','') as varchar(4000)) NameHierarchy --highest parent reads right to left as in name3.name2.name1 (replace '.' so name parse is easy in last step)
, cast(Root.Acctid as varchar(4000)) HierarchySort --reverse of above, read left to right name1.name2.name3 for sorting on reporting only
, cast(Root.Name as varchar(4000)) HierarchyLabel --use for labels on reporting only, indents names under sorted hierarchy
, Root.CustomerCount CustomerCount
FROM
tempdb.dbo.account Root
WHERE
Root.ParentID is null
UNION ALL
SELECT
Recurse.Acctid AccountId
, Recurse.Name AccountName
, Recurse.ParentId ParentId
, Root.HierarchyLevel + 1 HierarchyLevel --next level in hierarchy
, cast(cast(recurse.Acctid as varchar(40)) + '.' + Root.IdHierarchy as varchar(4000)) IdHierarchy --cast because in real system this is a uniqueidentifier type needs converting
, cast(replace(recurse.Name,'.','') + '.' + Root.NameHierarchy as varchar(4000)) NameHierarchy --replace '.' for parsing in last step, cast to make room for lots of sub levels down the hierarchy
, cast(Root.AccountName + '.' + Recurse.Name as varchar(4000)) HierarchySort
, cast(space(root.HierarchyLevel * 4) + Recurse.Name as varchar(4000)) HierarchyLabel
, Recurse.CustomerCount CustomerCount
FROM
tempdb.dbo.account Recurse INNER JOIN
AccountHierarchy Root on Root.AccountId = Recurse.ParentId
)
SELECT
hier.AccountId
, Hier.AccountName
, hier.ParentId
, hier.HierarchyLevel
, hier.IdHierarchy
, hier.NameHierarchy
, hier.HierarchyLabel
, parsename(hier.IdHierarchy,1) Acct1Id
, parsename(hier.NameHierarchy,1) Acct1Name --This is why we stripped out '.' during recursion
, parsename(hier.IdHierarchy,2) Acct2Id
, parsename(hier.NameHierarchy,2) Acct2Name
, parsename(hier.IdHierarchy,3) Acct3Id
, parsename(hier.NameHierarchy,3) Acct3Name
, parsename(hier.IdHierarchy,4) Acct4Id
, parsename(hier.NameHierarchy,4) Acct4Name
, hier.CustomerCount
/* fantastic up to this point. Next block of code is what causes problem.
Logic of code is "sum of CustomerCount for this location and all branches below in this branch of hierarchy"
In live environment, goes from taking 3 seconds to 4 minutes by adding this one calc */
, (
SELECT
sum(children.CustomerCount)
FROM
AccountHierarchy Children
WHERE
hier.IdHierarchy = right(children.IdHierarchy, (1 /*length of id field*/ * hier.HierarchyLevel) + hier.HierarchyLevel - 1 /*for periods inbetween ids*/)
--"where this location's idhierarchy is within child idhierarchy"
--previously tried a charindex(hier.IdHierarchy,children.IdHierarchy)>0, but that performed even worse
) TotalCustomerCount
FROM
AccountHierarchy hier
ORDER BY
hier.HierarchySort
drop table tempdb.dbo.Account
11/20/2013 GÜNCELLEME
Önerilen çözümlerden bazıları meyve sularımın akmasını sağladı ve ben yaklaşan yeni bir yaklaşım denedim, ancak yeni / farklı bir engel getirdi. Dürüst olmak gerekirse, bunun ayrı bir gönderiyi garanti edip etmediğini bilmiyorum, ancak bu sorunun çözümü ile ilgili.
Karar verdiğim şey, toplamı (customercount) zorlaştıran şeyin, çocukların en üstte başlayan ve biriken bir hiyerarşi bağlamında tanımlanmasıdır. Bu yüzden aşağıdan yukarıya doğru inşa eden bir hiyerarşi oluşturarak, "başka bir hesabın üstü olmayan hesaplar" tarafından tanımlanan kökü kullanarak ve yinelemeli birleştirme işlemini geriye doğru yaparak başladım (root.parentacctid = recurse.acctid)
Bu şekilde, özyineleme gerçekleştikçe çocuk müşteri sayısını üst öğeye ekleyebilirim. Raporlama ve düzeylere nasıl ihtiyacım olduğu için, yukarıdan aşağıya ek olarak bu aşağıdan yukarıya cte yapıyorum, sonra sadece hesap kimliği ile katılıyorum. Bu yaklaşım, orijinal dış sorgu müşteri sayısından çok daha hızlı olduğu ortaya çıkıyor, ancak birkaç engelle karşılaştım.
İlk olarak, birden çok çocuğa ebeveyn olan hesaplar için yanlışlıkla müşteri sayısını yakaladım. Ben bazı acctid's için çift veya üçlü sayma müşteri sayısı vardı, çocuk sayısı vardı. Benim çözümüm, bir oyuncunun kaç tane düğümü olduğunu sayan başka bir cte oluşturmak ve yineleme sırasında acct.customercount'u bölmekti, bu yüzden tüm şubeyi eklediğimde acct çift sayılmıyor.
Bu noktada, bu yeni sürümün sonuçları doğru değil, ama nedenini biliyorum. Botup cte kopyaları oluşturuyor. Özyineleme geçtiğinde, kökte (alt düzey çocuklar) hesap tablosundaki bir hesaba alt öğe olan herhangi bir şeyi arar. Üçüncü yinelemede, ikincisinde yaptığı aynı hesapları alır ve tekrar koyar.
Aşağıdan yukarıya bir ktetonun nasıl yapılacağıyla ilgili fikirler mi yoksa bu akan başka fikirler mi alıyor?
Use Tempdb
go
CREATE TABLE dbo.Account
(
Acctid varchar(1) NOT NULL
, Name varchar(30) NULL
, ParentId varchar(1) NULL
, CustomerCount int NULL
);
INSERT Account
SELECT 'A','Best Bet',NULL,1 UNION ALL
SELECT 'B','eStore','A',2 UNION ALL
SELECT 'C','Big Bens','B',3 UNION ALL
SELECT 'D','Mr. Jimbo','B',4 UNION ALL
SELECT 'E','Dr. John','C',5 UNION ALL
SELECT 'F','Brick','A',6 UNION ALL
SELECT 'G','Mortar','C',7 ;
With AccountHierarchy AS
( --Root values have no parent
SELECT
Root.AcctId AccountId
, Root.Name AccountName
, Root.ParentId ParentId
, 1 HierarchyLevel
, cast(Root.Acctid as varchar(4000)) IdHierarchy --highest parent reads right to left as in id3.Acctid2.Acctid1
, cast(replace(Root.Name,'.','') as varchar(4000)) NameHierarchy --highest parent reads right to left as in name3.name2.name1 (replace '.' so name parse is easy in last step)
, cast(Root.Acctid as varchar(4000)) HierarchySort --reverse of above, read left to right name1.name2.name3 for sorting on reporting only
, cast(Root.Acctid as varchar(4000)) HierarchyMatch
, cast(Root.Name as varchar(4000)) HierarchyLabel --use for labels on reporting only, indents names under sorted hierarchy
, Root.CustomerCount CustomerCount
FROM
tempdb.dbo.account Root
WHERE
Root.ParentID is null
UNION ALL
SELECT
Recurse.Acctid AccountId
, Recurse.Name AccountName
, Recurse.ParentId ParentId
, Root.HierarchyLevel + 1 HierarchyLevel --next level in hierarchy
, cast(cast(recurse.Acctid as varchar(40)) + '.' + Root.IdHierarchy as varchar(4000)) IdHierarchy --cast because in real system this is a uniqueidentifier type needs converting
, cast(replace(recurse.Name,'.','') + '.' + Root.NameHierarchy as varchar(4000)) NameHierarchy --replace '.' for parsing in last step, cast to make room for lots of sub levels down the hierarchy
, cast(Root.AccountName + '.' + Recurse.Name as varchar(4000)) HierarchySort
, CAST(CAST(Root.HierarchyMatch as varchar(40)) + '.'
+ cast(recurse.Acctid as varchar(40)) as varchar(4000)) HierarchyMatch
, cast(space(root.HierarchyLevel * 4) + Recurse.Name as varchar(4000)) HierarchyLabel
, Recurse.CustomerCount CustomerCount
FROM
tempdb.dbo.account Recurse INNER JOIN
AccountHierarchy Root on Root.AccountId = Recurse.ParentId
)
, Nodes as
( --counts how many branches are below for any account that is parent to another
select
node.ParentId Acctid
, cast(count(1) as float) Nodes
from AccountHierarchy node
group by ParentId
)
, BottomUp as
( --creates the hierarchy starting at accounts that are not parent to any other
select
Root.Acctid
, root.ParentId
, cast(isnull(root.customercount,0) as float) CustomerCount
from
tempdb.dbo.Account Root
where
not exists ( select 1 from tempdb.dbo.Account OtherAccts where root.Acctid = OtherAccts.ParentId)
union all
select
Recurse.Acctid
, Recurse.ParentId
, root.CustomerCount + cast ((isnull(recurse.customercount,0) / nodes.nodes) as float) CustomerCount
-- divide the recurse customercount by number of nodes to prevent duplicate customer count on accts that are parent to multiple children, see customercount cte next
from
tempdb.dbo.Account Recurse inner join
BottomUp Root on root.ParentId = recurse.acctid inner join
Nodes on nodes.Acctid = recurse.Acctid
)
, CustomerCount as
(
select
sum(CustomerCount) TotalCustomerCount
, hier.acctid
from
BottomUp hier
group by
hier.Acctid
)
SELECT
hier.AccountId
, Hier.AccountName
, hier.ParentId
, hier.HierarchyLevel
, hier.IdHierarchy
, hier.NameHierarchy
, hier.HierarchyLabel
, hier.hierarchymatch
, parsename(hier.IdHierarchy,1) Acct1Id
, parsename(hier.NameHierarchy,1) Acct1Name --This is why we stripped out '.' during recursion
, parsename(hier.IdHierarchy,2) Acct2Id
, parsename(hier.NameHierarchy,2) Acct2Name
, parsename(hier.IdHierarchy,3) Acct3Id
, parsename(hier.NameHierarchy,3) Acct3Name
, parsename(hier.IdHierarchy,4) Acct4Id
, parsename(hier.NameHierarchy,4) Acct4Name
, hier.CustomerCount
, customercount.TotalCustomerCount
FROM
AccountHierarchy hier inner join
CustomerCount on customercount.acctid = hier.accountid
ORDER BY
hier.HierarchySort
drop table tempdb.dbo.Account