Data Modeling in Power BI: Joins, Relationships, and Schemas

Inner Join Venn Diagram

Introduction

Data modeling is the process of organizing your data into tables, defining relationships between them, and enhancing the data with calculated fields, measures, and hierarchies. This process ensures accurate analysis and sets you up to create clear, impactful Power BI reports.

Types of SQL Joins

Joins are one of the most important features that SQL offers. Joins allow us to make use of the relationships we have set up between our tables.
In this article, we’ll break down the core SQL join types:

1. INNER JOIN

The SQL INNER JOIN statement joins two tables based on a common column and selects rows that have matching values in these columns.

Inner Join Venn Diagram

Example:

SELECT *
FROM employees
INNER JOIN departments
ON employees.department_id = departments.id;

In this query:

employees.department_id refers to the department_id column from the employees table.
departments.id refers to the id column from the departments table.

The ON clause ensures that rows are matched based on these columns, creating a relationship between the two tables.
The query above returns all the fields from both tables. The INNER keyword only affects the number of rows returned, not the number of columns. The INNER JOIN filters rows based on matching department_id and id, while the SELECT * ensures all columns from both tables are included.

2. LEFT JOIN

The SQL LEFT JOIN combines two tables based on a common column. It then selects records having matching values in these columns and the remaining rows from the left table.

Left Join Venn Diagram

Example:

-- left join Customers and Orders tables based on their shared customer_id columns
-- Customers is the left table
-- Orders is the right table

SELECT Customers.customer_id, Customers.first_name, Orders.item
FROM Customers
LEFT JOIN Orders
ON Customers.customer_id = Orders.customer_id;

Here, the SQL command combines data from the Customers and Orders tables.

The query selects the customer_id and first_name from Customers and the amount from Orders.

Hence, the result includes rows where customer_id from Customers matches customer from Orders.

3. RIGHT JOIN

The SQL RIGHT JOIN statement joins two tables based on a common column. It selects records that have matching values in these columns and the remaining rows from the right table.

Right Join Venn Diagram

Case Example

-- join Customers and Orders tables
-- based on customer_id of Customers and customer of Orders
-- Customers is the left table
-- Orders is the right table

SELECT Customers.customer_id, Customers.first_name, Orders.amount
FROM Customers
RIGHT JOIN Orders
ON Customers.customer_id = Orders.customer;

Here, the SQL command selects the customer_id and first_name columns (from the Customers table) and the amount column (from the Orders table).

And, the result set will contain those rows where there is a match between customer_id (of the Customers table) and customer (of the Orders table), along with all the remaining rows from the Orders table.

4. FULL OUTER JOIN

The SQL FULL OUTER JOIN statement joins two tables based on a common column. It selects records that have matching values in these columns and the remaining rows from both of the tables.

Full Outer Join Venn Diagram

Use Case Example

SELECT Customers.customer_id, Customers.first_name, Orders.amount
FROM Customers
FULL OUTER JOIN Orders
ON Customers.customer_id = Orders.customer;

Here, the SQL command selects the customer_id and first_name columns (from the Customers table) and the amount column (from the Orders table).

The result set will contain all rows of both the tables, regardless of whether there is a match between customer_id (of the Customers table) and customer (of the Orders table).

5. ANTI JOIN

  1. The SQL LEFT ANTI JOINreturns rows in the left table that have no matching rows in the right table.
    How it works:
    Achieved with LEFT JOIN + WHERE [key in right table] IS NULL.
  2. The SQL RIGHT ANTI JOIN returns rows in the right table that have no matching rows in the left table.
    How it works:
    Achieved with RIGHT JOIN + WHERE [key in left table] IS NULL.

Case Example

SELECT *
FROM tableA
LEFT JOIN tableB ON tableA.key = tableB.key
WHERE tableB.key IS NULL;

This query will return all rows from tableA that do not have a corresponding row in tableB

CARDINALITY

