End-to-End Flow of API-Powered Import
This project connects directly to the World Bank Open Data API using native VBA.
The macro sends RESTful HTTP requests via `XmlHttp`, retrieves SDMX-JSON datasets, and parses them using a custom-built `JsonConverter` module.
Results are dynamically written into Excel tables, with support for parameters like countries, indicators, and years. No external libraries required.
The query LatestData demonstrates this logic in action and serves as a canonical example of how raw API data is turned into business-relevant signals.
let
// === STEP 1: LOAD SOURCE DATA ===
Source = Dataset,
// === STEP 2: GROUP AND SORT ===
GroupedRows = Table.Group(
Source,
{"Countries.Country", "Countries.Region", "Countries.Income Group", "Countries.Weight", "Indicators.Indicator", "Indicators.Unit", "Indicators.Type"},
{
{
"GroupedData",
each Table.FirstN(
Table.Sort(_, {{"Year", Order.Descending}}),
2
),
type table [
Countries.Region = nullable text,
Countries.Country = text,
Countries.Income group = text,
Indicators.Indicator = text,
Indicators.Type = text,
Indicators.Unit = text,
Countries.Weight = nullable number,
Year = nullable number,
Value = nullable number
]
}
}
),
// === STEP 3: EXTRACT STRUCTURED RECORDS ===
Extracted = Table.AddColumn(
GroupedRows,
"ExtractedValues",
each [
Region = Record.FieldOrDefault([GroupedData]{0}, "Countries.Region"),
Country = Record.FieldOrDefault([GroupedData]{0}, "Countries.Country"),
Income group = Record.FieldOrDefault([GroupedData]{0}, "Countries.Income Group"),
Weight = Record.FieldOrDefault([GroupedData]{0}, "Countries.Weight"),
Indicator = Record.FieldOrDefault([GroupedData]{0}, "Indicators.Indicator"),
Unit = Record.FieldOrDefault([GroupedData]{0}, "Indicators.Unit"),
Type = Record.FieldOrDefault([GroupedData]{0}, "Indicators.Type"),
ValueLastYear = try Number.From(Record.FieldOrDefault([GroupedData]{0}, "Value")) otherwise null,
ValuePreviousYear = try Number.From(Record.FieldOrDefault([GroupedData]{1}, "Value")) otherwise null
]
),
// === STEP 4: BUILD FINAL TABLE WITH EXPLICIT TYPE DEFINITION ===
FinalTable = Table.FromRecords(Extracted[ExtractedValues]),
Final = Table.TransformColumns(FinalTable, {
{"Weight", each _, type nullable number},
{"ValueLastYear", each Number.Round(Number.FromText(Text.From(_)), 4), type number},
{"ValuePreviousYear", each Number.Round(Number.FromText(Text.From(_)), 4), type number}
}),
#"Filtered Rows" = Table.SelectRows(Final, each true)
in
#"Filtered Rows"
Step 3: Data Modeling in Power Pivot Once the data is cleaned and enriched in Power Query, it is loaded into Power Pivot for semantic modeling. This step builds logical relationships between dimension tables (e.g. Geo, Calendar, Indicators) and the fact table. The goal: enable efficient filtering, slicing, and context-aware calculations in DAX. In this model, LatestData acts as the central fact table. All other dimension tables feed filter context into DAX calculations. By explicitly defining one-to-many relationships, we ensure fast queries, consistent slicing, and reusable logic across the dashboard. |
With a clean data model in place, DAX is used to define the business logic: dynamic measures, KPIs, rankings, and advanced calculations. The logic adapts to the indicator type (absolute or relative), handles missing data, and drives all visualizations in the dashboard.
LastYearValue:=SWITCH(
TRUE();
// Case 1: Absolute indicator (Type = "A")
// Return a simple SUM of ValueLastYear
HASONEVALUE(LatestData[Type]) &&
VALUES(LatestData[Type]) = "A";
SUM(LatestData[ValueLastYear]);
// Case 2: Relative indicator (Type = "R")
// Return a weighted AVERAGE of ValueLastYear using Weight
HASONEVALUE(LatestData[Type]) &&
VALUES(LatestData[Type]) = "R";
DIVIDE(
// Numerator: sum of (ValueLastYear × Weight)
SUMX(
LatestData;
LatestData[ValueLastYear] * LatestData[Weight]
);
// Denominator: sum of weights
SUMX(
LatestData;
LatestData[Weight]
);
// Fallback result in case of division by zero
BLANK()
);
// Default case: no valid type selected — return BLANK
BLANK()
)