260 likes | 481 Views
Need-for-Speed Performance Tuning. Duncan Klett VP Analytics Research. Outline. Performance guidelines 10.1 query improvements Impact of guidelines Custom data model design. Performance Guidelines (1). Avoid “set” expressions Use Lookup and Join Use the “right” source worksheet(s)
E N D
Need-for-SpeedPerformance Tuning Duncan Klett VP Analytics Research
Outline • Performance guidelines • 10.1 query improvements • Impact of guidelines • Custom data model design
Performance Guidelines (1) • Avoid “set” expressions • Use Lookup and Join • Use the “right” source worksheet(s) • Use common source component sheet if possible • Use single IN for all component sheet filters rather than individual filters • Use “expression-based” fields in data model rather than worksheet queries
Performance Guidelines (2) • Use “summarization” functions for group totals, fractions, etc • Collapse levels in composites • Defer bucketing • Use Reference to tables rather than strings • Avoid string concatenation if possible • Use Variables to identify “focus” items • Customized Data Model: Use “references” rather than strings
Choosing the table to report • Queries on Input data are fast (input tables, input fields) • Use Activity if “driver” and AvailableDate not needed • Use CTPActivity (or CTPPlannedOrder) if AvailableDate is needed but Driver is not needed • Full-level-peg tables are slower, but have driver information • FlatBillUp and FlatBillDown can be slow if multiple time-phased BOMs, substitute BOMs, and/or PartSources
Avoiding FlatBill* tables • 10.1 has new analytic variable:RR_Analytics_FlatBill_MergeIdentical • Reduces time and memory requirements by collapsing identical BOM and PartSource records when calculaing • To get a list of Parts that might be within the BOM (and within the same site), try (see data model guide): • Part In Components.Component OF ($SelectedFilter and Site[Eval('Site \'' + $SelectedSite + '\'')] )
Choosing the table to report • Use WhereConsumed rather than: • WhereConsumedForDemand or LateSupply unless: • reporting data from less than 10 driver parts and • filter is in context of independent demands • WhereConsumedForSupply • unless you need to peg to intermediate supply orders
Example Worksheet (for performance) • Crosstab to show demand, supply and balance, pegged back to selected part(s) by driver • Filter so only components with negative balance shown • Include Description and component make/buy sourcing
Worksheet Structure & Performance Composite Structure:
Original Query SRCWhereConsumedDemand: WhereConsumed[DriverPart[PRIVATE s'IndDmd parts' AND ABCCode='A' AND Name='CRUISER'] AND DriverPart.Site[PUBLIC Site 'All Sites']] {DriverPart=DriverPart.Name :By , DriverSite=DriverPart.Site :By , DriverDescription=DriverPart.Description :By , Level=Part.MultiSiteLowLevelCode :By , Part=Part.Name :By , Site=Part.Site :By , Description=Part.Description :By , MakeBuy=IF( has Part.PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Make'], 'Make ','') + IF( has Part.PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Buy'], 'Buy ','') + IF( has Part.PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Transfer'], 'XFER','') :By , DemandDate=NeedDate :Bucket(CMPBalance!!Buckets!) , Quantity=NeedQuantity }; SRCWhereConsumedSupply … , AvailableDate=SplitSupplyAvailable :Bucket(CMPBalance!!Buckets!) , Quantity=NeedQuantity }; CMPBalance (SRCWhereConsumedDemand! OUTERJOIN SRCWhereConsumedSupply!) {DriverPart=SRCWhereConsumedDemand!DriverPart :By , DriverSite=SRCWhereConsumedDemand!DriverSite :By , DriverDescription=SRCWhereConsumedDemand!DriverDescription :By , Level=SRCWhereConsumedDemand!Level :By , Part=SRCWhereConsumedDemand!Part :By , Site=SRCWhereConsumedDemand!Site :By , Description=SRCWhereConsumedDemand!Description :By , MakeBuy=SRCWhereConsumedDemand!MakeBuy :By , Date=SRCWhereConsumedDemand!DemandDate :Bucket(CMPBalance!!Buckets!) , Demand=SRCWhereConsumedDemand!Quantity , Supply=SRCWhereConsumedSupply!Quantity , Balance=SRCWhereConsumedSupply!Quantity - SRCWhereConsumedDemand!Quantity: Subtotal(Running, Site) }; CMPBalanceFilter (SRCWhereConsumedDemand! OUTERJOIN SRCWhereConsumedSupply!) {DriverPart=SRCWhereConsumedDemand!DriverPart :By , DriverSite=SRCWhereConsumedDemand!DriverSite :By , DriverDescription=SRCWhereConsumedDemand!DriverDescription :By , Level=SRCWhereConsumedDemand!Level :By , Part=SRCWhereConsumedDemand!Part :By , Site=SRCWhereConsumedDemand!Site :By , Description=SRCWhereConsumedDemand!Description :By , Date=SRCWhereConsumedDemand!DemandDate :Bucket(CMPBalance!!Buckets!) , Balance=SRCWhereConsumedSupply!Quantity - SRCWhereConsumedDemand!Quantity: Subtotal(Running, Site) }[Date < d'20120305']; INBalanceFilter (CMPBalanceFilter!)[CMPBalanceFilter!Balance < 0] {ComponentPart=CMPBalanceFilter!Part :By , Site=CMPBalanceFilter!Site :By }; ComponentPlanning (CMPBalance!)[{ CMPBalance!Part, CMPBalance!Site } IN InBalanceFilter!] {DriverPart=CMPBalance!DriverPart :By :m n , DriverSite=CMPBalance!DriverSite :By :m n , DriverDescription=CMPBalance!DriverDescription :By :m n , Level=CMPBalance!Level :By :m n , Part=CMPBalance!Part :By :m n , Site=CMPBalance!Site :By :m n , Description=CMPBalance!Description :By :m n , MakeBuy=CMPBalance!MakeBuy :By :m n , Date=CMPBalance!Date :Bucket(ComponentPlanning!!Buckets!) :m n , Demand=CMPBalance!Demand :m n , Supply=CMPBalance!Supply :m n , Balance=CMPBalance!Supply - CMPBalance!Demand: Subtotal(Running, Site) :m n };
Worksheet Performance Tool Where did the time go? • 2 almost identical queries on WhereConsumed • 7,000+ records (after bucketing) eachIncluding a “set” expression for make/buy • Identical source sheets are RE-USED
Improvements • Merge the WhereConsumed queries into one • Pass Part as a reference • Removed Site, Description, Level fields • These can be calculated in the “top” sheet from the References • Defer bucketing • Use separate IN sheet to identify all POSSIBLE components • Avoid “Set” expression to determine Make, Buy, etc.
Identifying Possible Components • Component sheet based on Part • Use filter expression (rather than filter radio buttons) • Example Filter expression: In Components.Component OF ($SelectedFilter and Site[Eval('Site \'' + $SelectedSite + '\'')] ) • First column reports Self (as a Reference)
Avoiding a “set” operation • (Original) WhereConsumed expression: IF( has Part.PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Make'], 'Make ','') + IF( has Part.PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Buy'], 'Buy ','') + IF( has Part.PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Transfer'], 'XFER','') • Move per-record structure in SRC WhereConsumed to per-part in Part query • Add column to InSelectedComponents worksheet • (Revised) Part expression: IF( has PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Make'], 'Make ','') +IF( has PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Buy'], 'Buy ','') +IF( has PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Transfer'], 'XFER','') • Use Lookup in final worksheet: MakeBuy=Lookup( CMPBalanceNewFilter!Part, InSelectedComponents!, 'NONE', Exact)
SRC sheet column comparison • Revised • DriverPart=DriverPart :Ref :By , • Part=Part :Ref :By , • AvailableDate=SplitSupplyAvailable :By , • DemandDate=NeedDate :By , • Quantity=NeedQuantity Original (Demand, repeated for Supply) • DriverPart=DriverPart.Name :By , • DriverSite=DriverPart.Site :By , • DriverDescription=DriverPart.Description :By , • Level=Part.MultiSiteLowLevelCode :By , • Part=Part.Name :By , Site=Part.Site :By , • Description=Part.Description :By , • MakeBuy=IF( has Part.PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Make'], 'Make ','') + IF( has Part.PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Buy'], 'Buy ','') + IF( has Part.PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Transfer'], 'XFER','') :By , • DemandDate=NeedDate :Bucket(CMPBalance!!Buckets!) , • Quantity=NeedQuantity
(Improved) Worksheet Structure & Performance Composite Structure (10.1) & Query Times: 9.5.1 Query times
Revised Query InSelectedComponents Part[((Components.Component OF $SelectedFilter and Site[Eval('Site \'' + $SelectedSite + '\'')] ))] {PartRef=Self :Ref :By , MakeBuy=IF( has PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Make'], 'Make ','') +IF( has PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Buy'], 'Buy ','') +IF( has PartSources [ LastEffectiveDate > d'mrpdate' and Type.PlannedOrderSupplyType.Source='Transfer'], 'XFER','') :Max }; SRCWhereConsumed WhereConsumed[DriverPart[PRIVATE s'IndDmd parts' AND ABCCode='A' AND Name='CRUISER'] and (Part IN INSelectedComponents!) AND DriverPart.Site[PUBLIC Site 'All Sites']] {DriverPart=DriverPart :Ref :By , Part=Part :Ref :By , AvailableDate=SplitSupplyAvailable :By , DemandDate=NeedDate :By , Quantity=NeedQuantity }; CMPSupply (SRCWhereConsumed!){DriverPart=SRCWhereConsumed!DriverPart :Ref :By , Part=SRCWhereConsumed!Part :Ref :By , AvailableDate=SRCWhereConsumed!AvailableDate :By , Quantity=SRCWhereConsumed!Quantity }; CMPDemand (SRCWhereConsumed!){DriverPart=SRCWhereConsumed!DriverPart :Ref :By , Part=SRCWhereConsumed!Part :Ref :By , DemandDate=SRCWhereConsumed!DemandDate :By , Quantity=SRCWhereConsumed!Quantity }; CMPBalanceNew (CMPSupply! OUTERJOIN CMPDemand!){DriverPart=CMPSupply!DriverPart :Ref :By , Part=CMPSupply!Part :Ref :By , Date=CMPSupply!AvailableDate :Bucket(CMPBalanceNewFilter!!Buckets!) , Supply=CMPSupply!Quantity , Demand=CMPDemand!Quantity , Balance=CMPSupply!Quantity - CMPDemand!Quantity: Subtotal(Running, Part) }; CMPBalanceNewFilter (CMPBalanceNew!){Part=CMPBalanceNew!Part :Ref :By , DriverPart=CMPBalanceNew!DriverPart :Ref :By , MinBalance=CMPBalanceNew!Balance :By: Subtotal(Min, Part) , Date=CMPBalanceNew!Date :Bucket(CMPBalanceNewFilter!!Buckets!) , Supply=CMPBalanceNew!Supply , Demand=CMPBalanceNew!Demand , Balance=CMPBalanceNew!Balance }; ComponentPlanningNew (CMPBalanceNewFilter!)[CMPBalanceNewFilter!MinBalance < 0] {DriverPart=CMPBalanceNewFilter!DriverPart.Name :By :m n , DriverSite=CMPBalanceNewFilter!DriverPart.Site :By :m n , DriverDescription=CMPBalanceNewFilter!DriverPart.Description :By :m n , Level=CMPBalanceNewFilter!Part.MultiSiteLowLevelCode :By :m n , Part=CMPBalanceNewFilter!Part.Name :By :m n , Site=CMPBalanceNewFilter!Part.Site :By :m n , Description=CMPBalanceNewFilter!Part.Description :By :m n , MakeBuy=Lookup( CMPBalanceNewFilter!Part, InSelectedComponents!, 'NONE', Exact) :By :m n , Date=CMPBalanceNewFilter!Date :Bucket(ComponentPlanningNew!!Buckets!) :m n , Demand=CMPBalanceNewFilter!Demand :m n , Supply=CMPBalanceNewFilter!Supply :m n , Balance=CMPBalanceNewFilter!Supply - CMPBalanceNewFilter!Demand: Subtotal(Running, Site) :m n }
Setting/Displaying “Focus Part” • Previously, needed a column expression, such asSelectedPart = IF ( Part = $FocusPart, ‘Y’, ‘’) • Then, use conditional formatting:Part = SelectedPart • Now, simply use Column Part value = $FocusPartin Conditional Formatting • Formatting is applied, no re-running of query is required
Customized data model design • Avoid strings • Preferably only use for “key” fields and descriptions • Put the field on the “right” table • If the value is the same for a set of data, put the new field on a “header” record for the set • Original new fields on IndependentDemand: • (all fields were Strings) • U_CustomerLocation • U_CustomerCountry • U_CustomerRegion • Alternative 1 • IndependentDemand: • Order -> Customer • Customer: • U_Location • U_Location: • U_Country • U_Country • U_Region • Alternative 2 • IndependentDemand: • U_CustomerLocation • U_Location: • U_Country • U_Country • U_Region • Advantages of Alternatives: • Easier maintenance • Allows drop-lists for data entry • Ensures data consistency • Faster, simpler queries • Avoids long chains of nested IF statements
Performance Guidelines • Avoid “set” expressions • Use Lookup and Join • Use the “right” source worksheet(s) • Use common source component sheet if possible • Use single IN for all component sheet filters rather than individual filters • Use “expression-based” fields in data model rather than worksheet queries • Use “summarization” functions for group totals, fractions, etc • Collapse levels in composites • Defer bucketing • Use Reference to tables rather than strings • Avoid string concatenation if possible • Use Variables to identify “focus” items • Customized Data Model: Use “references” rather than strings
ThanksQuestions ?? Duncan Klett