In Power BI, the term “cardinality” describes the type of relationship between two tables according to how many related rows each table has. It specifies the relationships between rows in one table and rows in another.

  1. One-to-Many (1 : *): In this relationship, one record in the first table connects to many records in the second table.
    Example:
    Each customer can have multiple orders, but each order belongs to only one customer.
    In the model view, this appears as 1 –> *
    The “1” side is usually a dimension table (like Customers).
    The “many” side is a fact table (like Orders).

  2. Many-to-One (* : 1): This is the reverse direction of a one-to-many relationship. It happens when the filter starts from the many side and moves to the one side.
    Example:
    If you select a particular order in a visual (from the Orders table), Power BI can trace it back to the correct Customer in the Customers table.
    Multiple orders (many) point to one customer (one), that is a many-to-one relationship.

  3. One-to-One (1 : 1): Each record in one table matches exactly one record in another table.
    Example:
    Assume, you have a Customer table and a Customer Profile table. Each customer ID appears only once in both tables.
    Useful when you split a large table into smaller parts for better performance.

  4. Many-to-Many (* : *): Both tables can have repeating values in their key columns
    Example:
    Imagine you are a student and you want to join club, each student can join multiple clubs and each club can have multiple students.
    Power BI manages this scenario using a bridge table, which maps each student to each club.

Active Vs. Inactive Relationships

Active
An active relationship in Power BI is the primary, default connection between two tables. Power BI automatically uses active relationships for filtering and calculations unless you specify otherwise.
You can only have one active relationship between two tables at a time, even if there are multiple potential ways they could be related.
Example:
Consider a Sales table and a Dates table. You might have a relationship based on the OrderDate field. If this is the main date you want to use for your analysis, it will be marked as the active relationship.

Inactive
An inactive relationship is a secondary connection between two tables that Power BI does not automatically use for filtering or calculations. These relationships are useful when you need multiple ways to connect tables, but only one connection should be used by default.
Inactive relationships can be activated manually in specific measures or calculations using DAX (Data Analysis Expressions).
Example:
In addition to OrderDate, your Sales table might also have a ShipDate field that relates to the Dates table. You can create an inactive relationship between ShipDate and Dates, which you can activate selectively when needed.

CROSS FILTER

Cross filtering in Power BI determines how filters are applied across related tables in a relationship. It defines the direction in which the filter context flows between tables.
There are two types of cross filter direction:

  1. Single Direction:
    Filters flow from one table to another, for example: from Customer to Orders.
    This is the default and most efficient setup.
  2. Both Direction/Bidirectional:
    Filters flow both ways between tables.
    Used when both tables should influence each other, for example: Region and Sales.

How Cardinality and Cross Filtering work together

Cardinality defines how tables are connected, while cross filtering defines how filters move through those connections. Together, they ensure that your visuals respond correctly to user actions.
Example:
Cardinality: One-to-Many between Customers and Orders.
Cross Filter Direction: Single from Customers to Orders.
Result: Selecting a customer filters their orders but not the other way around.

Difference between Relationships and Joins

Relationships:

  • Are displayed as flexible noodles between logical tables
  • Require you to select matching fields between two logical tables
  • Do not require you to select join types
  • Make all row and column data from related tables potentially available in the data source
  • Maintain each table’s level of detail in the data source and during analysis
  • Create independent domains at multiple levels of detail. Tables aren’t merged together in the data source.
  • During analysis, create the appropriate joins automatically, based on the fields in use.
  • Do not duplicate aggregate values (when Performance Options are set to Many-to-Many)
  • Keep unmatched measure values (when Performance Options are set to Some Records Match)

Joins:

  • Are displayed with Venn diagram icons between physical tables
  • Require you to select join types and join clauses
  • Joined physical tables are merged into a single logical table with a fixed combination of data
  • May drop unmatched measure values
  • May duplicate aggregate values when fields are at different levels of detail
  • Support scenarios that require a single table of data, such as extract filters and aggregation

Fact Vs. Dimension Tables

