Text is never clean. Different cases, accents, extra spaces, and mixed formats break comparisons. String functions normalize and slice text  but collation decides what “equal†even means.
In real databases, text data is rarely consistent:
Apple, apple, APPLEcafé vs cafeString functions help normalize, format, and filter text so comparisons and reports behave correctly. But first  how SQL compares strings.
In SQL, a string is stored with collation rules that decide how text is compared, sorted, and matched. Two strings that look different may be treated as equal.
CREATE TABLE sales (
item VARCHAR(30) NOT NULL, quantity INT DEFAULT NULL
) ENGINE=InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci;
CREATE TABLE sales (
item VARCHAR(30) NOT NULL, quantity INT DEFAULT NULL
);
-- pgsql: collation per column/database via CREATE COLLATION; default is often case-sensitive
This table's collation is utf8mb4_0900_ai_ci  accent-insensitive, case-insensitive.
Rules for comparing and sorting strings:
| Flag | Meaning | What it controls | Example | Result |
|---|---|---|---|---|
| ai | Accent-Insensitive | Ignores é, è, ñ | cafe vs café | Equal |
| as | Accent-Sensitive | Respects accents | cafe vs café | Not equal |
| ci | Case-Insensitive | Ignores case | Apple vs apple | Equal |
| cs | Case-Sensitive | Respects case | Apple vs apple | Not equal |
CONCAT(a,b,c…) joins strings as-isCONCAT_WS(sep, a,b,c…) joins with a separator (WS = with separator) and skips NULL valuesSELECT CONCAT('Hello', ' ', 'Raj');
SELECT 'Hello' || ' ' || 'Raj';
Output: Hello Raj
SELECT CONCAT('Hello', NULL, 'Raj');
SELECT 'Hello' || NULL || 'Raj';
Output: NULL  CONCAT returns NULL if any argument is NULL (same for ||).
SELECT CONCAT_WS(' ', 'Hello', NULL, 'Raj');
SELECT CONCAT_WS(' ', 'Hello', NULL, 'Raj');
Output: Hello Raj  CONCAT_WS ignores NULL.
SELECT CONCAT_WS('. ', NULL, 'Hi', 'Raj');
SELECT CONCAT_WS('. ', NULL, 'Hi', 'Raj');
Output: Hi. Raj  separator applied only between real values.
Tip: Use CONCAT when no NULL expected; use CONCAT_WS when NULLs may exist.
LOWER() → lowercaseUPPER() → uppercaseSELECT LOWER('RAjstriveR');
SELECT LOWER('RAjstriveR');
Output: rajstriver
SELECT UPPER('RAjstriveR');
SELECT UPPER('RAjstriveR');
Output: RAJSTRIVER
Useful for standardizing text for display or case-insensitive comparisons.
TRIM()  both sidesLTRIM()  left onlyRTRIM()  right onlySELECT TRIM(' RAJ ');
SELECT TRIM(' RAJ ');
Output: RAJ
SELECT LTRIM(' RAJ ');
SELECT LTRIM(' RAJ ');
Output: RAJ⣠(trailing space remains)
SELECT RTRIM(' RAJ ');
SELECT RTRIM(' RAJ ');
Output: â£RAJ (leading space remains)
Common use: cleaning user input or CSV imports.
LENGTH() → number of bytesCHAR_LENGTH() → number of charactersSELECT LENGTH('RAJ');
SELECT LENGTH('RAJ');
Output: 3
SELECT LENGTH('é');
SELECT LENGTH('é');
Output: 2 (MySQL) / 1 (pgsql  LENGTH counts chars in PG). In MySQL, é is 2 bytes in utf8mb4.
SELECT LENGTH('😀');
SELECT LENGTH('😀');
Output: 4 (MySQL bytes) / 1 (pgsql chars)
SELECT CHAR_LENGTH('RAJ');
SELECT CHAR_LENGTH('RAJ');
Output: 3
SELECT CHAR_LENGTH('é');
SELECT CHAR_LENGTH('é');
Output: 1
SELECT CHAR_LENGTH('😀');
SELECT CHAR_LENGTH('😀');
Output: 1
Use CHAR_LENGTH for user-visible characters, LENGTH for byte-level checks (MySQL).
Why does LENGTH('é') = 2 but CHAR_LENGTH('é') = 1 in MySQL?
LEFT(str, n)  first n charsRIGHT(str, n)  last n charsSUBSTRING(str, start, len)  slice (MySQL 1-based)SELECT LEFT('plus_user', 3);
SELECT LEFT('plus_user', 3);
Output: plu
SELECT RIGHT('plus_user', 4);
SELECT RIGHT('plus_user', 4);
Output: user
SELECT SUBSTRING('plus_user', 3);
SELECT SUBSTRING('plus_user' FROM 3);
Output: us_user (from 3rd char onward)
SELECT SUBSTRING('plus_user', 3, 2);
SELECT SUBSTRING('plus_user' FROM 3 FOR 2);
Output: us
LOCATE(substr, str)  MySQLINSTR(str, substr)  MySQL alternativePOSITION(substr IN str) / STRPOS(str, substr)  PostgreSQL1-based index; returns 0 if not found.
SELECT LOCATE('@', 'Raj@gmail.com');
SELECT POSITION('@' IN 'Raj@gmail.com');
Output: 4
SELECT INSTR('Raj@gmail.com', '@');
SELECT STRPOS('Raj@gmail.com', '@');
Output: 4
SELECT INSTR('Raj@gmail.com', '7');
SELECT STRPOS('Raj@gmail.com', '7');
Output: 0 (not found)
Replaces all occurrences of a substring.
SELECT REPLACE('a_b_c', '_', '-');
SELECT REPLACE('a_b_c', '_', '-');
Output: a-b-c
% = any length (0+)_ = exactly one characterSELECT 'plus_user' LIKE 'plus%';
SELECT 'plus_user' LIKE 'plus%';
Output: 1 (true)
SELECT 'plus1user' LIKE 'plus_us_r';
SELECT 'plus1user' LIKE 'plus_us_r';
Output: 1  _ matches 1
SELECT 'plus1user' LIKE 'plus\_user';
SELECT 'plus1user' LIKE 'plus\_user';
Output: 0  escaped \_ is literal _, not wildcard.
Note: Escaping \_/\% depends on SQL mode.
ai=accent-insensitive, as=accent-sensitive, ci=case-insensitive, cs=case-sensitive
utf8mb4_0900_ai_ci → Apple=apple, cafe=café (both equal)
CONCAT(a,b) → NULL if any NULL; CONCAT_WS(sep,a,b) → skips NULL
LOWER/UPPER → case; TRIM/LTRIM/RTRIM → spaces both/left/right
LENGTH → bytes (MySQL), CHAR_LENGTH → chars; é=2 bytes/1 char, 😀=4/1
LEFT/RIGHT(n), SUBSTRING(str, start [,len]) 1-based
LOCATE/INSTR → 1-based, 0 if not found; pgsql: POSITION / STRPOS
REPLACE('a_b_c','_','-') → a-b-c; LIKE %/_ with \_ for literal
Primary sources: MySQL String Functions · Postgres String Functions · MySQL Collation. Ask me anything that's unclear.
Questions? Ask your agent  you can follow up on any concept, quiz answer, or how this applies to a specific query. Review the SQL glossary or syntax quick reference for a compressed overview.