Data fundamentals
Count the right thing when combining tables.
In this lesson · 03 of 10
All lessons in this track →Saved in this browser. View your learning.
Start with the idea
Count the right thing when combining tables.
Decide what each row represents before counting. Combining tables can create multiple rows for one person. Keep missing values different from zero.
Join: Combining tables using a shared field, such as a person ID.
See the example and check your understanding
A made-up teaching example
Person A has two application rows. Person B has none. A joined table can contain two rows for A. Counting those rows as people would count A twice.
Ask yourself
Are two application rows always two different people?
Show the explanation
No. Count the intended unit, such as unique people or applications. Keep the original tables and record the transformation.
Start with these checks
- Name what one row represents.
- Check repeated IDs after a join.
- Preserve missing values and source records.
Use the different practice case below and check every requirement in its full instructions.
The video explains the main idea. Use the sources, instructions and checks below to complete the practice.
Captions appear here during playback.
Data fundamentals
Count the right thing when combining tables.
Video playback is unavailable. Read the descriptive transcript below, go directly to practice, or open the video file.
Source-based teaching example · Fictional data · Reviewed 2026-10-03. Microsoft Mark · locally synthesized. On a small screen, use fullscreen for diagram details or read the transcript.
Read the descriptive transcript
0:00 · What you will learn
Count the right thing when combining tables.
What appears on screen
Count the right thing when combining tables.
0:05 · The idea
Decide what each row represents before counting. Combining tables can create multiple rows for one person. Keep missing values different from zero.
What appears on screen
Decide what each row represents before counting. Combining tables can create multiple rows for one person. Keep missing values different from zero.
0:18 · One useful term
Join. Combining tables using a shared field, such as a person ID.
What appears on screen
Combining tables using a shared field, such as a person ID.
0:26 · A worked example · 1
Person A has two application rows. Person B has none. A joined table can contain two rows for A.
What appears on screen
Person A has two application rows. Person B has none. A joined table can contain two rows for A.
0:36 · A worked example · 2
Counting those rows as people would count A twice.
What appears on screen
Counting those rows as people would count A twice.
0:42 · Your turn to think
Are two application rows always two different people? Pause and decide before continuing.
What appears on screen
Are two application rows always two different people? Pause the video if you need more time.
0:59 · Why this answer works
No. Count the intended unit, such as unique people or applications. Keep the original tables and record the transformation.
What appears on screen
No. Count the intended unit, such as unique people or applications. Keep the original tables and record the transformation.
1:10 · Check 1
Name what one row represents.
What appears on screen
Name what one row represents.
1:15 · Check 2
Check repeated IDs after a join.
What appears on screen
Check repeated IDs after a join.
1:21 · Check 3
Preserve missing values and source records.
What appears on screen
Preserve missing values and source records.
1:26 · Now try a different case
Open the lesson and try its different practice case. Use every check in the full task. Watching alone does not mark the lesson complete.
What appears on screen
Open the lesson Try the new case Check and revise The full task and its requirements are in the linked lesson.
Watching explains the example. Complete the lesson practice to check what you can do. Explore all visual lessons →
Lesson preparation and outcomes
Prerequisites: Lists, dictionaries and simple Python functions.
By the end, you can: name what one row represents before you count; choose a join and predict its row count; count people and applications separately; bind query values with placeholders; handle bad rows by a stated rule and record a number’s lineage.
Worked scenario and practice feedback
Two synthetic people have IDs a and b. Applications contains two rows for a and none for b. An inner join yields two rows for a; a left join also preserves b with missing application fields. Counting joined rows as people overcounts a. Aggregate by the intended entity.
Practice: one task, in the practice exercise below.
Data engineering supports many AI tasks. Loading it. Cleaning it. Joining it. Filtering it. Data preparation and validation often determine whether a model can do useful work; no fixed percentage applies to every system. We start with the two tools you’ll use for that: SQL and pandas.
Part 1 SQL: the 80% you'll use.
SQL is the language for talking to relational databases. Postgres, MySQL, SQLite, BigQuery, Snowflake, Redshift: all speak it. Learn it once, use it everywhere.
You need six verbs.
-- 1. SELECT — what columns to get back
SELECT name, rank, branch FROM veterans;
-- 2. WHERE — filter rows
SELECT name FROM veterans WHERE branch = 'Marines' AND years >= 4;
-- 3. ORDER BY — sort
SELECT name, years FROM veterans ORDER BY years DESC LIMIT 10;
-- 4. GROUP BY + aggregate — collapse rows
SELECT branch, COUNT(*) AS n, AVG(years) AS avg_years
FROM veterans
GROUP BY branch;
-- 5. JOIN — combine two tables
SELECT v.name, j.title
FROM veterans v
JOIN job_apps j ON v.id = j.veteran_id
WHERE j.status = 'offer';
-- 6. INSERT / UPDATE / DELETE — modify data
INSERT INTO veterans (name, branch) VALUES ('Operator A', 'Army');
UPDATE veterans SET rank = 'O-3' WHERE name = 'Operator A';
DELETE FROM veterans WHERE id = 42;Those six verbs and three operators (=, AND, OR) cover most of what you do. The rest (subqueries, window functions, CTEs) you pick up as you need them.
One pattern worth learning by name
The JOIN is the single most useful operation in SQL. You’ll use it constantly. Mental model: you have two tables that share a column. You want one combined view.
-- "Show me every veteran and any job apps they have."
SELECT v.name, j.title, j.status
FROM veterans v
LEFT JOIN job_apps j ON v.id = j.veteran_id;LEFT JOIN keeps every row from the left table even if it has no match on the right. INNER JOIN only keeps rows that match on both sides. RIGHT JOIN is the inverse of left and rarely used.
Count the right thing after a join
A join returns one row per match, not one row per person. Name what one row represents before you count.
SELECT COUNT(*) AS joined_rows,
COUNT(DISTINCT v.id) AS people,
COUNT(j.veteran_id) AS rows_with_an_app
FROM veterans v
LEFT JOIN job_apps j ON v.id = j.veteran_id;COUNT(*) counts rows. COUNT(DISTINCT v.id) counts people. COUNT(j.veteran_id) skips rows where the left join found no application and filled in NULL. NULL means no value. It is not zero.
Bind values, do not build the string
Pass a value separately from the SQL text. Python’s sqlite3 uses ? as the placeholder.
import sqlite3
con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE veterans (id TEXT, name TEXT, branch TEXT)")
branch = "Marines" # could be typed by a user
rows = con.execute(
"SELECT name FROM veterans WHERE branch = ?", (branch,)
).fetchall()This is a parameterized query. The database receives the value as data, so a quote or semicolon inside it cannot change the query. Never join a value into the SQL with + or an f-string. Placeholders stand for values, not table or column names.
Part 2 pandas: the Python equivalent for in-memory data.
pandas provides in-memory table operations familiar from SQL. Use it when the working data and intermediate operations fit available memory. Row count alone is not a capacity rule: column count, string length, data types and temporary copies matter. Measure df.memory_usage(deep=True).sum() on a representative sample, preserve headroom, and profile the intended operations. Read selected columns, use appropriate types, or process independent chunks when needed. See the official scaling guidance; some workloads require a database or another execution engine.
import pandas as pd
# Load a CSV
df = pd.read_csv("veterans.csv")
# Look at it
df.head() # first 5 rows
df.info() # columns + types
df.describe() # numeric summary
df.shape # (rows, cols)
# Select columns
df[["name", "branch"]]
# Filter rows
df[df["branch"] == "Marines"]
df[(df["branch"] == "Marines") & (df["years"] >= 4)]
# Sort
df.sort_values("years", ascending=False).head(10)
# Group + aggregate
df.groupby("branch").agg(n=("name", "count"), avg_years=("years", "mean"))
# Join two DataFrames
merged = df.merge(jobs_df, left_on="id", right_on="veteran_id", how="left")If you know SQL, pandas is an evening. If you don’t, pandas is harder than SQL because the syntax is denser. Either way, both repay the time.
Part 3 The file formats you'll see.
| Format | Use when | Read with |
|---|---|---|
| CSV | Spreadsheets, hand-curated data, exports | pd.read_csv() |
| JSON / JSONL | API responses, structured logs | pd.read_json() |
| Parquet | Column-oriented analytics; compressed; choose from workload needs | pd.read_parquet() |
| SQL | Anything in a relational database | pd.read_sql() |
| Excel | When you have to. Convert to CSV if you can. | pd.read_excel() |
Parquet is the format the data world uses for serious work. Smaller than CSV, faster to read, preserves types. Once you’re past prototype data, prefer parquet.
df.to_parquet("veterans.parquet")
df2 = pd.read_parquet("veterans.parquet")
Part 4 Cleaning data: the unglamorous half of the job.
Real data is messy. Names with whitespace. Dates as strings. Branch codes that should match but don’t. Two patterns worth burning in:
Strip and standardize
df["branch"] = df["branch"].str.strip().str.lower()
df["name"] = df["name"].str.title()Parse dates
df["enlisted_at"] = pd.to_datetime(df["enlisted_at"], errors="coerce")
df["years"] = (pd.Timestamp.now() - df["enlisted_at"]).dt.days / 365errors="coerce" turns a value that is not a real date, such as 2026-02-30, into NaT. Set those rows aside. Do not guess the day. SQLite’s date() does not reject it: it returns 2026-03-02 with no warning.
Handle missing values
df["branch"] = df["branch"].fillna("Unknown") # assign back; inplace is deprecated under pandas' copy-on-write
df = df.dropna(subset=["enlisted_at"]) # dropna returns a new frame — assign it, don't discard itWhitespace, types and null handling are useful initial diagnostic checks. If a query is returning nothing, check whitespace, types, and nulls first.
Record the lineage
Lineage is the trail from a number back to its source. Write down which file, which filter and which join produced it, so someone else can rerun it and get the same number. Keep the original file unchanged. Clean in a query or a copy.
Interface/reference limits
Use the workflow reference to distinguish model capability, deployment and harness controls. Provider snippets in this lesson are illustrative, have not been executed against an actual provider in this revision, and require current SDK/model capability verification before separately authorized use.
Interactive practice / Fictional fixture
Record your counts and your explanation
Use people.csv and applications.csv from the practice exercise. Enter each figure, then explain the difference. The worked solution unlocks after you have written your explanation and run the checks.
Everything runs in this browser. No AI model is called and no work is uploaded. Bounded checks cover specific facts and rules. Your writing is not graded: you review it yourself. Practice never marks a lesson complete automatically.
Name the unit of each figure. Say which rows you set aside, and why.
Try the task, run the checks, then revise. Checks are teaching feedback, not an instructor grade.
Observed browser simulation
This trace comes from the bounded local checker. It is not a Python run, model result or production tool execution.
Self-review
Locked. Write every written part, then run the checks. A few words is not an attempt.
This is self-review, not grading. Read our answers, then tick what your own work does. Ticks are optional. They stay on this page and are not saved or sent anywhere. A different answer can be just as good if it meets the lesson criteria.
Explain why the row count and the people count differ, and what you would report
A strong answer
people.csv has 10 rows but 9 people, because P04 is entered twice. applications.csv has 12 rows, and one row is one application. The left join on the files as given returns 13 rows: 11 applications that match a person, P04’s application a second time, and one row for P07, who has none. Joined rows are person-application pairs, so I would not report them as people. I would report 9 people and 12 applications, and say that 8 people have at least one application. I set A07 aside because its owner, P99, is not in people.csv. I flag A08 because 2026-02-30 is not a calendar date, and I leave it out of date-based counts. I changed neither file. Lineage for the people count: people.csv as received, one row per person_id, no join.
A flawed answer
The join returned 13 rows, so there are 13 people with applications on file. I changed the bad date on A08 to 2026-02-28 and deleted A07 because nobody owns it. Then the totals looked clean.
What is wrong with it: It reports joined rows as people, guesses a date and deletes a source row. Worked solution. People: 9. The file has 10 rows and P04 is on two of them, so count distinct person_id values. Applications: 12. One row is one application. Joined rows: 13. Eleven applications match a person. P04 is on two person rows, so its one application comes back twice, which makes 12. P07 has no application and comes back once with NULL, which makes 13. No matching person: 1. A07 names P99. A join that starts from people never shows it; start from applications, left join to people and keep the rows where the person side is NULL. Invalid date: 1. A08 is dated 2026-02-30. Keep the row, flag it and leave it out of date-based counts. A trap: the inner join returns 12 rows, the same as the application file. One application is lost and one is doubled, so a matching total does not show that a join is correct. solve.sql in the practice files runs every one of these queries.
Keep or restore your work
Drafts are not saved automatically. Save only fictional practice data. A browser draft may be lost if storage is cleared; download a copy to keep it.