Characteristic Fact Table Dimension Table
Basic Definition It contains data (often transactional) that you want to analyze It accompanies the fact table and stores information that describe records in the fact table
Purpose It contains measures and is used for analysis and decision making It contains information about a business and its process
Type of Data Numeric and textual format Textual format
Primary/Foreign Key A primary key for each dimension which is acts as a foreign key in the dimension table A foreign key associated with the primary key of the fact table
Hierarchy No hierarchy Contains a hierarchy
Attributes Less attributes More Attributes
Records More Records Less Records
Table Growth Grows vertically Grows horizontally
Data Model Fewer fact tables in the data model More dimension tables in a data model
Update Frequency Records added very frequently Records not added frequently

Star Schema

A star schema is a way to organize data in a database, especially in data warehouses, to make it easier and faster to analyze. At the center, there’s a main table called the fact table, which holds measurable data. Around it are dimension tables.

star schema diagram

Snowflake Schema

A snowflake schema splits dimension tables into smaller sub-dimensions to keep data more organized and detailed; just like snowflakes in a large lake.

snowflake schema diagram

Flat Table

A flat table in Power BI is a single, wide table containing all data, including measures and descriptive attributes, without relationships, similar to a spreadsheet.

OrderID Date Product Category Customer Region Qty Unit Price
1001 01/01/26 Laptop Electronics Alice North 1 1000
1002 01/02/26 Desk Furniture Bob South 2 150
1003 01/02/26 Laptop Electronics Charlie North 1 1000

Role-playing Dimensions

Role-playing dimensions in Power BI occur when a single dimension table (e.g. Date) connects to a fact table multiple times, representing different roles (e.g. Order Date, Ship Date). Power BI allows only one active relationship between tables; subsequent roles are inactive.

Common Modeling Issues

  1. Building Models from CSV exports.
  2. Missing Unique Keys.
  3. Using Multiple date tables.
  4. Overlapping attributes across tables.
  5. Lack of normalisation.
  6. Ignoring virtual relationships.
  7. Keeping unnecessary columns.

Conclusion

I hope you found this blog helpful in understanding the significance of data modeling and the basic principles crucial to building an effective data model. By understanding these fundamental concepts, such as star schemas, cardinality, cross-filter direction, and active and inactive relationships, you should be well on your way to becoming a more proficient Power BI developer.

Unlock Local AI: Generating Synthetic Data for Powerful Fine-Tuning

I created a new website: Free Access to the 8 Volumes on Typescript & AI Masterclass, no registration required. Choose Volume and chapter on the menu on the left. 160 Chapters and hundreds of quizzes at the end of chapters.

Synthetic data generation is rapidly becoming the key to deploying powerful AI models locally – on your browser, phone, or edge device. Forget expensive cloud APIs and privacy concerns. This guide dives deep into the theory and practice of creating custom datasets to fine-tune smaller models, unlocking performance previously only achievable with massive architectures like GPT-4. We’ll explore the underlying principles, provide a practical code example, and discuss advanced techniques for building a robust synthetic data pipeline.

The Power of Synthetic Data: From Cloud to Edge

Large Language Models (LLMs) are incredibly powerful, but their size and computational demands make them impractical for many real-world applications. Running a 70B parameter model requires significant GPU resources and incurs ongoing costs. Fine-tuning a smaller, more efficient model (like a 3B or 7B parameter model) offers a compelling alternative, but requires a high-quality, task-specific dataset. This is where synthetic data comes in.

Instead of relying on scarce and expensive human-labeled data, we leverage a larger “teacher” model to generate the data needed to train a smaller “student” model. This isn’t just data augmentation; it’s knowledge distillation at the dataset level. The goal is to compress the vast knowledge of a large transformer into a lightweight model capable of running locally using technologies like WebGPU or Transformers.js. This opens up exciting possibilities for offline AI, privacy-preserving applications, and low-latency performance.

The Teacher-Student Dynamic: A Learning Analogy

Think of a renowned professor (the Teacher Model, like GPT-4 or Llama 3 70B) teaching a class of students (the Student Model, a smaller 7B or 3B parameter model). The professor has broad knowledge, while the students need to master a specific skill – say, writing Python code. General textbooks are slow and uneven. However, a curated set of practice problems and solutions (the Synthetic Data) allows students to focus their learning. The Teacher generates the curriculum.

