Ready for the app
Step-by-step manual
01The business question
The clean tables exist; now the app needs a model it can trust. A star schema with one fact for movements, one for budget against actual, and a dimension per customer, product, branch, salesperson and date, stored as QVDs in the model folder, with only the model left in memory.
02Before you start
- Unzip the episode package:
Holidays.csv, the files of the previous episodes andstart.qep(episode 5's end). - Import
start.qepwith Import QEP. Keep episode 5's debugging checkpoint: this episode switches it off from the toolbar instead of deleting it. - In Qlik, create the folder
Modelinside your QXDistributors folder: the model QVDs go there.
03Step by step
-
1
Check before writing the model
Three nodes:
- A LET/SET:
LET vMovementRows = NoOfRows('Movement'). - An IF on
$(vMovementRows) = 0: THEN writes a TRACE and exits the script; ELSE writes the row count. - A SWITCH on
'$(vEnvironment)': one TRACE forPROD, one forQAand a default.

Figure 6.1 — Check before writing the model - A LET/SET:
-
2
Build the keys
A TXF
tmp_FactSales(NoConcatenate) readsMovement. With the Key Builder:MovementDateas%SK_DateCustomerIDas%SK_CustomerProductIDas%SK_ProductBranchIDas%SK_BranchSalespersonIDas%SK_SalespersonAutoNumber(BranchID & '|' & Num(Month(MovementDate)))as%CK_BranchMonth

Figure 6.2 — Build the keys -
3
A master calendar
The Master Calendar Wizard builds it over
Movement.MovementDate:- The date range into
vMinDateandvMaxDate. tmp_CalendarBasewithAUTOGENERATE … WHILE.Dim_Calendarwith%SK_Date, Year, MonthNum, Month, Quarter, Week, Weekday andIsWeekend.IsHolidayandHolidayNamefrommap_Holiday.

Figure 6.3 — A master calendar - The date range into
-
4
The business layer, stored as QVDs
TXF nodes for the model, each with a CHECKPOINT storing it into
$(vPathModel):Fact_SalesFact_BudgetVsActual: before it is stored, a RENAME node, fieldBudgetAmounttoBudgetAmountCOPDim_Customer,Dim_Product,Dim_Branch,Dim_SalespersonDim_Calendar

Figure 6.4 — The business layer, stored as QVDs -
5
Let Qlik derive the signup calendar
An AutoCalendar on
SignupDate: year, month and year-quarter, derived by Qlik withDECLARE FIELD DEFINITIONandDERIVE FIELDS.
Figure 6.5 — Let Qlik derive the signup calendar -
6
Only the model stays
A DROP of every transform table:
Customer, Product, Salesperson, BranchSales, SaleReturn, Movement, BudgetVsActualtmp_FactSales, tmp_CalendarBase
A last TRACE says the reload finished.

Figure 6.6 — Only the model stays -
7
Switch the debugging checkpoint off
On episode 5's checkpoint, tick Marked for Toolbar Disable/Enable. Then use the toolbar's checkpoint switch: every marked checkpoint turns off at once, and its
STOREandEXIT SCRIPTstay in the script as comments, ready to switch back on.
Figure 6.7 — Switch the debugging checkpoint off -
8
Generate, export, deploy
Generate Syntax:
_Mainsets the formats,vTxfPath,vPathRaw, the project variables (HidePrefixincluded) and includes every tab. Export saves the script, its documentation or its JSON; Deploy writes every tab into a folder. Episode 7 imports this whole model back from its script.
Figure 6.8 — Generate, export, deploy
04Check your result
- Run the script in Qlik: it runs to the end (the debugging checkpoint is commented out) and writes seven QVDs into
Model/. - In memory only the model:
Fact_Sales319 rows,Fact_BudgetVsActual24,Dim_Customer57,Dim_Product37,Dim_Branch8,Dim_Salesperson12,Dim_Calendar90 (January 1 to March 31, three holidays). The fields starting with%are hidden from the app's lists. - Q-Expert splits the script into
_Main, the only part you paste into Qlik, and one.qvsfile per tab that_Mainreads withMUST_INCLUDE. All of them should match the ones below, except the date in the header and a// Q-Expert · Ref …line with your own reference.
_Main
Paste into QlikThis is the only script you paste into Qlik Sense: open the app's Data load editor, replace its content with this and reload. Each $(MUST_INCLUDE=…) line reads one of the files below in its place, so the rest of the model never goes into the editor.
Show script · 49 lines
/*---------------------------------------------------------------------------------------- Description [MAIN]: ------------------------------------------------------------------------------------- Author: The Q-Fábrica team - Date: [2026-11-02] ------------------------------------------------------------------------------------- Changes ------------------------------------------------------------------------------------- Author Date Detail ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- Copyright © Q-Expert Distributors. Q-Expert Qlik Developer Framework. www.qfabrica.com ----------------------------------------------------------------------------------------*/ SET ThousandSep=','; SET DecimalSep='.'; SET MoneyThousandSep=','; SET MoneyDecimalSep='.'; SET MoneyFormat='$ #,##0.00;-$ #,##0.00'; SET TimeFormat='hh:mm:ss'; SET DateFormat='YYYY-MM-DD'; SET TimestampFormat='YYYY-MM-DD hh:mm:ss'; SET MonthNames='Jan;Feb;Mar;Apr;May;Jun;Jul;Aug;Sep;Oct;Nov;Dec'; SET LongMonthNames='January;February;March;April;May;June;July;August;September;October;November;December'; SET DayNames='Mon;Tue;Wed;Thu;Fri;Sat;Sun'; SET LongDayNames='Monday;Tuesday;Wednesday;Thursday;Friday;Saturday;Sunday'; SET FirstWeekDay=0; SET BrokenWeeks=1; SET ReferenceDay=4; SET FirstMonthOfYear=1; LET vExecutionDate = Timestamp(Now(), 'YYYY-MM-DD hh:mm:ss'); SET vTxfPath = 'lib://QXDistributors/Transform/'; SET vPathRaw = 'lib://QXDistributors/'; SET vEnvironment = 'PROD'; // PROD, QA or DEV SET vBaseCurrency = 'COP'; // Currency every amount is converted to SET vPathModel = 'lib://QXDistributors/Model/'; // The model QVDs the apps load SET HidePrefix = '%'; // Keys (%) stay out of the app lists $(MUST_INCLUDE=$(vTxfPath)_QVS/Flow_1.qvs); $(MUST_INCLUDE=$(vTxfPath)_QVS/Flow_2.qvs); $(MUST_INCLUDE=$(vTxfPath)_QVS/Flow_3.qvs); $(MUST_INCLUDE=$(vTxfPath)_QVS/Flow_4.qvs); $(MUST_INCLUDE=$(vTxfPath)_QVS/Movement.qvs); $(MUST_INCLUDE=$(vTxfPath)_QVS/Flow_6.qvs);
Flow_1.qvs
File · read by MUST_INCLUDENot pasted: saved as a file. On the Flow_1 tab of Generate Syntax, Export downloads Flow_1.qvs (Deploy writes every tab into a folder at once). Put it in lib://QXDistributors/Transform/_QVS/Flow_1.qvs, the Transform/_QVS folder inside your QXDistributors folder. If the file is not there, the reload stops with an error: that is what MUST_INCLUDE is for.
Show script · 35 lines
/*---------------------------------------------------------------------------------------- Description [Flow_1]: ------------------------------------------------------------------------------------- Author: The Q-Fábrica team - Date: [2026-11-02] ------------------------------------------------------------------------------------- Changes ------------------------------------------------------------------------------------- Author Date Detail ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- Copyright © Q-Expert Distributors. Q-Expert Qlik Developer Framework. www.qfabrica.com ----------------------------------------------------------------------------------------*/ // ------------------------------------------------------------------------------------------------- // Table defined directly in the script (2 columns). [map_DocType_Src]: LOAD * INLINE [ "DocType", "DocTypeName" "NIT", "Company" "CC", "Person" ]; // ------------------------------------------------------------------------------------------------- // Mapping table (DocType → DocTypeName) from map_DocType_Src, for ApplyMap. [map_DocType]: MAPPING LOAD DocType, DocTypeName RESIDENT [map_DocType_Src]; DROP TABLE [map_DocType_Src];
Flow_2.qvs
File · read by MUST_INCLUDENot pasted: saved as a file. On the Flow_2 tab of Generate Syntax, Export downloads Flow_2.qvs (Deploy writes every tab into a folder at once). Put it in lib://QXDistributors/Transform/_QVS/Flow_2.qvs, the Transform/_QVS folder inside your QXDistributors folder. If the file is not there, the reload stops with an error: that is what MUST_INCLUDE is for.
Show script · 36 lines
/*---------------------------------------------------------------------------------------- Description [Flow_2]: ------------------------------------------------------------------------------------- Author: The Q-Fábrica team - Date: [2026-11-02] ------------------------------------------------------------------------------------- Changes ------------------------------------------------------------------------------------- Author Date Detail ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- Copyright © Q-Expert Distributors. Q-Expert Qlik Developer Framework. www.qfabrica.com ----------------------------------------------------------------------------------------*/ // ------------------------------------------------------------------------------------------------- // Table defined directly in the script (2 columns). [map_CustomerStatus_Src]: LOAD * INLINE [ "Status", "CustomerStatus" "A", "Active" "I", "Inactive" "B", "Blocked" ]; // ------------------------------------------------------------------------------------------------- // Mapping table (Status → CustomerStatus) from map_CustomerStatus_Src, for ApplyMap. [map_CustomerStatus]: MAPPING LOAD Status, CustomerStatus RESIDENT [map_CustomerStatus_Src]; DROP TABLE [map_CustomerStatus_Src];
Flow_3.qvs
File · read by MUST_INCLUDENot pasted: saved as a file. On the Flow_3 tab of Generate Syntax, Export downloads Flow_3.qvs (Deploy writes every tab into a folder at once). Put it in lib://QXDistributors/Transform/_QVS/Flow_3.qvs, the Transform/_QVS folder inside your QXDistributors folder. If the file is not there, the reload stops with an error: that is what MUST_INCLUDE is for.
Show script · 36 lines
/*---------------------------------------------------------------------------------------- Description [Flow_3]: ------------------------------------------------------------------------------------- Author: The Q-Fábrica team - Date: [2026-11-02] ------------------------------------------------------------------------------------- Changes ------------------------------------------------------------------------------------- Author Date Detail ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- Copyright © Q-Expert Distributors. Q-Expert Qlik Developer Framework. www.qfabrica.com ----------------------------------------------------------------------------------------*/ // ------------------------------------------------------------------------------------------------- // Table defined directly in the script (2 columns). [map_ChannelName_Src]: LOAD * INLINE [ "Channel", "ChannelName" "P", "Phone" "T", "In store" "W", "Web" ]; // ------------------------------------------------------------------------------------------------- // Mapping table (Channel → ChannelName) from map_ChannelName_Src, for ApplyMap. [map_ChannelName]: MAPPING LOAD Channel, ChannelName RESIDENT [map_ChannelName_Src]; DROP TABLE [map_ChannelName_Src];
Flow_4.qvs
File · read by MUST_INCLUDENot pasted: saved as a file. On the Flow_4 tab of Generate Syntax, Export downloads Flow_4.qvs (Deploy writes every tab into a folder at once). Put it in lib://QXDistributors/Transform/_QVS/Flow_4.qvs, the Transform/_QVS folder inside your QXDistributors folder. If the file is not there, the reload stops with an error: that is what MUST_INCLUDE is for.
Show script · 24 lines
/*---------------------------------------------------------------------------------------- Description [Flow_4]: ------------------------------------------------------------------------------------- Author: The Q-Fábrica team - Date: [2026-11-02] ------------------------------------------------------------------------------------- Changes ------------------------------------------------------------------------------------- Author Date Detail ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- Copyright © Q-Expert Distributors. Q-Expert Qlik Developer Framework. www.qfabrica.com ----------------------------------------------------------------------------------------*/ // ------------------------------------------------------------------------------------------------- // Mapping table (BrandID → BrandName) from $(vPathRaw)Brands.txt, for ApplyMap. [map_BrandName]: MAPPING LOAD BrandID, BrandName FROM [$(vPathRaw)Brands.txt] (txt, utf8, delimiter is '|', embedded labels, msq);
Movement.qvs
File · read by MUST_INCLUDENot pasted: saved as a file. On the Movement tab of Generate Syntax, Export downloads Movement.qvs (Deploy writes every tab into a folder at once). Put it in lib://QXDistributors/Transform/_QVS/Movement.qvs, the Transform/_QVS folder inside your QXDistributors folder. If the file is not there, the reload stops with an error: that is what MUST_INCLUDE is for.
Show script · 735 lines
/*---------------------------------------------------------------------------------------- Description [Movement]: ------------------------------------------------------------------------------------- Author: The Q-Fábrica team - Date: [2026-11-02] ------------------------------------------------------------------------------------- Changes ------------------------------------------------------------------------------------- Author Date Detail ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- Copyright © Q-Expert Distributors. Q-Expert Qlik Developer Framework. www.qfabrica.com ----------------------------------------------------------------------------------------*/ // ------------------------------------------------------------------------------------------------- // Empty table with 1 column (WHERE 1=0), schema only. [tmp_SalesRaw]: LOAD * INLINE [ "Dummy" ] WHERE 1=0; // ------------------------------------------------------------------------------------------------- // Loop FOR EACH … IN FileList() over $(vFile). FOR EACH vFile IN FileList('$(vPathRaw)Sales_*.csv') // ------------------------------------------------------------------------------------------------- // Raw load from $(vFile), concatenated into [tmp_SalesRaw]. CONCATENATE ([tmp_SalesRaw]) LOAD SaleID, SaleDate, CustomerID, ProductID, SalespersonID, BranchID, Quantity, UnitPrice, DiscountPct, Currency, Channel, FileName() AS SourceFile FROM [$(vFile)] (txt, utf8, delimiter is ',', embedded labels, msq); NEXT vFile; DROP FIELD Dummy FROM [tmp_SalesRaw]; LET vSalesRawRows = NoOfRows('tmp_SalesRaw'); TRACE Sales rows read from the monthly files: $(vSalesRawRows); // ------------------------------------------------------------------------------------------------- // Raw load of tmp_Parameter (no source set yet). [tmp_Parameter]: LOAD Parameter, Value FROM [$(vPathRaw)Parameters.txt] (txt, utf8, delimiter is ' ', embedded labels, msq); LET vCreditThreshold = Lookup('Value', 'Parameter', 'CreditThreshold', 'tmp_Parameter'); DROP TABLE [tmp_Parameter]; // ------------------------------------------------------------------------------------------------- // Mapping table (Num(Date#(HolidayDate, 'YYYY-MM-DD')) AS HolidayDate → HolidayName) from $(vPathRaw)Holidays.csv, for ApplyMap. [map_Holiday]: MAPPING LOAD DISTINCT Num(Date#(HolidayDate, 'YYYY-MM-DD')) AS HolidayDate, HolidayName FROM [$(vPathRaw)Holidays.csv] (txt, utf8, delimiter is ',', embedded labels, msq); QUALIFY *; UNQUALIFY CustomerID, ProductID, SalespersonID, BranchID, CategoryID, BrandID, DocNumber; // ------------------------------------------------------------------------------------------------- // Raw load of tmp_CustomerRaw (no source set yet). [tmp_CustomerRaw]: LOAD CustomerID, CustomerName, DocType, DocNumber, City, Segment, SignupDate, Status, CreditLimit FROM [$(vPathRaw)Customers.csv] (txt, utf8, delimiter is ',', embedded labels, msq); // ------------------------------------------------------------------------------------------------- // Raw load of tmp_ProductRaw (no source set yet). [tmp_ProductRaw]: LOAD ProductID, SKU, ProductName, CategoryID, BrandID, ListPrice, UnitCost, UnitOfMeasure, Status FROM [$(vPathRaw)Products.csv] (txt, utf8, delimiter is ',', embedded labels, msq); // ------------------------------------------------------------------------------------------------- // Raw load of tmp_SalespersonRaw (no source set yet). [tmp_SalespersonRaw]: LOAD SalespersonID, SalespersonName, BranchID, HireDate, MonthlyTarget FROM [$(vPathRaw)Salespeople.csv] (txt, utf8, delimiter is ',', embedded labels, msq); // ------------------------------------------------------------------------------------------------- // Raw load of tmp_BranchRaw (no source set yet). [tmp_BranchRaw]: LOAD BranchID, BranchName, Region, City, Format FROM [$(vPathRaw)Branches.csv] (txt, utf8, delimiter is ',', embedded labels, msq); // ------------------------------------------------------------------------------------------------- // Raw load of tmp_CategoryRaw (no source set yet). [tmp_CategoryRaw]: LOAD CategoryID, CategoryName, Family FROM [$(vPathRaw)Categories.csv] (txt, utf8, delimiter is ',', embedded labels, msq); UNQUALIFY *; // ------------------------------------------------------------------------------------------------- // Raw load of tmp_BrandRaw (no source set yet). [tmp_BrandRaw]: LOAD BrandID, BrandName, CountryOfOrigin FROM [$(vPathRaw)Brands.txt] (txt, utf8, delimiter is '|', embedded labels, msq); // ------------------------------------------------------------------------------------------------- // Transforms tmp_CustomerRaw. [tmp_CustomerClean]: LOAD CustomerID, DocNumber, [tmp_CustomerRaw.Segment] AS Segment, [tmp_CustomerRaw.CreditLimit] AS CreditLimit, Capitalize(Trim(tmp_CustomerRaw.CustomerName)) AS CustomerName, ApplyMap('map_DocType', tmp_CustomerRaw.DocType, 'Other') AS DocTypeName, If(Len(Trim(tmp_CustomerRaw.City)) = 0, 'No city', Trim(tmp_CustomerRaw.City)) AS CustomerCity, Date(Date#(tmp_CustomerRaw.SignupDate, 'DD/MM/YYYY')) AS SignupDate, ApplyMap('map_CustomerStatus', tmp_CustomerRaw.Status, 'Unknown') AS CustomerStatus, If(tmp_CustomerRaw.CreditLimit >= $(vCreditThreshold), 'High', 'Standard') AS CreditProfile, If(DocNumber = Previous(DocNumber), Peek('SurvivorID'), CustomerID) AS SurvivorID RESIDENT [tmp_CustomerRaw] ORDER BY DocNumber, CustomerID; // ------------------------------------------------------------------------------------------------- // Mapping table (CustomerID → SurvivorID) from tmp_CustomerClean, for ApplyMap. [map_CustomerSurvivor]: MAPPING LOAD CustomerID, SurvivorID RESIDENT [tmp_CustomerClean]; // ------------------------------------------------------------------------------------------------- // Transforms tmp_CustomerClean, combined via CONCATENATE. [Customer]: NOCONCATENATE LOAD CustomerID, DocNumber, Segment, CreditLimit, CustomerName, DocTypeName, CustomerCity, SignupDate, CustomerStatus, CreditProfile RESIDENT [tmp_CustomerClean] WHERE CustomerID = SurvivorID; CONCATENATE ([Customer]) // ------------------------------------------------------------------------------------------------- // Table defined directly in the script (3 columns). LOAD * INLINE [ "CustomerID", "CustomerName", "CustomerStatus" "-1", "Unknown customer", "Unknown" ]; DROP TABLE [tmp_CustomerClean]; // ------------------------------------------------------------------------------------------------- // Transforms tmp_ProductRaw, combined via LEFT JOIN. [Product]: LOAD ProductID, [tmp_ProductRaw.SKU] AS SKU, CategoryID, BrandID, [tmp_ProductRaw.ListPrice] AS ListPrice, [tmp_ProductRaw.UnitCost] AS UnitCost, [tmp_ProductRaw.UnitOfMeasure] AS UnitOfMeasure, Capitalize(Trim(tmp_ProductRaw.ProductName)) AS ProductName, ApplyMap('map_BrandName', BrandID, 'No brand') AS BrandName, Round( (tmp_ProductRaw.ListPrice - tmp_ProductRaw.UnitCost) / tmp_ProductRaw.ListPrice, 0.0001 ) AS MarginPct RESIDENT [tmp_ProductRaw] WHERE tmp_ProductRaw.Status = 'ACTIVE'; LEFT JOIN ([Product]) // ------------------------------------------------------------------------------------------------- // Transforms tmp_CategoryRaw. LOAD DISTINCT CategoryID, [tmp_CategoryRaw.CategoryName] AS CategoryName, [tmp_CategoryRaw.Family] AS Family RESIDENT [tmp_CategoryRaw]; LEFT JOIN ([Product]) // ------------------------------------------------------------------------------------------------- // Transforms tmp_BrandRaw. LOAD BrandID, CountryOfOrigin RESIDENT [tmp_BrandRaw]; // ------------------------------------------------------------------------------------------------- // Transforms tmp_SalespersonRaw, combined via LEFT JOIN. [Salesperson]: LEFT KEEP ([tmp_SalesRaw]) LOAD SalespersonID, BranchID AS HomeBranchID, [tmp_SalespersonRaw.MonthlyTarget] AS MonthlyTarget, Capitalize(Trim(tmp_SalespersonRaw.SalespersonName)) AS SalespersonName, Date(Date#(tmp_SalespersonRaw.HireDate, 'YYYY-MM-DD')) AS HireDate RESIDENT [tmp_SalespersonRaw]; // ------------------------------------------------------------------------------------------------- // Transforms tmp_BranchRaw. [Branch]: LOAD BranchID, [tmp_BranchRaw.BranchName] AS BranchName, [tmp_BranchRaw.Region] AS Region, [tmp_BranchRaw.City] AS BranchCity, [tmp_BranchRaw.Format] AS BranchFormat RESIDENT [tmp_BranchRaw]; DROP TABLE [tmp_BranchRaw]; DROP TABLE [tmp_BrandRaw]; DROP TABLE [tmp_CategoryRaw]; DROP TABLE [tmp_CustomerRaw]; DROP TABLE [tmp_ProductRaw]; DROP TABLE [tmp_SalespersonRaw]; // ------------------------------------------------------------------------------------------------- // Raw load of tmp_ExchangeRate (no source set yet). [tmp_ExchangeRate]: LOAD Currency, Rate, Date#(ValidFromDate, 'YYYY-MM-DD') AS ValidFromDate, Date#(ValidToDate, 'YYYY-MM-DD') AS ValidToDate FROM [$(vPathRaw)ExchangeRates.csv] (txt, utf8, delimiter is ',', embedded labels, msq); // ------------------------------------------------------------------------------------------------- // Associates SaleDate with the range [ValidFromDate, ValidToDate] from tmp_ExchangeRate. [tmp_RateBridge]: INTERVALMATCH (SaleDate, Currency) LOAD ValidFromDate, ValidToDate, Currency RESIDENT [tmp_ExchangeRate]; // ------------------------------------------------------------------------------------------------- // Transforms tmp_RateBridge, combined via LEFT JOIN. [tmp_SaleRate]: NOCONCATENATE LOAD SaleDate, ValidFromDate, ValidToDate, Currency RESIDENT [tmp_RateBridge]; LEFT JOIN ([tmp_SaleRate]) // ------------------------------------------------------------------------------------------------- // Transforms tmp_ExchangeRate. LOAD Currency, Rate, ValidFromDate, ValidToDate RESIDENT [tmp_ExchangeRate]; // ------------------------------------------------------------------------------------------------- // Transforms tmp_SalesRaw, combined via INNER JOIN. [tmp_Sales]: LOAD SaleID, ProductID, SalespersonID, BranchID, Quantity, UnitPrice, DiscountPct, Currency, SourceFile, Date(Date#(SaleDate, 'YYYY-MM-DD')) AS SaleDate, ApplyMap('map_CustomerSurvivor', CustomerID, -1) AS CustomerID, ApplyMap('map_ChannelName', Channel, 'Other') AS ChannelName, Quantity * UnitPrice * (1 - DiscountPct / 100) AS NetAmount RESIDENT [tmp_SalesRaw] WHERE Quantity > 0; INNER JOIN ([tmp_Sales]) // ------------------------------------------------------------------------------------------------- // Transforms Product. LOAD ProductID RESIDENT [Product]; LEFT JOIN ([tmp_Sales]) // ------------------------------------------------------------------------------------------------- // Transforms tmp_SaleRate. LOAD SaleDate, Currency, Rate RESIDENT [tmp_SaleRate] WHERE Currency <> '$(vBaseCurrency)'; DROP TABLE [tmp_SalesRaw]; DROP TABLE [tmp_ExchangeRate]; DROP TABLE [tmp_RateBridge]; DROP TABLE [tmp_SaleRate]; // ------------------------------------------------------------------------------------------------- // Raw load of tmp_AmountBand (no source set yet). [tmp_AmountBand]: LOAD FromAmount, ToAmount, Band AS AmountBand FROM [$(vPathRaw)AmountBands.csv] (txt, utf8, delimiter is ',', embedded labels, msq); // ------------------------------------------------------------------------------------------------- // Associates NetAmountCOP with the range [FromAmount, ToAmount] from tmp_AmountBand. [tmp_BandBridge]: INTERVALMATCH (NetAmountCOP) LOAD FromAmount, ToAmount RESIDENT [tmp_AmountBand]; // ------------------------------------------------------------------------------------------------- // Transforms tmp_BandBridge, combined via LEFT JOIN. [tmp_SaleBand]: NOCONCATENATE LOAD NetAmountCOP, FromAmount, ToAmount RESIDENT [tmp_BandBridge]; LEFT JOIN ([tmp_SaleBand]) // ------------------------------------------------------------------------------------------------- // Transforms tmp_AmountBand. LOAD FromAmount, ToAmount, AmountBand RESIDENT [tmp_AmountBand]; // ------------------------------------------------------------------------------------------------- // Transforms tmp_Sales, combined via LEFT JOIN. [Sales]: NOCONCATENATE LOAD *, Round(NetAmount * Alt(Rate, 1)) AS NetAmountCOP RESIDENT [tmp_Sales]; LEFT JOIN ([Sales]) // ------------------------------------------------------------------------------------------------- // Transforms tmp_SaleBand. LOAD NetAmountCOP, AmountBand RESIDENT [tmp_SaleBand]; DROP TABLE [tmp_Sales]; DROP TABLE [tmp_AmountBand]; DROP TABLE [tmp_BandBridge]; DROP TABLE [tmp_SaleBand]; DROP FIELD SourceFile FROM [Sales]; STORE [Sales] INTO [$(vTxfPath)Sales.qvd] (qvd); // ------------------------------------------------------------------------------------------------- // Transforms SaleReturn_PrecedingLoad, combined via INNER JOIN. [SaleReturn]: LOAD ReturnID, SaleID, Reason, ReturnedQty, ReturnDate, If(Match(Reason, 'DAMAGED', 'EXPIRED'), 'Quality', 'Commercial') AS ReasonType ; // ------------------------------------------------------------------------------------------------- // Raw load of SaleReturn_PrecedingLoad (no source set yet). LOAD ReturnID, SaleID, Reason, Quantity AS ReturnedQty, Date(Date#(ReturnDate, 'YYYY-MM-DD')) AS ReturnDate FROM [$(vPathRaw)Returns.csv] (txt, utf8, delimiter is ',', embedded labels, msq); INNER JOIN ([SaleReturn]) // ------------------------------------------------------------------------------------------------- // Transforms Sales. LOAD SaleID, CustomerID, ProductID, SalespersonID, BranchID, Currency, ChannelName, NetAmountCOP / Quantity AS UnitNetCOP RESIDENT [Sales]; // ------------------------------------------------------------------------------------------------- // Transforms Sales, combined via CONCATENATE. [Movement]: NOCONCATENATE LOAD SaleID, SaleDate AS MovementDate, CustomerID, ProductID, SalespersonID, BranchID, Quantity, Currency, ChannelName, NetAmountCOP, AmountBand, 'Sale' AS MovementType RESIDENT [Sales]; CONCATENATE ([Movement]) // ------------------------------------------------------------------------------------------------- // Transforms SaleReturn. LOAD SaleID, ReturnDate AS MovementDate, CustomerID, ProductID, SalespersonID, BranchID, Currency, ChannelName, ReasonType, 'Return' AS MovementType, -ReturnedQty AS Quantity, Round(-ReturnedQty * UnitNetCOP) AS NetAmountCOP RESIDENT [SaleReturn]; // ------------------------------------------------------------------------------------------------- // Raw load of Budget (no source set yet). [Budget]: LOAD BranchID, Jan, Feb, Mar FROM [$(vPathRaw)Budget.csv] (txt, utf8, delimiter is ',', embedded labels, msq); STORE [Movement] INTO [$(vTxfPath)Movement.qvd] (qvd); // ------------------------------------------------------------------------------------------------- // Unpivots columns into [BudgetMonthName] / [BudgetAmount] (CROSSTABLE). [tmp_Budget]: CROSSTABLE('BudgetMonthName', 'BudgetAmount', 1) LOAD BranchID, Jan, Feb, Mar RESIDENT [Budget]; DROP TABLE [Budget]; // ------------------------------------------------------------------------------------------------- // Transforms tmp_Budget, combined via OUTER JOIN. [BudgetVsActual]: LOAD BranchID, BudgetAmount, Match(BudgetMonthName, 'Jan', 'Feb', 'Mar') AS MonthNum RESIDENT [tmp_Budget]; OUTER JOIN ([BudgetVsActual]) // ------------------------------------------------------------------------------------------------- // Transforms Movement. LOAD BranchID, Num(Month(MovementDate)) AS MonthNum, Sum(NetAmountCOP) AS ActualAmountCOP RESIDENT [Movement] GROUP BY BranchID, Num(Month(MovementDate)); DROP TABLE [tmp_Budget]; LET vMovementRows = NoOfRows('Movement'); // ------------------------------------------------------------------------------------------------- // IF $(vMovementRows) = 0, with ELSE. IF $(vMovementRows) = 0 THEN TRACE ERROR: no sales reached the model, the reload stops here; EXIT SCRIPT; ELSE TRACE Movements ready for the model: $(vMovementRows); ENDIF // ------------------------------------------------------------------------------------------------- // Transforms Movement. [tmp_FactSales]: NOCONCATENATE LOAD SaleID, MovementDate AS [%SK_Date], CustomerID AS [%SK_Customer], ProductID AS [%SK_Product], SalespersonID AS [%SK_Salesperson], BranchID AS [%SK_Branch], Quantity, Currency, ChannelName, NetAmountCOP, AmountBand, ReasonType, MovementType, AutoNumber(BranchID & '|' & Num(Month(MovementDate))) AS [%CK_BranchMonth] RESIDENT [Movement]; // ------------------------------------------------------------------------------------------------- // Transforms Movement. [tmp_DateRange]: LOAD Min(MovementDate) AS MinDate, Max(MovementDate) AS MaxDate RESIDENT [Movement]; LET vMinDate = Num(Peek('MinDate', 0, 'tmp_DateRange')); LET vMaxDate = Num(Peek('MaxDate', 0, 'tmp_DateRange')); DROP TABLE [tmp_DateRange]; // ------------------------------------------------------------------------------------------------- // Builds a range with WHILE (field CalendarDate). [tmp_CalendarBase]: LOAD Date($(vMinDate) + IterNo() - 1) AS [CalendarDate] AUTOGENERATE 1 WHILE $(vMinDate) + IterNo() - 1 <= $(vMaxDate); // ------------------------------------------------------------------------------------------------- // Transforms tmp_FactSales. [Fact_Sales]: LOAD SaleID, [%SK_Customer], [%SK_Product], [%SK_Salesperson], [%SK_Branch], Quantity, Currency, ChannelName, NetAmountCOP, AmountBand, ReasonType, MovementType, [%CK_BranchMonth], [%SK_Date] RESIDENT [tmp_FactSales]; STORE [Fact_Sales] INTO [$(vPathModel)Fact_Sales.qvd] (qvd); // ------------------------------------------------------------------------------------------------- // Transforms BudgetVsActual. [Fact_BudgetVsActual]: LOAD BudgetAmount, ActualAmountCOP, AutoNumber(BranchID & '|' & MonthNum) AS [%CK_BranchMonth] RESIDENT [BudgetVsActual]; RENAME FIELD BudgetAmount TO BudgetAmountCOP; STORE [Fact_BudgetVsActual] INTO [$(vPathModel)Fact_BudgetVsActual.qvd] (qvd); // ------------------------------------------------------------------------------------------------- // Transforms Customer. [Dim_Customer]: LOAD CustomerID AS [%SK_Customer], DocNumber, Segment, CreditLimit, CustomerName, DocTypeName, CustomerCity, SignupDate, CustomerStatus, CreditProfile RESIDENT [Customer]; STORE [Dim_Customer] INTO [$(vPathModel)Dim_Customer.qvd] (qvd); // ------------------------------------------------------------------------------------------------- // Transforms Product. [Dim_Product]: LOAD ProductID AS [%SK_Product], SKU, ListPrice, UnitCost, UnitOfMeasure, CategoryName, Family, BrandName, CountryOfOrigin, ProductName, MarginPct RESIDENT [Product]; STORE [Dim_Product] INTO [$(vPathModel)Dim_Product.qvd] (qvd); // ------------------------------------------------------------------------------------------------- // Transforms Branch. [Dim_Branch]: LOAD BranchID AS [%SK_Branch], BranchName, Region, BranchCity, BranchFormat RESIDENT [Branch]; STORE [Dim_Branch] INTO [$(vPathModel)Dim_Branch.qvd] (qvd); // ------------------------------------------------------------------------------------------------- // Transforms Salesperson. [Dim_Salesperson]: LOAD SalespersonID AS [%SK_Salesperson], HomeBranchID, MonthlyTarget, SalespersonName, HireDate RESIDENT [Salesperson]; STORE [Dim_Salesperson] INTO [$(vPathModel)Dim_Salesperson.qvd] (qvd); // Checkpoint disabled from the toolbar; the lines below are commented out // STORE [BudgetVsActual] INTO [$(vTxfPath)BudgetVsActual.qvd] (qvd); // EXIT SCRIPT; // ------------------------------------------------------------------------------------------------- // Transforms tmp_CalendarBase. [Dim_Calendar]: LOAD CalendarDate, CalendarDate AS [%SK_Date], Year(CalendarDate) AS Year, Num(Month(CalendarDate)) AS MonthNum, Month(CalendarDate) AS Month, 'Q' & Ceil(Month(CalendarDate) / 3) AS Quarter, Week(CalendarDate) AS Week, WeekDay(CalendarDate) AS Weekday, If(WeekDay(CalendarDate) >= 5, 1, 0) AS IsWeekend, If(Len(ApplyMap('map_Holiday', Num(CalendarDate), '')) > 0, 1, 0) AS IsHoliday, ApplyMap('map_Holiday', Num(CalendarDate), Null()) AS HolidayName RESIDENT [tmp_CalendarBase]; STORE [Dim_Calendar] INTO [$(vPathModel)Dim_Calendar.qvd] (qvd); // ------------------------------------------------------------------------------------------------- // Kept as written in the imported script (no matching node type). SignupCalendar: DECLARE FIELD DEFINITION Tagged ('$date') FIELDS Dual(Year($1), YearStart($1)) AS [Year] Tagged ('$axis', '$year'), Month($1) AS [Month] Tagged ('$month', '$cyclic'), Dual(Year($1) & '-Q' & Num(Ceil(Month($1)/3)), QuarterStart($1)) AS [YearQuarter] Tagged ('$yearquarter', '$qualified'); // ------------------------------------------------------------------------------------------------- // Kept as written in the imported script (no matching node type). DERIVE FIELDS FROM FIELDS [SignupDate] USING [SignupCalendar]; DROP TABLE [Branch]; DROP TABLE [BudgetVsActual]; DROP TABLE [Customer]; DROP TABLE [Movement]; DROP TABLE [Product]; DROP TABLE [SaleReturn]; DROP TABLE [Sales]; DROP TABLE [Salesperson]; DROP TABLE [tmp_CalendarBase]; DROP TABLE [tmp_FactSales]; TRACE Q-Expert Distributors reload finished;
Flow_6.qvs
File · read by MUST_INCLUDENot pasted: saved as a file. On the Flow_6 tab of Generate Syntax, Export downloads Flow_6.qvs (Deploy writes every tab into a folder at once). Put it in lib://QXDistributors/Transform/_QVS/Flow_6.qvs, the Transform/_QVS folder inside your QXDistributors folder. If the file is not there, the reload stops with an error: that is what MUST_INCLUDE is for.
Show script · 30 lines
/*---------------------------------------------------------------------------------------- Description [Flow_6]: ------------------------------------------------------------------------------------- Author: The Q-Fábrica team - Date: [2026-11-02] ------------------------------------------------------------------------------------- Changes ------------------------------------------------------------------------------------- Author Date Detail ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- Copyright © Q-Expert Distributors. Q-Expert Qlik Developer Framework. www.qfabrica.com ----------------------------------------------------------------------------------------*/ // ------------------------------------------------------------------------------------------------- // SWITCH '$(vEnvironment)': 2 branches plus DEFAULT. SWITCH '$(vEnvironment)' CASE 'PROD' TRACE Production reload: the model QVDs are the ones the apps read; CASE 'QA' TRACE QA reload: check the model QVDs before promoting them; DEFAULT TRACE Development reload; END SWITCH
05Why this way
- Only keys share names. Every other field has a name of its own, so the facts and dimensions link through the
%keys and nothing else: no synthetic keys. - Check before writing. If no movement arrived, writing an empty model would overwrite yesterday's good one. The IF stops the reload first.
- One calendar over the real dates. A master calendar covering exactly the movement dates gives every chart the same year, month and holiday logic.
- Memory holds the model, disk holds the layers. Raw files, Transform QVDs and Model QVDs each have their folder; the app loads only
Model/.
06What comes next
Episode 7: what if someone hands you this script already written?
Before you model
Q-Explorator is the Q-Fábrica team's desktop tool to explore QVD, CSV, Excel and Parquet files and to convert CSV, TXT and Excel files into QVD. Open the files of each episode in it to see their content and quality first.
About Q-Explorator