Module 2, Session 9. UPPER, LOWER, INITCAP, TRIM, LENGTH, SUBSTRING, SPLIT_PART, POSITION, STRPOS, and safe concatenation with CONCAT and CONCAT_WS.
Open in Present mode · 400 practice questions · Live class schedule · Try the playground
Jo class mein bola, yahan nukte mein likha hai.
What I said in class, written here as key points.
Scalar functions clean messy names, slice emails, extract domain names, and join text safely without NULL propagation bugs. All in PostgreSQL 18, on RetailMart.
Four pillars: from messy text typed every possible way to clean, consistent values
UPPER, LOWER, INITCAP
TRIM, LTRIM, RTRIM
LENGTH vs OCTET_LENGTH
SUBSTRING FROM x FOR y
LEFT() & RIGHT()
POSITION() & STRPOS()
SPLIT_PART()
|| trap: one NULL, whole name NULL
CONCAT() & CONCAT_WS()
REPLACE()
customers.customers has 50,000 emails, and 8,369 of them are Gmail. Finding that, pulling out a domain, or printing a clean full name are all text jobs. Every report that shows a name, an email or a phone number needs today’s functions.From choosing rows to cleaning what is inside them
WHERE, IS NULL, LIKE, IN). Session 8 put them in order and cut pages (ORDER BY, LIMIT, OFFSET, keyset). But the values inside the cells never changed.
WHERE decides which rows survive, ORDER BY decides their order, and today’s functions decide what each value looks like.first_name || ' ' || last_name: one NULL part makes the whole name NULL, and the print tool wrote “NULL”.
SELECT CONCAT_WS(' ', INITCAP(TRIM(first_name)), INITCAP(TRIM(last_name))) AS invite_name FROM customers.customers ORDER BY customer_id LIMIT 10; -- Meera Naidu, Lakshmi Singh, Zoya Lal ...
TRIM, then INITCAP), and join with CONCAT_WS, which skips a NULL part instead of turning the whole name into NULL.Every failure in the story corresponds directly to an essential SQL scalar string tool.
INITCAP(), UPPER(), LOWER(), and TRIM().CONCAT() & CONCAT_WS().SPLIT_PART(), SUBSTRING(), and POSITION().Test per-row text transformation functions live on dirty customer input strings.
Make messy text consistent: same case, no extra spaces.
WHERE LOWER(email) = 'user@gmail.com').'aarav-mehta' becomes 'Aarav-Mehta'. Good for people's names and cities.LTRIM() for leading only, RTRIM() for trailing only, or BTRIM(str, 'xyz') to strip custom character sets.-- Messy names, the way real sign-up forms collect them SELECT raw_name, UPPER(raw_name) AS upper_name, INITCAP(TRIM(raw_name)) AS clean_name, LENGTH(raw_name) AS raw_len, LENGTH(TRIM(raw_name)) AS clean_len FROM (VALUES (' rAhUl sHaRmA '), ('PRIYA nair'), (' aarav-mehta')) AS t(raw_name);
Observe how PostgreSQL extracts slices from text with 1-based indexing.
Cut a piece out of text by position (SUBSTRING, LEFT, RIGHT) or by a separator (SPLIT_PART).
SUBSTRING(str FROM start FOR len) extracts a slice of given length starting at 1-based position. Length is optional (defaults to end of string).SPLIT_PART(string, delimiter, field) splits on a delimiter character and returns the Nth 1-based field. If field doesn't exist, returns empty string ''.POSITION(substr IN str) or STRPOS(str, substr) returns the 1-based index where the substring first occurs. Returns 0 if not found.-- Product names start with the brand: 'Samsung Animi Max' SELECT product_id, product_name, SPLIT_PART(product_name, ' ', 1) AS brand_word, LEFT(product_name, 3) AS first_3, POSITION(' ' IN product_name) AS first_space, SUBSTRING(product_name FROM 1 FOR POSITION(' ' IN product_name) - 1) AS brand_by_substring FROM products.products LIMIT 10;
See why the || operator is lethal on nullable columns and how CONCAT rescues data.
Join text that may have NULLs in it, and swap one piece of text for another.
CONCAT(val1, val2, ...) turns every part into text and treats NULL as empty, so one missing part never wipes out the result. Watch the double space it can leave.CONCAT_WS(separator, v1, v2, ...) puts the first argument between the parts and skips NULL parts completely: no double spaces, no ', ,'.REPLACE(string, from_substr, to_substr) replaces all occurrences of a target string. Case-sensitive; returns unchanged string if not found.-- Rahul has no middle name (NULL). Compare the three ways to join. SELECT first_name, middle_name, first_name || ' ' || middle_name || ' ' || last_name AS pipe_result, CONCAT(first_name, ' ', middle_name, ' ', last_name) AS concat_result, CONCAT_WS(' ', first_name, middle_name, last_name) AS concat_ws_result, REPLACE(phone, '-', '') AS phone_digits FROM (VALUES ('Rahul', NULL, 'Sharma', '98765-43210'), ('Priya', 'K', 'Nair', '91234-56789')) AS t(first_name, middle_name, last_name, phone);
Two mistakes and two habits that save you from silent wrong answers.
SUBSTRING(code FROM 0 FOR 2)FROM 0 FOR 2 returns only 1 character.1 for the first character.
first_name || ' ' || middle_name || ' ' || last_nameCONCAT_WS(' ', first_name, middle_name, last_name).
LENGTH('हिंदी') returns 5 (characters), while OCTET_LENGTH('हिंदी') returns 15 (bytes in UTF-8). Use LENGTH() to count characters. And LENGTH(NULL) is NULL, not 0: WHERE LENGTH(phone) = 10 silently skips rows with no phone.
SPLIT_PART returns an empty string '', not NULL. Wrap with NULLIF(SPLIT_PART(...), '') so downstream logic works properly.
Audit the phone numbers of all 50,000 RetailMart customers, and mask them before sharing.
-- Phones that are not exactly 10 digits, masked before anyone sees them SELECT customer_id, INITCAP(first_name) || ' ' || INITCAP(last_name) AS full_name, LENGTH(phone) AS digits, 'XXXXXX' || RIGHT(phone, 4) AS masked_phone, SPLIT_PART(email, '@', 2) AS email_domain FROM customers.customers WHERE phone IS NOT NULL AND LENGTH(phone) <> 10 LIMIT 12;
Run each one, then change it for the "your turn" line. All on real RetailMart tables.
SELECT UPPER(last_name) || ' ' || UPPER(LEFT(first_name, 1)) || '.' AS short_name FROM customers.customers LIMIT 10;
Your turn: Make it 'M. Naidu': the initial, a dot, a space, then INITCAP(last_name).
SELECT email, SPLIT_PART(email, '@', 1) AS username, LENGTH(SPLIT_PART(email, '@', 1)) AS len FROM customers.customers WHERE LENGTH(SPLIT_PART(email, '@', 1)) > 15 ORDER BY len DESC LIMIT 10;
Your turn: Find the 10 shortest usernames instead. Which ORDER BY do you need?
SELECT product_name, LENGTH(product_name) - LENGTH(REPLACE(product_name, ' ', '')) + 1 AS words FROM products.products ORDER BY words DESC LIMIT 10;
Your turn: Show only the names with exactly 3 words.
SELECT store_name, UPPER(LEFT(store_name, 1)) || UPPER(LEFT(SPLIT_PART(store_name, ' ', 2), 1)) AS initials FROM stores.stores LIMIT 10;
Your turn: Show 'RETAILMART GWALIOR (Gwalior)': UPPER of the name, then the city in brackets.
Core rules and interview takeaways for String Functions.
INITCAP(TRIM(col)) first prunes outside spaces, then formats words. Always clean before you format.SPLIT_PART. It is faster to write, easier to read, and prevents off-by-one errors.|| when joining nullable columns without wrapping in COALESCE or replacing with CONCAT_WS().Five core string transformation questions interviewers ask, with production-grade answers.
UPPER('hello') → 'HELLO', so 100 rows in means 100 rows out. Aggregate functions read many rows and return a single summary value: COUNT(*) on customers.customers → 50,000. Scalar functions work anywhere in SELECT, WHERE and ORDER BY; aggregates cannot go in WHERE, and once a plain column sits next to one you need GROUP BY (Sessions 13 and 14).Click any flashcard to flip and test your memory on PostgreSQL string behavior.
Universal principles for robust text transformation and production data sanitization.
Complete PostgreSQL string function index with interactive ▶ Run links.
SELECT UPPER('retailmart'), LOWER('RETAILMART');
SELECT INITCAP('rahul kumar sharma');
SELECT TRIM(' clean me ');
SELECT LENGTH('RetailMart');
SELECT SUBSTRING('PostgreSQL' FROM 1 FOR 4);
SELECT SPLIT_PART('user@domain.com', '@', 2);
SELECT POSITION('@' IN 'user@domain.com');
SELECT CONCAT_WS(', ', 'A', NULL, 'B');
SELECT REPLACE('v1.0', 'v1', 'v2');
Only today's string functions, no joins and no GROUP BY. Write each one in the playground, then check your row count against the one given.
customers.customers, format names as 'Sharma, Rahul' (last_name, first_name) using INITCAP and ||. Show 20 customers.'XXXXXX' + last 4 digits using RIGHT(). Filter out NULL phone numbers anyway: RetailMart has none, real tables do (50,000 rows).gmail.com users using LIKE (8,369 customers).products.products with names longer than 30 characters (11 products): display product_name, its LENGTH, and its first word using SPLIT_PART.Track your progress across the 59-session SQL course.
Coming next in Module 2: Scalar Functions & Temporal Logic.