Microservices & Serverless Functions: A Web Development Parallel

In modern web development, LLMs can be likened to Microservices. A large LLM is a monolithic backend handling many tasks. Synthetic data generation is like extracting a specific microservice and refactoring it into a lightweight, serverless function.

  • Teacher (Monolith): Powerful but resource-intensive.
  • Synthetic Data: Defines the behavior of the specific service.
  • Student (Serverless Function): Optimized for a single task, running efficiently on edge devices.

Understanding the Theoretical Foundations

Synthetic data generation relies on several key concepts:

Distribution Matching & Kullback-Leibler (KL) Divergence

Language models are essentially probability distributions over sequences of tokens. Fine-tuning adjusts the model’s weights to match a target distribution. With human data, the target distribution comes from human writing. With synthetic data, it comes from the Teacher’s outputs. The Student learns to minimize the divergence between its predicted distribution and the Teacher’s.

This is often formalized using Kullback-Leibler (KL) Divergence, a measure of how one probability distribution differs from another. Minimizing KL divergence during fine-tuning helps the Student mimic the Teacher’s reasoning and accuracy.

The Critical Role of System Prompts

The System Prompt is the architect of the data generation process. It doesn’t just ask a question; it instructs the Teacher how to generate the data. A well-crafted System Prompt is crucial for quality.

Example:

"You are a data curator. Your task is to generate high-quality instruction-response pairs for training a coding assistant. You must generate diverse scenarios, include edge cases, and provide step-by-step reasoning in the response."

This transforms the Teacher into a data factory, ensuring the generated data adheres to specific formatting requirements.

The Synthetic Data Pipeline: A Multi-Stage Process

  1. Seed Generation: The Teacher creates a list of topics or questions.
  2. Response Generation: The Teacher answers, often using Chain-of-Thought (CoT) reasoning.
  3. Filtering & Validation: A “Critic” model (another instance of the Teacher or a reward model) evaluates quality. This is like a code linter, checking for errors.
  4. Formatting: Data is structured into formats like ChatML or Alpaca templates.

Code Example: Streaming Synthetic Data Generation (Node.js & TypeScript)

This example demonstrates a minimal backend endpoint using Node.js, Express, and TypeScript to generate synthetic data using a local LLM (simulated via Ollama). It focuses on streaming data generation and structuring it for fine-tuning.

import express, { Request, Response } from 'express';
import axios, { AxiosResponse } from 'axios';

// --- Types & Interfaces ---
interface SyntheticDataPoint {
  instruction: string;
  response: string;
  category: string;
}

// --- Constants ---
const app = express();
const PORT = 3000;
const OLLAMA_API_URL = 'http://localhost:11434/api/generate';

// --- Helper Functions ---
async function generateSyntheticData(topic: string): Promise<SyntheticDataPoint> {
  const prompt = `Generate a high-quality instruction and response pair about ${topic}. Output ONLY JSON: {"instruction": "...", "response": "...", "category": "${topic}"}`;
  const response: AxiosResponse = await axios.post(OLLAMA_API_URL, { model: 'llama2', prompt: prompt, format: 'json', stream: false });
  return {
    instruction: response.data.response.instruction,
    response: response.data.response.response,
    category: topic
  };
}

app.get('/generate-stream', (req: Request, res: Response) => {
  const { topic } = req.query;

  if (!topic || typeof topic !== 'string') {
    res.status(400).json({ error: "A 'topic' query parameter is required." });
    return;
  }

  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.flushHeaders();

  (async () => {
    for (let i = 0; i < 3; i++) {
      const dataPoint = await generateSyntheticData(topic);
      const sseMessage = `event: updatendata: ${JSON.stringify(dataPoint)}nn`;
      res.write(sseMessage);
      await new Promise(resolve => setTimeout(resolve, 500));
    }
    res.write('event: endndata: {"status": "completed"}nn');
    res.end();
  })();
});

app.listen(PORT, () => {
  console.log(`Synthetic Data Generator running at http://localhost:${PORT}`);
});

