Where does the fan-out hide?
You know the fan-out. Two tables, a join on the wrong grain, and suddenly revenue's triple what it should be. Nobody can say why the dashboard total doesn't match the report.
Tableau's relationship model was built to stop that. A semantic model in Data 360 is the same idea with agents standing on top, and it fanned out on me anyway. Three times. Three different numbers.
The game. Tuesday, July 9, 2002. The All-Star Game in Milwaukee. Both managers tried to get everybody in, and by the 11th both bullpens were empty: ten pitchers for the National League, nine for the American. 7–7. The commissioner called it a tie, and 41,871 people booed. I kept score. Ten future Hall of Famers played that night, the most of any game I've seen: six hitters and four pitchers. Count them the way my first measure did, batters only, and you get six. Which players count is the whole question here, and it's the pitchers who go missing.
Keeping_Score model in Data 360: seven objects, five relationships. Plays reaches the Hall of Fame only through Lahman People, joined on the batter, so pitchers never get in: that's the 35. HOF Sightings stands alone, and it already knows batters and pitchers both count: that's the 44.The question was “how many Hall of Famers have I seen play?” The contract's answer is 44: every inducted batter or pitcher who appeared in a game I attended. The first measure I wrote in Data 360 joined People to the Hall of Fame table and came back 281. Every inducted player in history. Nothing in the formula forced the path through my games, so the engine didn't walk it. A measure's query joins only the objects its expression names.
Add Plays to the formula and it drops to 35. Better, and still wrong. The graph has one edge from People to Plays, the batter, so pitchers never get in. The 44 comes from a pre-built sightings table that already knows batters and pitchers both count. I kept the 35 in the model as its own measure, right next to the 44, so the lesson stays measurable. Corollary found the same day: two metrics from unrelated fact tables can't share one query. The engine throws NO_PATH, and Tableau Next inherits the same limit, because it's the same model.
The same cross-table formula that Data 360 fanned out to 281, Tableau Cloud refused outright. “Only one upstream base table.” Different engines make different bets about whether to stop you or trust you. Neither one knows what a Hall of Famer sighting is. That's yours.
The whole layer was built into a fresh scratch org from scripts: two connections, Snowflake by key-pair and Databricks by token, seven data streams reading the data where it sits, one model with seven objects and five relationships and the seven measures. Verified seven of seven through the semantic engine on 2026-09-04. The docs said the connectors needed the browser. They didn't. Two Setup toggles were the only clicks in the sprint.
Then the third cloud. BigQuery's connector is walled in that org by an entitlement no API would name, so the marts were exported to Parquet in Google Cloud Storage and ingested through the storage connector: seven more streams, a second model. Ingest, not zero-copy. Two zero-copy legs and one copy. Three clouds in one org.
The ingest leg found its own bug. A total-replace load keyed on player id kept one Hall of Fame ballot per player, 1,543 of 6,426 rows, and the graph measure silently read 24 instead of 35. Fix: a composite ballot key in the export. The number that told me was the parity harness, which is the only reason the wrong 24 lasted a day instead of a quarter.
Before you trust a count from a semantic model, ask it for one you know is wrong: every inducted player, not just the ones you saw. If both come back the same, the path isn't being walked.
Next, batting 7th: Do the numbers survive the move? →