Skip to content
Reference / SQL String Functions
Reference

SQL String Functions

Measure, reshape, clean, and combine text values.

Text literals use single quotes: 'active'. Double quotes mean an identifier (a column or table name), which is a common beginner mix-up.

Common functions

Function Returns
length(s) Number of characters.
lower(s) / upper(s) Change case.
trim(s) Remove leading/trailing spaces.
substring(s, 1, 3) Slice from position, length.
replace(s, 'a', 'b') Replace all occurrences.
split_part(s, '@', 2) Nth piece after splitting on a delimiter.
concat(a, b) or a || b Join strings together.

Cleaning and combining

Normalize and join
select first_name || ' ' || last_name        as full_name,
       lower(trim(email))                   as email_clean,
       split_part(email, '@', 2)            as email_domain
from   customers

Case-insensitive matching

Two equivalent ways
-- normalize both sides
where lower(state) = 'pa'

-- or use ILIKE (DuckDB / Postgres)
where state ilike 'pa'

Text types and lengths

In DuckDB, TEXT, STRING, CHAR(n), and VARCHAR(n) are all the same type, VARCHAR. The length is accepted and ignored: a varchar(5) column stores eight characters without complaint, and a char(5) column doesn't pad 'ab' with spaces.

Four spellings, one type
create or replace table text_types (
  a text,
  b char(5),
  c varchar(5),
  d string
);
insert into text_types values ('x', 'ab', 'abcdefgh', 'y');
select length(b) as b_length,
       c,
       typeof(c) as c_type
from text_types;

length counts characters, and strlen counts the bytes they take in UTF-8. They differ for any character outside plain ASCII. An emoji with a skin tone is two characters to length and one to length_grapheme, which counts what a reader sees as one symbol:

Characters, bytes, and symbols
select length('héllo')           as chars,
       strlen('héllo')           as bytes,
       length('👍🏽')            as thumbs_chars,
       length_grapheme('👍🏽')   as thumbs_symbols,
       strlen('👍🏽')            as thumbs_bytes;

That returns 5, 6, 2, 1, and 8. A limit like "a code is at most 10 characters" means length; a limit on storage or a fixed-width file means bytes.

Comparing and sorting text

= compares text exactly. 'Tools ' with a trailing space doesn't equal 'Tools', and 'tools' doesn't either. Clean both sides with trim and lower, or compare with collate nocase:

Exact, trimmed, and case-insensitive
select 'Tools ' = 'Tools'                  as trailing_space,
       'tools' = 'Tools'                   as different_case,
       trim('Tools ') = 'Tools'            as trimmed,
       'tools' = 'Tools' collate nocase    as nocase;

ORDER BY on text sorts by character code, so every uppercase letter comes before every lowercase one, and accented letters come after z. collate nocase.noaccent sorts the way a reader expects:

Byte order, then reader order
select name
from (values ('zebra'), ('Éclair'), ('apple'), ('Banana')) as t(name)
order by name;

select name
from (values ('zebra'), ('Éclair'), ('apple'), ('Banana')) as t(name)
order by name collate nocase.noaccent;

The first returns Banana, apple, zebra, Éclair. The second returns apple, Banana, Éclair, zebra.

Close matches and patterns

levenshtein(a, b) counts the single-letter edits that turn one string into the other, and jaro_winkler_similarity(a, b) scores them from 0 to 1. Both compare characters exactly, so lowercase both sides first:

Names within two edits
select first_name,
       last_name,
       levenshtein(lower(last_name), 'castelano') as edits
from customers
where levenshtein(lower(last_name), 'castelano') <= 2

Both Castellanos come back, 1 edit away. A close match is a candidate, not proof: Ford and Foss are only 2 edits apart. For text with a shape rather than a spelling, such as "digits, then letters", see SQL regular expressions.

How other databases differ

Engine Too long for VARCHAR(n) Counting = on text
DuckDB Stored anyway length characters, strlen bytes Case and spaces count
PostgreSQL Error: value too long for type character varying(5) length characters, octet_length bytes Case counts. CHAR(n) pads, and ignores the padding
MySQL Error in strict mode: Data too long for column LENGTH is bytes; CHAR_LENGTH characters Case-insensitive by default
SQL Server Error: String or binary data would be truncated LEN characters, without trailing spaces; DATALENGTH bytes Case-insensitive by default; trailing spaces ignored
SQLite Stored anyway length characters Case and spaces count

So the same query can match a row in SQL Server or MySQL and miss it in DuckDB. When text comes from another system, trim it and settle its case once, as it loads.

Dialect note

Concatenation differs by engine: || works in DuckDB, Postgres, SQLite, and Oracle. SQL Server uses + (or CONCAT), and only accepts || from SQL Server 2025 and in Azure SQL. MySQL needs CONCAT unless PIPES_AS_CONCAT is enabled. CONCAT(...) is the most portable choice.