SQL String Functions: UPPER, SUBSTRING, TRIM & More
On this page 11 sections ▾
Text arrives messy: inconsistent capitalisation, stray spaces, values glued together that you need apart, values apart that you need together. String functions are SQL's toolkit for cleaning and reshaping text inside the query, so the data comes back the way the report needs it. This guide covers the core set that exists, under one spelling or another, in every major database.
Changing case: UPPER and LOWER
SELECT UPPER(first_name) AS shouted, LOWER(email) AS normalised
FROM Employees
ORDER BY emp_id
LIMIT 3;
| shouted | normalised |
|---|---|
| ANANYA | ananya.rao@example.com |
| MARCUS | m.bennett@example.com |
| LEILA | leila.h@example.com |
● 3 rows · produced by running this query on the sample database
Cosmetics are the obvious use; the important one is case-insensitive comparison. Whether 'Austin' equals 'austin' depends on the database's collation settings — in SQLite, which runs this playground, it does not — but lower-casing both sides makes the comparison behave the same everywhere:
SELECT company, city
FROM Customers
WHERE LOWER(city) = 'austin';
Measuring length: LENGTH and LEN
SELECT first_name, LENGTH(first_name) AS name_length
FROM Employees
ORDER BY name_length DESC, first_name
LIMIT 5;
| first_name | name_length |
|---|---|
| Hiroshi | 7 |
| Isabela | 7 |
| Ananya | 6 |
| Daniel | 6 |
| Marcus | 6 |
● 5 rows · produced by running this query on the sample database
Hiroshi and Isabela tie at seven characters and three names tie at six, so first_name breaks
the ties and keeps the order the same on every run. Typical uses are data quality checks — finding
suspiciously short names, or values that overflow a field limit.
The spelling is the annoying part. SQLite, MySQL and PostgreSQL say LENGTH, and this
playground, which runs SQLite, accepts only that. SQL Server calls it LEN:
-- SQL Server
SELECT TOP 5 first_name, LEN(first_name) AS name_length
FROM Employees
ORDER BY name_length DESC, first_name;
The behaviour is not quite identical either. MySQL’s LENGTH counts bytes, so a
letter such as é counts as two; CHAR_LENGTH counts characters there. SQL Server’s
LEN ignores trailing spaces, so LEN('abc ') is 3, while SQLite’s
LENGTH('abc ') is 5.
Slicing text: SUBSTRING
SELECT email, SUBSTRING(email, 1, 5) AS first_five
FROM Employees
ORDER BY emp_id
LIMIT 3;
| first_five | |
|---|---|
| ananya.rao@example.com | anany |
| m.bennett@example.com | m.ben |
| leila.h@example.com | leila |
● 3 rows · produced by running this query on the sample database
The arguments are the string, the starting position, and how many characters to take — and SQL counts from 1, not 0, which surprises everyone arriving from a programming language. Combined with other functions it builds things like initials:
SELECT company, UPPER(SUBSTRING(company, 1, 1)) AS initial
FROM Customers
ORDER BY customer_id
LIMIT 5;
| company | initial |
|---|---|
| Meridian Trading Co. | M |
| Acme Robotics | A |
| Sakura Logistics | S |
| Kilimanjaro Foods | K |
| Andes Textiles | A |
● 5 rows · produced by running this query on the sample database
Find and replace: REPLACE
SELECT REPLACE(email, '@example.com', '') AS mailbox
FROM Employees
ORDER BY emp_id
LIMIT 4;
| mailbox |
|---|
| ananya.rao |
| m.bennett |
| leila.h |
| t.nowak |
● 4 rows · produced by running this query on the sample database
REPLACE swaps every occurrence of one substring for another — here replacing the domain with nothing, which strips it. It only changes the query's output; the stored data is untouched unless you put the expression inside an UPDATE.
Trimming whitespace: TRIM
SELECT TRIM(' SQL Practice ') AS trimmed;
| trimmed |
|---|
| SQL Practice |
● 1 row · produced by running this query on the sample database
TRIM removes leading and trailing spaces — the classic cure for data pasted in from spreadsheets, where
'Sales ' quietly fails to equal 'Sales'. Most databases also offer LTRIM and
RTRIM for one side only.
Joining text: || and CONCAT
SELECT first_name || ' ' || last_name AS full_name,
CONCAT(last_name, ', ', first_name) AS sorted_name
FROM Employees
ORDER BY emp_id
LIMIT 3;
| full_name | sorted_name |
|---|---|
| Ananya Rao | Rao, Ananya |
| Marcus Bennett | Bennett, Marcus |
| Leila Haddad | Haddad, Leila |
● 3 rows · produced by running this query on the sample database
Both forms work here, which is convenient because the real world splits: || is the SQL
standard (PostgreSQL, SQLite, Oracle), while MySQL wants CONCAT() and SQL Server uses
+ or, since SQL Server 2012, CONCAT(). SQLite gained CONCAT() in
version 3.44. The difference that bites is what happens when one part is NULL:
SELECT 'Ananya' || NULL AS with_pipes,
CONCAT('Ananya', NULL) AS with_concat,
'Ananya' || COALESCE(NULL, '') AS pipes_made_safe;
| with_pipes | with_concat | pipes_made_safe |
|---|---|---|
| NULL | Ananya | Ananya |
● 1 row · produced by running this query on the sample database
In this playground, as in PostgreSQL, a NULL anywhere in a || chain makes the whole result
NULL, while CONCAT() skips it. That is not universal: MySQL’s CONCAT()
returns NULL if any argument is NULL (its CONCAT_WS() skips them), SQL Server’s
+ returns NULL by default just like ||, and Oracle’s ||
treats NULL as empty text. The third column is the portable fix: wrap any part that may be missing in
COALESCE(part, ''), and whichever way your database joins text, the answer is the same.
Handling missing text: COALESCE and NULLIF
Not strictly string functions — they work on any type — but text is where you meet them daily. COALESCE returns the first non-NULL argument, which is how a NULL becomes a readable label:
SELECT e.first_name, COALESCE(m.first_name, 'no manager') AS reports_to
FROM Employees e
LEFT JOIN Employees m ON m.emp_id = e.manager_id
ORDER BY e.emp_id
LIMIT 6;
| first_name | reports_to |
|---|---|
| Ananya | no manager |
| Marcus | Ananya |
| Leila | Ananya |
| Tomas | no manager |
| Grace | Tomas |
| Hiroshi | Tomas |
● 6 rows · produced by running this query on the sample database
NULLIF is its mirror: NULLIF(a, b) returns NULL when the two arguments are equal, otherwise
returns the first. Its everyday job is turning junk placeholder values — empty strings, 'N/A' — back into
honest NULLs before COALESCE or aggregates handle them:
SELECT NULLIF('same', 'same') AS becomes_null, NULLIF('a', 'b') AS stays_a;
| becomes_null | stays_a |
|---|---|
| NULL | a |
● 1 row · produced by running this query on the sample database
Try it yourself
Every example above marked runs in the playground works on the sample database as-is. Try chaining functions — UPPER inside SUBSTRING inside REPLACE — and watch how they compose.
Open the PlaygroundOne habit worth building
Wrapping a column in a function inside WHERE — as in LOWER(city) = 'austin' — is
fine on small data, but on a large indexed table it can stop the database using the index, because the
index stores city, not LOWER(city). Production databases solve this with
case-insensitive collations or function-based indexes. Nothing to act on while learning — just remember
that where you put the function matters more as tables grow.
Practice this topic
Reading explains it; solving it is what makes it stick. These exercises use exactly what this page covered, and your answer is checked by running it against the same database:
- Mailing labels Easy
- Case-insensitive search Easy
- Strip the domain Medium
- The longest product names Medium
- Who reports to whom Medium
Frequently asked questions
What are string functions in SQL?
Built-in functions that transform text values inside a query: changing case, measuring length, slicing out substrings, replacing text, trimming spaces and joining values together. They change what the query returns, not what the table stores.
What is the difference between LEN and LENGTH?
Mostly the spelling: SQL Server uses LEN; MySQL, PostgreSQL and SQLite use LENGTH. Two traps hide behind it. MySQL's LENGTH counts bytes, so a letter such as é counts as two; use CHAR_LENGTH there to count characters. SQL Server's LEN ignores trailing spaces. SQLite, which runs this site's playground, accepts only LENGTH.
Does SUBSTRING start counting at 0 or 1?
At 1. SUBSTRING(email, 1, 5) takes the first five characters. Starting positions in SQL are 1-based across every major database, unlike most programming languages.
How do I concatenate strings in SQL?
The standard operator is ||, used by PostgreSQL, SQLite and Oracle. MySQL uses the CONCAT() function, and SQL Server uses + or CONCAT(). NULL handling differs: || returns NULL if any part is NULL in PostgreSQL and SQLite (Oracle treats the NULL as empty text), and so does + in SQL Server by default. CONCAT() skips NULLs in PostgreSQL, SQL Server and SQLite, but returns NULL in MySQL, where CONCAT_WS skips them.
Do string functions change the data stored in the table?
No. In a SELECT they only shape the output. To change stored values, use the same expression inside an UPDATE statement — for example SET email = LOWER(email).
How do I compare text case-insensitively in SQL?
The portable way is to apply LOWER (or UPPER) to both sides of the comparison. Whether plain = ignores case depends on the database and its collation: MySQL and SQL Server usually do, PostgreSQL and SQLite do not. In SQLite, LIKE ignores case for the letters A to Z while = does not.
Related reading
About the author
SQL Practice
SQL Practice publishes free SQL tutorials, an in-browser playground and 42 practice exercises, built so you can run what you read. Every runnable example in our tutorials is executed against the site’s sample database before it is published. Read how we keep tutorials accurate, or report a mistake.