SUBSTRING — Extract Parts of Text
title: “SUBSTRING” part: 2 topic_number: 1 slug: “substring” difficulty: “Intermediate” prerequisites: “string-functions-upper-lower” —
SUBSTRING — Extract Parts of Text
What Is It?
SUBSTRING() (also written as SUBSTR()) extracts a portion of a string. It’s like cutting out a specific section of text — you specify where to start and how many characters to take.
Real-world analogy: Like highlighting a specific word or phrase in a document.
When you’d use it:
- Extract area codes from phone numbers
- Get the first letter of a name
- Parse formatted codes (like ‘EMP-12345’ → ‘12345’)
- Extract year/month from date strings
Syntax Breakdown
1
2
3
SUBSTRING(string, start_position, length)
-- Or
SUBSTRING(string FROM start_position FOR length)
Parameters:
string: The text to extract from (column or literal)start_position: Where to begin (1 = first character)length: How many characters to extract (optional — if omitted, takes everything from start to end)
Key points:
- Positions are 1-indexed (first character is position 1, not 0)
- If start_position is negative, counts from the end (-1 = last character)
- If length exceeds string length, returns as much as available
- Returns NULL if input is NULL
Basic Examples
Extract First 3 Characters
1
2
3
4
5
SELECT
first_name,
SUBSTRING(first_name, 1, 3) AS first_three
FROM employees
LIMIT 5;
Expected output:
| first_name | first_three |
|---|---|
| Alice | Ali |
| Bob | Bob |
| Carol | Car |
| David | Dav |
| Eve | Eve |
What happened: Started at position 1, took 3 characters.
Extract Last 4 Characters (Negative Position)
1
2
3
4
5
SELECT
project_name,
SUBSTRING(project_name, -4) AS last_four
FROM projects
LIMIT 5;
Expected output:
| project_name | last_four |
|---|---|
| Website Redesign | sign |
| Mobile App | App |
| CRM System | stem |
| API Development | ment |
| Data Pipeline | line |
What happened: Negative position counts from the end. -4 means “start 4 characters from the end”.
Extract Middle Section
1
2
3
SELECT
'EMP-12345' AS employee_code,
SUBSTRING('EMP-12345', 5, 5) AS id_number;
Expected output:
| employee_code | id_number |
|---|---|
| EMP-12345 | 12345 |
What happened: Started at position 5 (after ‘EMP-‘), took 5 characters.
Going Deeper
Parse Phone Numbers
Imagine employees have phone numbers in format ‘(555) 123-4567’:
1
2
3
4
5
6
-- Create test data
SELECT
'(555) 123-4567' AS phone,
SUBSTRING('(555) 123-4567', 2, 3) AS area_code,
SUBSTRING('(555) 123-4567', 7, 3) AS prefix,
SUBSTRING('(555) 123-4567', 11, 4) AS line_number;
Expected output:
| phone | area_code | prefix | line_number |
|---|---|---|---|
| (555) 123-4567 | 555 | 123 | 4567 |
Positions:
- Area code: starts at 2 (after ‘(‘), length 3
- Prefix: starts at 7 (after ‘) ‘), length 3
- Line: starts at 11 (after ‘-‘), length 4
Extract Domain from Email
1
2
3
4
5
SELECT
'[email protected]' AS email,
SUBSTRING('[email protected]',
LOCATE('@', '[email protected]') + 1
) AS domain;
Expected output:
| domain | |
|---|---|
| [email protected] | company.com |
How it works:
LOCATE('@', email)finds position of ‘@’ (15)+ 1moves past the ‘@’- SUBSTRING from that position to end
Advanced: You’ll learn LOCATE in advanced topics, but this shows SUBSTRING’s power when combined with other functions.
Create Initials
1
2
3
4
5
6
7
8
9
10
11
SELECT
first_name,
last_name,
CONCAT(
UPPER(SUBSTRING(first_name, 1, 1)),
'.',
UPPER(SUBSTRING(last_name, 1, 1)),
'.'
) AS initials
FROM employees
LIMIT 5;
Expected output:
| first_name | last_name | initials |
|---|---|---|
| Alice | Johnson | A.J. |
| Bob | Smith | B.S. |
| Carol | Williams | C.W. |
| David | Brown | D.B. |
| Eve | Davis | E.D. |
Professional formatting with first and last initials.
Pause and Predict: What does
SUBSTRING('Hello', 1, 100)return?
Answer
`'Hello'` If length exceeds the string length, SUBSTRING returns as much as is available. It doesn't error or pad with spaces.Watch Out — Common Mistakes
Mistake #1: Using 0-Based Indexing
1
2
3
-- WRONG (trying to use 0-based indexing)
SELECT SUBSTRING('Hello', 0, 3);
-- Returns: 'Hel' (MySQL treats 0 as 1)
What programmers expect: ‘Hel’ (characters at positions 0, 1, 2)
What MySQL does: Treats position 0 as position 1, so returns ‘Hel’ anyway (but this is confusing!)
Better:
1
2
3
-- • CORRECT (explicit 1-based indexing)
SELECT SUBSTRING('Hello', 1, 3);
-- Returns: 'Hel'
Key: Always use 1 for the first character.
Mistake #2: Forgetting Length Parameter
1
SELECT SUBSTRING('EMP-12345', 5);
What beginners think: “This will return 5 characters.”
What actually happens: Returns everything from position 5 to the end: ‘12345’
When this is useful: Extracting “from here to the end”
When you need a specific length: Always include the third parameter:
1
SELECT SUBSTRING('EMP-12345', 5, 3); -- Returns: '123'
Mistake #3: Not Handling NULL
1
2
3
-- Assumes column has values
SELECT SUBSTRING(middle_name, 1, 1) AS middle_initial
FROM employees;
Problem: If middle_name is NULL, result is NULL (not an error, but might surprise you).
Better:
1
2
3
-- • Handle NULL explicitly
SELECT IFNULL(SUBSTRING(middle_name, 1, 1), '') AS middle_initial
FROM employees;
Or:
1
2
3
4
5
6
SELECT
CASE
WHEN middle_name IS NOT NULL THEN SUBSTRING(middle_name, 1, 1)
ELSE 'N/A'
END AS middle_initial
FROM employees;
Edge Case Spotlight
Negative Length (Not Supported)
1
2
SELECT SUBSTRING('Hello', 2, -1);
-- Returns: '' (empty string)
MySQL doesn’t support negative length. If you provide a negative number, it returns an empty string (not an error).
Multibyte Characters (UTF-8)
1
2
3
4
5
SELECT
'Café' AS word,
SUBSTRING('Café', 1, 3) AS first_three,
LENGTH('Café') AS byte_length,
CHAR_LENGTH('Café') AS char_length;
Expected output:
| word | first_three | byte_length | char_length |
|---|---|---|---|
| Café | Caf | 5 | 4 |
What’s happening:
- ‘é’ takes 2 bytes in UTF-8
- SUBSTRING works with characters, not bytes (returns ‘Caf’)
- LENGTH counts bytes (5)
- CHAR_LENGTH counts characters (4)
In MySQL 8+ with utf8mb4: SUBSTRING handles international characters correctly.
Try This
Exercise 1 (Guided)
Extract the first letter of each employee’s last name, in uppercase. Show last_name and first_letter. Sort alphabetically by first_letter.
Hint
UPPER(SUBSTRING(last_name, 1, 1))Exercise 2 (Independent)
For all projects, create a “short name” that is the first 10 characters of project_name. If project_name is less than 10 characters, show the full name. Show project_name and short_name.
Hint
SUBSTRING(project_name, 1, 10) — it automatically handles short names.Exercise 3 (Challenge)
Extract the last 3 characters of each employee’s email address (the domain extension, like ‘com’, ‘org’, etc.). Show email and extension. Handle NULL emails by displaying ‘N/A’.
Hint
Use negative position for last characters. Use IFNULL or COALESCE for NULLs.Answer Key
Exercise 1 Answer
```sql SELECT last_name, UPPER(SUBSTRING(last_name, 1, 1)) AS first_letter FROM employees ORDER BY first_letter, last_name; ``` **Expected output:** | last_name | first_letter | |-----------|--------------| | Anderson | A | | Brown | B | | Clark | C | | Davis | D | | Garcia | G | | ... | ... | First letters grouped together, sorted alphabetically.Exercise 2 Answer
```sql SELECT project_name, SUBSTRING(project_name, 1, 10) AS short_name FROM projects ORDER BY project_name; ``` **Expected output:** | project_name | short_name | |--------------|------------| | API Development | API Develo | | CRM System | CRM System | | Data Pipeline | Data Pipel | | Marketing Campaign | Marketing | | Mobile App | Mobile App | | Security Audit | Security A | | Website Redesign | Website Re | Truncated to 10 characters (or full name if shorter).Exercise 3 Answer
```sql SELECT email, IFNULL(SUBSTRING(email, -3), 'N/A') AS extension FROM employees ORDER BY extension, last_name; ``` **Expected output:** | email | extension | |-------|-----------| | NULL | N/A | | NULL | N/A | | [email protected] | com | | [email protected] | com | | [email protected] | org | Domain extensions extracted from email addresses. **Note:** This assumes emails end with a 3-letter extension. In reality, extensions vary (.com, .co.uk, .info, etc.), so you'd need more sophisticated parsing.Quick Recap
• SUBSTRING(string, start, length) extracts text portions
• Positions are 1-indexed (first character = 1)
• Negative positions count from the end
• Omit length to get “from here to end”
• Returns NULL if input is NULL
• Handles UTF-8 characters correctly in MySQL 8+
• Combine with CONCAT, UPPER, LOWER for powerful transformations
Up Next
Time for a Challenge! → Mini Challenge 7
You’ve completed Part 1 foundations and learned SUBSTRING! Test your combined skills with a comprehensive challenge.