Advanced Techniques & Considerations

  • ReAct Loops: Iteratively refine prompts based on data quality.
  • Quality Filtering: Use a separate model to score generated data.
  • Token Efficiency: Optimize data for limited context windows.
  • Error Handling: Robustly handle LLM hallucinations and API errors.
  • Parallel Generation: Use Promise.all() to speed up data creation (be mindful of GPU/CPU limits).

Conclusion: The Future of Local AI

Synthetic data generation is a game-changer for deploying AI locally. By leveraging the power of large models to create custom datasets, we can unlock the potential of smaller, more efficient models, bringing AI closer to the edge and empowering a new generation of privacy-focused, low-latency applications. The key is understanding the underlying principles, building a robust pipeline, and continuously refining the process to ensure data quality.

The concepts and code demonstrated here are drawn directly from the comprehensive roadmap laid out in the book The Edge of AI. Local LLMs (Ollama), Transformers.js, WebGPU, and Performance Optimization Amazon Link of the AI with JavaScript & TypeScript Series.
The ebook is also on Leanpub.com: https://leanpub.com/EdgeOfAIJavaScriptTypeScript.

👉 Free Access now to the TypeScript & AI Series on Programming Central, it includes 8 Volumes, 160 Chapters and hundreds of quizzes for every chapter.

The Vibe Coding Playbook: A Jungle Guide for the Context-Driven Developer

screenshot from the project repo

The Tuxedo in the Jungle

We all enter the Vibe Coding domain from different places, carrying different luggage for this new journey. Some are well prepared and start the journey fully equipped. It’s not their first rodeo, as they are seasoned code masters and this is just a new tool.

Others travel lightweight and haven’t packed in advance. Little or no prior programming skills at all. Scarce knowledge of important “side” topics that are pillars for the coding world. So, their journey feels more like making their way through a vast jungle – dressed in a tuxedo, no machete, just a small nail clipper for all the thick bushes ahead.

I find myself somewhere in the middle. I started this adventure (vibe coding or as I prefer to address it: context coding) fairly recently. I left my tuxedo in the wardrobe, but somehow found that a simple t-shirt, shorts and flip flops are not the best jungle attire either.

From FAQ.md to a Structured Playbook

After watching hundreds of YouTube tutorials on this subject, I still had (have) millions of questions and lots of blind spots. One day I just decided to sit down and spontaneously write down all my questions into a simple text file.

Then it turned to a FAQ.md file. Then came the insight to feed that file to the top 10 LLM models and collect back their answers – and put them in a separate directory of .md files.

That is how this vague idea turned into a vibe coding project, as I asked OpenCode to analyze all these .md files and produce structured content for vibe coding newbies like myself.

screenshot from the project repo

Perhaps the title – Vibe Coding Playbook – feels too grandiose for such a modest project.

But this is a work in progress and I really do hope that this little repo of mine could one day be a beneficial pit stop for all in need of some help on this long and exhausting drive.

My bigger hope is that there will be people who would help to make that come true. With more content or at least with competent feedback , comments and/or ideas.

What’s Inside the Repo?

So far the repo is really humble: a brief intro to Vibe Coding as a term, followed by 11 sections with short explanations of different aspects of this subject. All based on this FAQ.md and the answers from the Top 10 LLM models.

Recently I created also a Roadmap section with plans for future content to be added.

another screenshot from the project repo

Using OpenCode as my coding partner, I did some basic styling, polished the content and added basic examples here and there. As I said, it is still rather a draft or beta version, than a finished work.

So far, the project took 6 days and 39 commits. But I still consider it…

A Work in Progress

Vibe (context) coding itself is a real jungle whose trees, bushes and lianas change colors – and even places – literally every day. So edits and adjustments are inevitable, for that I am sure.

Check out the repo here: https://github.com/plamen5rov/vibe-coding-playbook

I really can’t wait to read your reaction, observation, assessment, comments and even criticism.

There are at least two things that are far worse than criticism: silence and indifference.

I’d love to hear your thoughts.

Do you feel like you’re hacking through the jungle with a nail clipper, or have you found your “machete” yet?

hacking through the vibe coding jungle

Non-hateful comments are highly welcome.