🚧 Under construction — VoTechWorks is being built. A free tech program — complimentary, as our commitment to get America working.
voTechWorksGrade 13 · a vocational technology learning center, by urbanicity
newSchoolWorks.com › VoTechWorks › Technology & Design › Data Science & Analytics › Data Cleaning and Preparation for Analysis › Lesson 8
TEC-DATA85-05 · Lesson 8 · 11 clock hours · lab

Cleaning Data in SQL and Integrating Sources

Objective. Students will write SQL queries that perform data-type casting, TRIM, COALESCE, CASE-WHEN recoding, and JOIN-based integration to prepare a multi-table dataset in PostgreSQL for analysis.

⚠ Pending professional review

This lesson, its worked examples and its practice questions has not yet been verified by an industry-certified professional or by a certification or approval body. It is a study draft and may contain errors. Do not use it as instructions for work on a job site or on live equipment. Always follow your instructor, your supervising professional, and current official references and regulations.

VoTechWorks program approval and accreditation are still in progress. Credentials are issued only by the certifying bodies named in each program, after their own exams.

Why SQL Cleaning Matters

Raw operational data is almost never analysis-ready. Work-order systems, customer databases, and sensor logs are designed for transaction speed, not analytical accuracy. Before any chart or model can be trusted, you must resolve mismatched data types, inconsistent strings, unexpected NULLs, and records scattered across multiple tables. SQL is the most direct tool for this work because the cleaning happens inside the database, close to the data, without exporting files or risking version confusion.

This lab uses PostgreSQL, an open-source relational database used widely in government, healthcare, and technology. You will work in DBeaver or the psql command-line client.

Data Integrity and Workspace Safety

Before writing a single query, protect the source data. Always work on a copy of the dump, never the original file. In a professional environment, raw data is treated like evidence — altering it without an audit trail can invalidate analysis and, in regulated industries, create legal exposure. In this sandbox, your first action after loading the dump is to confirm you are connected to the correct schema and that you have only SELECT and CREATE VIEW privileges, not DROP or DELETE. If you are uncertain, ask your instructor before proceeding. This is the data-world equivalent of verifying a machine is de-energized before you touch it.

Understanding the Four Tables

The city work-order dataset arrives as four un-normalized tables:

Each table has its own quality problems. You will fix them individually using views before joining.

TRIM and LOWER: Standardizing Strings

`TRIM` removes leading and trailing whitespace from a string. Without it, the value `' Electrical '` will never match `'Electrical'` in a JOIN or a WHERE clause — they look the same on screen but differ in the database. `LOWER` converts all characters to lowercase so that `'SMITH'`, `'Smith'`, and `'smith'` collapse to one value.

Always apply both together on any string column used in a JOIN or GROUP BY:

```sql

SELECT TRIM(LOWER(technician_name)) AS technician_name

FROM technicians;

```

CAST: Fixing Data Types

When a numeric column is stored as `VARCHAR` — which happens when data is imported from CSV without a schema — arithmetic and comparisons break silently. `CAST` converts the stored value to the correct type:

```sql

SELECT CAST(estimated_hours AS NUMERIC(6,2)) AS estimated_hours

FROM work_orders;

```

If a value cannot be cast (for example, the string `'N/A'` cannot become a number), PostgreSQL raises an error. Inspect problem rows with a `WHERE` filter before casting the whole column.

COALESCE: Replacing NULLs

`COALESCE` returns the first non-NULL value from its argument list. It is the standard tool for filling gaps:

```sql

SELECT COALESCE(district_code, 'UNKNOWN') AS district_code

FROM locations;

```

In a LEFT JOIN, rows in the left table that have no match in the right table produce NULLs in every right-table column. Wrapping those columns in `COALESCE` prevents NULL propagation into your final dataset:

```sql

SELECT w.order_id,

COALESCE(c.category_desc, 'Uncategorized') AS category

FROM work_orders w

LEFT JOIN categories c ON w.category_code = c.category_code;

```

CASE-WHEN: Recoding Values

`CASE-WHEN` is SQL's conditional expression. Use it to translate numeric codes into meaningful labels that non-technical readers can understand:

```sql

CASE

WHEN priority_code = 1 THEN 'Critical'

WHEN priority_code = 2 THEN 'High'

WHEN priority_code = 3 THEN 'Medium'

ELSE 'Low'

END AS priority_label

```

Always include an `ELSE` clause. Without it, any value not matched by a `WHEN` branch returns NULL silently.

Saving Work as Views

A view stores a SELECT query under a name without copying data. After cleaning each table, save the result as a view so that your JOIN query references clean, consistent data:

```sql

CREATE OR REPLACE VIEW v_technicians AS

SELECT

tech_id,

TRIM(LOWER(first_name)) AS first_name,

TRIM(LOWER(last_name)) AS last_name

FROM technicians;

```

Validation: Checking Your Own Work

Cleaning without validation is incomplete. Run a final query that confirms three things: total row count, NULL counts in required columns, and referential consistency (every work order has a valid location). A simple approach:

```sql

SELECT

COUNT(*) AS total_rows,

COUNT(*) FILTER (WHERE district_code IS NULL) AS null_district,

COUNT(*) FILTER (WHERE technician_name IS NULL) AS null_tech

FROM v_analysis_ready;

```

Record every figure in the validation checklist. Zero NULLs in required columns and a row count matching the answer key are the acceptance criteria for this lab.

Key terms8

TRIMA SQL function that removes leading and trailing whitespace characters from a string value, preventing mismatches in joins and comparisons.
CASTA SQL expression that converts a value from one data type to another, such as converting a VARCHAR column to NUMERIC so arithmetic can be performed.
COALESCEA SQL function that evaluates its arguments in order and returns the first non-NULL value, used to replace NULLs with meaningful defaults.
CASE-WHENA SQL conditional expression that evaluates conditions in sequence and returns a specified value for the first condition that is true, similar to an if-else statement.
LEFT JOINA JOIN that returns all rows from the left table and matching rows from the right table; columns from the right table are NULL when no match exists.
ViewA named, stored SELECT query in a database that behaves like a table but does not physically copy the data; used here to encapsulate cleaned versions of each source table.
NULLA special marker in SQL indicating that a value is absent or unknown; NULLs are not the same as zero or an empty string and require specific functions to handle.
Referential consistencyThe condition in which every foreign key value in one table has a matching primary key value in the referenced table, confirming that related records exist and are correctly linked.

Worked examples4

1. The 'work_orders' table has a column 'priority_code' stored as VARCHAR. Write a SELECT that casts it to INTEGER and adds a CASE-WHEN column named 'priority_label' with labels Critical, High, Medium, and Low for codes 1–4.
  1. Start the SELECT and reference the table: SELECT order_id, status
  2. Add the CAST expression: CAST(priority_code AS INTEGER) AS priority_code
  3. Add the CASE-WHEN block: CASE WHEN CAST(priority_code AS INTEGER) = 1 THEN 'Critical' WHEN CAST(priority_code AS INTEGER) = 2 THEN 'High' WHEN CAST(priority_code AS INTEGER) = 3 THEN 'Medium' WHEN CAST(priority_code AS INTEGER) = 4 THEN 'Low' ELSE 'Unknown' END AS priority_label
  4. Close with FROM work_orders; and run the query to confirm all rows return a label and no NULLs appear in priority_label.
Answer: SELECT order_id, status, CAST(priority_code AS INTEGER) AS priority_code, CASE WHEN CAST(priority_code AS INTEGER) = 1 THEN 'Critical' WHEN CAST(priority_code AS INTEGER) = 2 THEN 'High' WHEN CAST(priority_code AS INTEGER) = 3 THEN 'Medium' WHEN CAST(priority_code AS INTEGER) = 4 THEN 'Low' ELSE 'Unknown' END AS priority_label FROM work_orders;
2. The 'technicians' table has columns 'first_name' and 'last_name' with inconsistent casing and extra spaces. Write the CREATE VIEW statement for 'v_technicians' that standardizes both columns.
  1. Begin with CREATE OR REPLACE VIEW v_technicians AS
  2. Write SELECT tech_id to retain the primary key.
  3. Apply TRIM and LOWER to first_name: TRIM(LOWER(first_name)) AS first_name
  4. Apply TRIM and LOWER to last_name: TRIM(LOWER(last_name)) AS last_name
  5. Close with FROM technicians; — run SELECT * FROM v_technicians; to confirm the output shows lowercase, trimmed names.
Answer: CREATE OR REPLACE VIEW v_technicians AS SELECT tech_id, TRIM(LOWER(first_name)) AS first_name, TRIM(LOWER(last_name)) AS last_name FROM technicians;
3. Using the four cleaned views (v_work_orders, v_locations, v_technicians, v_categories), write a LEFT JOIN query that produces a single analysis-ready result. The 'district_code' column from v_locations must never be NULL in output — replace any NULL with the string 'UNKNOWN'.
  1. Start with SELECT w.order_id, w.priority_label, w.status from v_work_orders w as the driving table.
  2. LEFT JOIN v_locations l ON w.location_id = l.location_id and select COALESCE(l.district_code, 'UNKNOWN') AS district_code.
  3. LEFT JOIN v_technicians t ON w.tech_id = t.tech_id and select t.first_name, t.last_name.
  4. LEFT JOIN v_categories c ON w.category_code = c.category_code and select COALESCE(c.category_desc, 'Uncategorized') AS category_desc.
  5. Run the query and verify in a follow-up COUNT(*) FILTER (WHERE district_code IS NULL) that zero NULLs remain.
