Context:FIT3003_MOC Β· the SQL half of Bridge Tables Β· same build order as Building Dimension Tables β Building Fact Tables, with the bridge inserted between two dimensions
Problem it solves: turning an operational associative table into a bridge, and computing the two optional columns (WeightFactor, StoreGroupList) that live in the parent dimension.
Quick Revision
π― Trigger: the source has an associative table with exactly two FKs and the far entity cannot key the fact β create table XxxBridge as select * from <associative table>;
β‘ Key Constraint:WeightFactor and StoreGroupList are computed by grouping the parent against the associative table β never against the bridge you just built, and never against the fact.
π§ Minimal Working Example
(Product Sales, Chapter 5 β the whole snowflake in one script; the bridge is a straight copy of StockSupplier)
Expected output:ProductSalesFACT(TimeIDβ,ProductNoβ,Suburbββ,TotalSales) with noSupplierID β supplier is reachable only via ProductDIMβProductSupplierBRIDGEβSupplierDIM. Four tables in the factβs from βΉ three join conditions.
π Variations
Model 1 β bridge only (Lab 4, Truck Delivery)
Build order: TruckDim1 (copy of Truck) Β· TripSeason1 (by hand, four rows: Summer, Autumn, Winter, Spring) Β· TripDim1 (projection of Trip) Β· BridgeTableDim1 (copy of Destination) Β· StoreDim1 (copy of Store) Β· a TempFact with the season alter/update, then TruckFact1 β Building Fact Tables Route B.
The bridge is the cheapest table in the script β Destination(TripIDβ,StoreIDββ) already is the bridge; copy it, drop nothing.
1.0/count(*) not 1/count(*) β integer division would floor every weight to 0; the 1.0 forces the numeric type.
Inner join drops empty trips β a trip with no row in Destination never appears, so it silently loses its weight; check for one before trusting the row count β Data Exploration (Warehouse Validation).
Then use it: total cost per store =β(costΓw) across the bridge.
Select S.StoreId, S.StoreName, sum(Total_delivery_Cost * Weight_Factor) as "Total Cost for Store"from TruckFact2 F, TripDim2 T, StoreDim2 S, BridgeTableDim2 Bwhere F.TripId = T.TripIdand T.TripId = B.TripIdand B.StoreId = S.StoreIdgroup by S.StoreId, S.StoreNameorder by S.StoreId, S.StoreName;
Model 3 β add the list aggregate
Create Table TripDim3 AsSelect T.TripID, T.TripDate, T.TotalKm, 1.0/count(D.StoreID) As WeightFactor, LISTAGG (D.StoreID, '_') Within Group (Order By D.StoreID) As StoreGroupListFrom Trip T, Destination DWhere T.TripID = D.TripIDGroup By T.TripID, T.TripDate, T.TotalKm;
Expected output:
TRIPID
TRIPDATE
TOTALKM
WEIGHTFACTOR
STOREGROUPLIST
Trip1
14-APR-13
370
0.2
M1_M2_M3_M4_M8
Trip2
14-APR-13
570
0.333333
M1_M2_M4
Trip3
14-APR-13
250
0.333333
M1_M5_M6
Trip4
15-JUL-13
450
0.25
M5_M6_M7_M8
Trip5
15-JUL-13
175
0.5
M3_M7
Syntax β LISTAGG(attr1, '_') Within Group (Order By attr1) As columnname; append Desc inside Order By to reverse the list.
Within Group is mandatory β LISTAGG is an ordered-set aggregate, so the sort key is declared separately from the group by.
Joining with vs without StoreGroupList
-- With StoreGroupList: two tables, a LIKE against the concatenated listselect *from TripDim3 T, StoreDim3 Swhere T.StoreGroupList like '%'||S.StoreID||'%';-- Without StoreGroupList: three tables, an equi-join through the bridgeselect *from TripDim3 T, BridgeTable3 B, StoreDim3 Swhere T.TripID = B.TripIDand B.StoreID = S.StoreID;
Trade β the list version is visually appealing and needs one join fewer; it is redundant storage and the LIKE cannot use an index.
βοΈ Practice
Practice 1: before creating TripDim2, display each trip's ID, date, kilometres and number of stores. Then convert the count to the weight.
Reference solution
select t.tripid, t.tripdate, t.totalkm, count(*)from trip t, destination dwhere t.tripid = d.tripidgroup by t.tripid, t.tripdate, t.totalkm;-- then replace count(*) with 1.0/count(*)
Key move: experiment with select first and only wrap it in Create Table β¦ As once the numbers are right β verify by counting each tripβs stores by hand and dividing 1 by that number.
Practice 2: the Product Sales warehouse must keep supplier names but not the supply history. Write the bridge.
Key move: dropping SupplyDate, Location and QtySupplied collapses the supply history, so distinct is now required β and with SupplierName absorbed there is no SupplierDIM left to build β Bridge Tables.
β οΈ Common Mistakes
π‘ 1/count(*) β integer division returns 0 for every trip with more than one store; write 1.0/count(*).
π‘ Grouping TripDim2 on the bridge instead of Destination β the bridge is a copy, so it works here, but the pattern is βaggregate from the operational sourceβ β Building Fact Tables.
π‘ Omitting Within Group from LISTAGG β Oracle rejects the call; the ordering clause is part of the function, not optional decoration.
π‘ Forgetting the join condition in a comma-list from β Cartesian product, quota blown, account locked β SQL Joins (ANSI).