This blog post has also been published in English.

Dieser Beitrag basiert auf dem 1. Teil “DWH-Beladung basierend auf Fremdschlüssel-Topologie mit Biml – Teil 1: Lineare Beladung” und dient rein als Ergänzung zu diesem.

Nachdem sich die erste Begeisterung darüber, wie einfach die Sortierung nach Fremdschlüsseln umsetzen ließ, gelegt hat, stellt sich natürlich die Frage: Wieso läuft das sequentiell? Das dauert doch viel länger. Und das ist korrekt, weswegen wir es nun ändern.

Hierfür benötigen wir:

– Eine Liste aller Tabellen, die bereits geladen wurden (welche Anfangs natürlich leer ist)
– Dann identifizieren wir alle Tabellen, die nicht von anderen Tabellen referenziert werden
– Diese Tabellen laden wir, fügen sie zur Liste der geladenen Tabellen hinzu und schauen danach ob es Tabellen gibt, die wiederum nur auf diese Tabelle verweisen
– Falls ja, laden wir auch diese und fügen sie zur Liste hinzu, was wir anschließend auch für deren “Kinder” wiederholen
– Im nächsten Schritt suchen wir uns alle Tabellen, die mehrere Tabellen referenzieren und laden all jede, bei denen alle referenzierten Tabellen schon geladen wurden
– Diesen Schritt wiederholen wir, bis alle Tabellen geladen sind oder (hier) maximal 10-mal
– Sollten dann noch Tabellen übrig geblieben sein, würden wir diese unter Nutzung von TopoSort letztendlich sequentiell laden:

<#@ template tier="4" language="VB"#> 
<#@ code file="../Code/TopologySort.vb" #>
<#@ import namespace="TopologySort" #>
 <Biml xmlns="http://schemas.varigence.com/biml.xsd">
    <Packages>
        <Package Name="03_Load_Parallel" ConstraintMode="Linear" PackageSubpath="<#= OutputPath #>">
            <Tasks>
                <ExecutePackage Name="Recreate Tables">
                    <ExternalFile ExternalFilePath="<#= OutputPath #>\01_Create.dtsx"/>
                </ExecutePackage>
                <Container Name="Load tables without reference and immediate descendants" ConstraintMode="Parallel">
                    <Tasks>
                        <# for each t as asttablenode in rootnode.tables.where(function(r) r.columns.OfType(Of AstTableColumnTableReferenceNode)().Count+r.columns.OfType(Of AstMultipleColumnTableReferenceNode)().Count = 0) #>
                        <Container Name="Load <#= t.name #>" ConstraintMode="Linear">
                            <Tasks>
                                <#= CallBimlScript("05_Recursion.biml",t,LoadedTables) #>
                            </Tasks>
                        </Container>
                        <# next #>
                    </Tasks>
                </Container>
                <# do while rootnode.tables.where(function(e) not loadedtables.contains(e.name) and not e.columns.OfType(Of AstMultipleColumnTableReferenceNode).Where(function(c) c.foreigntable.name <> e.name and not loadedtables.contains(c.foreigntable.name) ).count > 0 and not e.columns.OfType(Of AstTableColumnTableReferenceNode).Where(function(m) m.foreigntable.name <> e.name and not loadedtables.contains(m.foreigntable.name) ).count > 0).Any and level < 10
				Level += 1
				loadabletables = rootnode.tables.where(function(e) not loadedtables.contains(e.name) and not e.columns.OfType(Of AstMultipleColumnTableReferenceNode).Where(function(c) c.foreigntable.name <> e.name and not loadedtables.contains(c.foreigntable.name) ).count > 0 and not e.columns.OfType(Of AstTableColumnTableReferenceNode).Where(function(m) m.foreigntable.name <> e.name and not loadedtables.contains(m.foreigntable.name) ).count > 0).ToList #>
                <Container Name="Load possible tables - Level <#= Level #>" ConstraintMode="Parallel">
                    <Tasks>
                        <# for each tbl as asttablenode in loadabletables #>
                        <#= CallBimlScript("06_Dataflow.biml",tbl,loadedtables) #>
                        <# next #>
                    </Tasks>
                </Container>
                <# loop 
				 if  loadedtables.count < rootnode.tables.count then #>
                <Container Name="Load leftover tables" ConstraintMode="Linear">
                    <Tasks>
                        <# for each t as asttablenode in rootnode.tables.TopoSort.where(function(r) not loadedtables.contains(r.name)) #>
                        <#= CallBimlScript("06_Dataflow.biml",t,LoadedTables) #>
                        <# next #>
                    </Tasks>
                </Container>
                <# end if #>
            </Tasks>
        </Package>
    </Packages>
</Biml> 

Den ersten Block, welcher die Tabellen ohne Referenzen und dann rekursiv deren eindeutige Kinder lädt, könnten wir auch weglassen. Wenn man diesen entfernt, werden dennoch alle Tabellen geladen, es gäbe nur mehr Durchläufe in der zweiten Schleife. Warum haben wir ihn dennoch eingebaut? Um auf ein anderes Feature hinzuweisen: Innerhalb von CallBimlScript lassen sich per CallBimlScript weitere Biml Dateien aufrufen. Auch mehrmals die gleichen, was dann den gewünschten Rekursionseffekt liefert:

<#@ template language="VB" designerbimlpath="Biml/Packages/Package/Tasks" #>
<#@ property name="tbl" type="AstTableNode" #> 
<#@ property name="LoadedTables" type="List (of String)" #> 
<#= CallBimlScript("06_Dataflow.biml",tbl,LoadedTables) #>
<# for each t as asttablenode in rootnode.tables.where(function(r) r.columns.OfType(Of AstTableColumnTableReferenceNode)().Count+r.columns.OfType(Of AstMultipleColumnTableReferenceNode)().Count  =1).where(function(e) e.columns.OfType(Of AstTableColumnTableReferenceNode)().first.foreigntable.name  = tbl.name )  #>
    <#= CallBimlScript("05_Recursion.biml",t,LoadedTables) #>
<#next #>

Je nach Rechnerausstattung und Datenmodell sollte diese Beladung deutlich schneller ablaufen als die vorherige sequentielle und dabei dennoch die Datenintegrität sicherstellen!

Haben Sie hierzu Fragen oder Anmerkungen? Wir freuen uns auf Ihren Input unter !

Weitere Informationen zu Biml, einschließlich Terminen und Blog Beiträgen finden Sie auch auf unserer Biml Seite.

Viel Spaß beim Biml’n!