Answer: SELECT w.order_id, w.priority_label, w.status, COALESCE(l.district_code, 'UNKNOWN') AS district_code, t.first_name, t.last_name, COALESCE(c.category_desc, 'Uncategorized') AS category_desc FROM v_work_orders w LEFT JOIN v_locations l ON w.location_id = l.location_id LEFT JOIN v_technicians t ON w.tech_id = t.tech_id LEFT JOIN v_categories c ON w.category_code = c.category_code;
4. Write a validation query against a view named 'v_analysis_ready' that returns: total row count, count of NULLs in 'district_code', and count of NULLs in 'priority_label'.
  1. Open SELECT and alias three aggregates.
  2. First aggregate: COUNT(*) AS total_rows
  3. Second aggregate: COUNT(*) FILTER (WHERE district_code IS NULL) AS null_district
  4. Third aggregate: COUNT(*) FILTER (WHERE priority_label IS NULL) AS null_priority_label
  5. Close with FROM v_analysis_ready; — expected result for a passing submission is null_district = 0 and null_priority_label = 0.
Answer: SELECT COUNT(*) AS total_rows, COUNT(*) FILTER (WHERE district_code IS NULL) AS null_district, COUNT(*) FILTER (WHERE priority_label IS NULL) AS null_priority_label FROM v_analysis_ready;
In class: Students receive four un-normalized PostgreSQL tables representing a city's work-order system; they write SELECT queries using TRIM, LOWER, CAST, and COALESCE to standardize each table individually, then save results as views. · Students write a multi-table JOIN that integrates the four views into a single analysis-ready table; they must handle NULLs from LEFT JOINs explicitly using COALESCE and add a CASE-WHEN column that recodes numeric priority codes into readable labels. · Students run a final validation query that counts rows, checks for NULLs in required columns, and verifies referential consistency; they paste results into a validation checklist and submit both the SQL script and the completed checklist.
Materials: PostgreSQL sandbox, DBeaver or psql, work-order SQL dump file, validation checklist template
Check: Validation checklist must show zero NULLs in required columns and row counts matching instructor answer key (±0 rows); SQL script reviewed for correct use of TRIM, COALESCE, CAST, CASE-WHEN, and JOIN; pass at 80 % on a correctness rubric.

Practice check8

Answer each question, then check it. Nothing is recorded; this is your own practice.

Data cleaning and transformation — CompTIA Data+ / Google Data Analytics
1. A technician name column contains the value ' Rodriguez ' with leading and trailing spaces. Which SQL function removes those spaces before the value is used in a JOIN?
Data cleaning and transformation — CompTIA Data+ / Google Data Analytics
2. A LEFT JOIN between work_orders and categories returns NULL in the category_desc column for rows with no matching category. Which expression correctly replaces those NULLs with the label 'Uncategorized'?
Data types and transformation — CompTIA Data+ / Microsoft PL-300
3. You need to convert a VARCHAR column called 'estimated_hours' to a two-decimal numeric type for arithmetic. Which expression is correct in PostgreSQL?
Data cleaning and transformation — CompTIA Data+ / Google Data Analytics
4. A CASE-WHEN expression is written without an ELSE clause. What does SQL return for a row whose value does not match any WHEN condition?
Data integration and joins — CompTIA Data+ / IBM Data Science Professional
5. You create four cleaned views and then write a multi-table LEFT JOIN across all four. Which of the following best describes why you use a LEFT JOIN rather than an INNER JOIN?
Data validation and quality — CompTIA Data+
6. After running a validation query, the result shows null_district = 14. What does this indicate about your cleaning script?
Data cleaning and transformation — Google Data Analytics / CompTIA Data+
7. Which combination of functions best standardizes a free-text city name column that has mixed case and extra whitespace before using it in a GROUP BY?
Database objects and data management — IBM Data Science Professional / Microsoft PL-300
8. A senior analyst asks you to ensure the cleaned dataset can be reproduced at any time from the original source tables without storing a duplicate copy of the data. Which database object best meets this requirement?
Record this lesson on my transcript. Sign in with your key and enrollment email to record lessons (enroll free if you have no key). This page stays open to read and practise.