🏠 Module Home · 🗂️ Handbook Home · ← 04 String Cleaning & Validation · Next → Module 11 NULL Handling ▶
This closing topic doesn’t introduce new functions — it applies everything from Topics 01–04 to end-to-end business reporting problems, using a logistics schema (warehouse labels, shipment tracking, carrier performance). The goal is to practice deciding which combination of string functions a real reporting requirement calls for, not just executing one when told which to use.
Business string analytics typically combines:
The skill this topic builds is sequencing these correctly — cleaning before extracting, extracting before grouping, formatting only at the very end.
Reporting and analytics teams are frequently asked for breakdowns “by region” or “by category” when no such column exists — only a composite code that encodes it. Business string analytics is the practice of deriving those dimensions reliably enough to aggregate on, without introducing miscounts from inconsistent formatting upstream.
Aggregating on a derived-but-uncleaned value silently fragments what should be a single group into several ("FEDX", "fedx", "FEDX " all becoming separate groups in a GROUP BY). This topic exists to make explicit that every derived-dimension report needs a cleaning pass before grouping, not just before display — a mistake that is easy to make once you’re comfortable with extraction functions in isolation.
LIKE/REGEXPThis topic is a synthesis of all functions covered in Topics 01–04: UPPER/LOWER, TRIM, LEFT/RIGHT/SUBSTRING, CONCAT/CONCAT_WS, LOCATE/SUBSTRING_INDEX, LIKE/REGEXP, REPLACE, LPAD/RPAD.
No new syntax. This topic demonstrates composition patterns such as:
SELECT
UPPER(TRIM(SUBSTRING_INDEX(tracking_number, '-', 1))) AS carrier_code,
COUNT(*) AS shipment_count
FROM shipments
GROUP BY UPPER(TRIM(SUBSTRING_INDEX(tracking_number, '-', 1)));
N/A — see Topics 01–04.
N/A at the function level; report-level queries in this topic return grouped, aggregated result sets.
tracking_number = " fedx-EU-88213 "
Step 1 (extract): SUBSTRING_INDEX(., '-', 1) → " fedx"
Step 2 (clean): TRIM(.) → "fedx"
Step 3 (normalize): UPPER(.) → "FEDX"
Step 4 (aggregate): GROUP BY on the fully-cleaned value
— NOT on the raw extracted value
Skipping steps 2–3 and grouping directly on the raw extraction would split this single carrier into multiple groups across records with different whitespace or casing.
Goal: Report shipment volume and average delivery days by carrier, extracted and cleaned from tracking_number.
SELECT
UPPER(TRIM(SUBSTRING_INDEX(tracking_number, '-', 1))) AS carrier_code,
COUNT(*) AS shipment_count,
ROUND(AVG(delivery_days), 1) AS avg_delivery_days
FROM shipments
GROUP BY UPPER(TRIM(SUBSTRING_INDEX(tracking_number, '-', 1)))
ORDER BY shipment_count DESC;
Reasoning: The GROUP BY expression must exactly match the SELECT expression, including the full clean-then-normalize chain — grouping on a partially cleaned version (e.g., forgetting UPPER()) reintroduces the fragmentation this topic exists to prevent.
tracking_number that doesn’t contain the expected delimiter will silently fall into its own (wrong) group rather than raising an error.GROUP BY on a function-derived expression cannot use a standard index on the underlying column, and materializes the full expression for every row before grouping — expected and acceptable for periodic reporting, but a strong signal to materialize the derived column if the report runs frequently or the table is large.SELECT and GROUP BY — functionally equivalent in most engines but significantly more maintainable and marginally friendlier to the query planner’s expression caching.tracking_number with no -) will have SUBSTRING_INDEX() return the whole string as the “carrier code” — these should be isolated and reviewed separately, not silently folded into the main report as their own spurious group.NULL values in the source column produce a NULL group in most engines’ GROUP BY, which typically sorts either first or last depending on the engine — always inspect whether a report’s top or bottom row is actually a NULL group before reporting a “top carrier.”SELECT, GROUP BY, and ORDER BY with a subtle inconsistency between the three (e.g., UPPER() in SELECT but not in GROUP BY), which most engines will reject outright — but some will silently allow with confusing results depending on SQL mode.GROUP BY, never after.GROUP BY expression exactly match the cleaning/normalization applied in the corresponding SELECT expression?tracking_number values have trailing whitespace and half don’t.SUBSTRING_INDEX() extraction without first checking whether every row actually contains the expected delimiter?tracking_number), fully cleaned before grouping.tracking_number values that don’t contain the expected two delimiters, so they can be excluded from the main carrier report with a documented reason.avg_delivery_days column padded to a fixed width for a plain-text summary email, using LPAD().This topic is where Topics 01–04 stop being separate skills and become one workflow: clean, extract, normalize, then aggregate and format — always in that order. The recurring lesson across every scenario in this module is that string data can’t be trusted to behave consistently on its own; every report built on derived text dimensions needs an explicit cleaning step, or its aggregate numbers are quietly wrong.