Combine Values with CONCAT
Combine values with CONCAT and string functions — PostgreSQL Tutorial.
Focus: combine values with concat and string functions
You've just built a query that returns a list of customers, but the first_name and last_name come back in separate columns, and your boss wants a single, human-readable greeting. Manually concatenating values in your application's code is wasteful and error-prone, especially when you also need to handle blanks, add separators, or clean up formatting. PostgreSQL gives you a toolkit of string functions — led by CONCAT — that lets you combine, transform, and polish text directly in the database, keeping your app layer lean and your output exactly as your users expect.
The problem this lesson solves
Real-world data rarely arrives in the shape you need. You might have a users table with split name fields, a products table with brand and model columns, or a logs table with separate date and message parts. Without a solid way to merge these values, your application code ends up doing the busywork: looping through rows, concatenating strings, and hunting for nulls. That approach is slow, hard to maintain, and easy to get wrong — especially when a NULL sneaks in and silently turns your entire string into null.
PostgreSQL's string functions solve this by moving the transformation into the query layer. With CONCAT and its companions, you can merge columns, add separators, trim whitespace, and even change case — all with a few readable function calls. This lesson shows you how to combine values cleanly, when each function shines, and how to avoid the classic pitfalls that trip up beginners.
Core concept / mental model
Think of a database query as a pipeline: raw rows go in, and the SELECT list determines what comes out. String functions act as transformers along that pipeline, taking one or more inputs and producing a single output string. CONCAT is the simplest of these: it takes two or more arguments and joins them end-to-end, treating NULL as an empty string.
But CONCAT is just the starting point. PostgreSQL's string toolkit includes:
CONCAT_WS— concatenates with a separator between each value, ignoringNULLs.||operator — the older, stricter concatenation method that propagatesNULLs.FORMAT— for building strings with a pattern, similar tosprintf.- Utility functions like
LENGTH,UPPER,LOWER,TRIM, andSUBSTRINGto adjust and inspect the result.
Mental model: imagine you're assembling a sentence from word cards. CONCAT just stacks cards side-by-side; CONCAT_WS slips a small connector (like a hyphen or space) between them; || is like a strict glue that refuses to work if any card is missing; and FORMAT gives you a template with blanks to fill in.
How it works step by step
-
Understand the basic syntax:
CONCAT(value1, value2, ...)— each argument is converted to text,NULLis treated as an empty string, and the results are joined. For example,CONCAT('Hello', ' ', 'World')yields'Hello World'. -
Add separators with CONCAT_WS: When you need a consistent delimiter (a space, comma, slash),
CONCAT_WS(sep, val1, val2, ...)insertssepbetween every argument. Crucially, it skipsNULLvalues entirely, soCONCAT_WS(' ', 'Jane', NULL, 'Doe')returns'Jane Doe'. -
Know when to use
||: The||operator is the raw concatenation workhorse. It's more flexible for non-text types, but if any operand isNULL, the whole result becomesNULL. Use it when you want to fail fast or when performance is paramount (it's slightly faster thanCONCAT). -
Leverage case and trimming functions: Often you need to clean up before you combine. Functions like
UPPER,LOWER,INITCAP, andTRIMlet you standardize text. For instance, to create a title-cased full name from messy inputs, combineINITCAPwithCONCAT_WS. -
Handle locale-aware formatting with FORMAT: The
FORMATfunction accepts a format string with%splaceholders and substitutes arguments. This is ideal for building complex messages like email templates or log lines.
Hands-on walkthrough
Let's start with a sample users table:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
city VARCHAR(50)
);
INSERT INTO users (first_name, last_name, city) VALUES
('John', 'Doe', 'New York'),
('Jane', 'Smith', 'Los Angeles'),
('Mike', NULL, 'Chicago'),
(NULL, 'Brown', 'Houston');
Example 1: Basic CONCAT
SELECT CONCAT(first_name, ' ', last_name) AS full_name
FROM users;
Expected output:
full_name
---------------
John Doe
Jane Smith
Mike
Brown
(4 rows)
Notice how NULL values are treated as empty strings, so Mike and Brown appear without extra spaces — CONCAT skips the blank.
Example 2: Using CONCAT_WS with a separator
SELECT CONCAT_WS(' ', first_name, last_name, city) AS info
FROM users;
Expected output:
info
-------------------------
John Doe New York
Jane Smith Los Angeles
Mike Chicago
Brown Houston
(4 rows)
CONCAT_WS inserts the space only between non-NULL values, so you never get double spaces.
Example 3: The || operator and NULL behavior
-- This returns NULL for NULL-containing rows
SELECT first_name || ' ' || last_name AS full_name
FROM users;
Expected output:
full_name
---------------
John Doe
Jane Smith
(4 rows)
Rows 3 and 4 are NULL because || propagates NULL. If you need those rows to be non-null, use CONCAT instead.
Example 4: Combining with UPPER and TRIM
SELECT CONCAT_WS(' ', UPPER(first_name), UPPER(last_name)) AS shout_name
FROM users
WHERE first_name IS NOT NULL;
Expected output:
shout_name
---------------
JOHN DOE
JANE SMITH
MIKE
(3 rows)
Here we combine UPPER transformation with the flexibility of CONCAT_WS to produce a clean, all-caps name.
Pro tip: If you ever need to concatenate a large number of columns or complex expressions, wrap them in a subquery or use
FORMATfor readability.CONCATandCONCAT_WSare variadic, but keeping your expression readable is more important than squeezing every character of SQL.
Compare options / when to choose what
Different tools serve different situations. Here’s a quick comparison:
| Function/Operator | Treats NULL as empty? |
Separator support | Use case |
|---|---|---|---|
CONCAT |
Yes | No (you supply it) | Simple joins where blank values are acceptable |
CONCAT_WS |
Yes (skips) | Yes (first arg) | Building strings with delimiters like commas or spaces |
|| |
No (propagates NULL) | No | When you need to enforce non-null data or need max speed |
FORMAT |
No (returns error if arg is null) | N/A (pattern based) | Complex templates with mixed literals and values |
UPPER/LOWER |
N/A (returns NULL) | N/A | Transforming case before or after concatenation |
When to choose what:
- Use
CONCATfor quick, human-readable merges where a missing value should just disappear. - Use
CONCAT_WSwhenever you need a separator — it’s the safest way to avoid double separators fromNULL. - Use
||in performance-critical paths or when you wantNULLto signal missing data. - Use
FORMATfor building structured strings like URLs, messages, or JSON snippets.
Troubleshooting & edge cases
The dreaded NULL propagation
If you use || with a NULL and get an unexpected NULL result, the fix is to switch to CONCAT or CONCAT_WS. This is the most common mistake for beginners.
-- Problem
SELECT NULL || 'abc'; -- returns NULL
-- Solution
SELECT CONCAT(NULL, 'abc'); -- returns 'abc'
Double separators
When building a full name with first_name || ' ' || last_name, a missing middle name can leave two spaces. Use CONCAT_WS to avoid this:
SELECT CONCAT_WS(' ', first_name, middle_name, last_name) FROM users;
Number to string conversion
CONCAT automatically converts numbers to text, but if you mix types with ||, be careful — PostgreSQL will convert implicitly, but you might get unexpected results with certain locale formatting. Use CAST or TO_CHAR for explicit control.
-- Works, but uses implicit conversion
SELECT 'Order ' || order_id FROM orders;
-- Better: explicit and flexible
SELECT CONCAT('Order ', TO_CHAR(order_id, 'FM9999')) FROM orders;
Performance considerations
String concatenation in SQL is generally fast, but if you're doing heavy procedural work, consider moving it to a computed column or a view. Indexing a concatenated expression can help with searches:
CREATE INDEX idx_users_full_name ON users ((CONCAT_WS(' ', first_name, last_name)));
Pro tip: For changing case in a locale-aware way, use
COLLATEwithINITCAPto respect the database's locale settings.LOWERandUPPERare safe for most purposes, but Turkish locales can behave unexpectedly.
What you learned & what's next
You now have a solid grasp of combining values with CONCAT and string functions in PostgreSQL. You can tell when to reach for CONCAT versus CONCAT_WS or the || operator, you understand how to handle NULL gracefully, and you know how to add case transformations and formatting to produce clean, user-ready strings. These skills are the foundation for building more complex queries that generate reports, fill in emails, or construct paths.
You also learned how to troubleshoot the classic NULL propagation issue and why CONCAT_WS is your friend when separators are involved. The next lesson in this track will likely dive into pattern matching with LIKE and regex, where you'll use these string skills to search and parse text even more precisely.
Practice recap
Run the sample queries on the users table, then try changing the separator to a comma or adding TRIM to your inputs. As a mini exercise, build a query that outputs a greeting like 'Hello, JOHN DOE!' using CONCAT_WS and UPPER.
Common mistakes
- Using
||when you need NULL-tolerant concatenation — always prefer CONCAT or CONCAT_WS unless you specifically want NULL propagation. - Forgetting that
CONCAT_WSskips NULL values, so with a non-null separator you might get fewer items than expected — check your row counts. - Assuming
CONCATconverts numbers elegantly when mixed with locale-specific text — useTO_CHARfor formatting numbers with commas and decimals. - Not trimming whitespace before concatenation, which leads to double spaces or leading/trailing spaces in output — combine
TRIMwith your concatenation. - Neglecting performance: putting expensive string conversions in a
WHEREclause can prevent index usage, so precompute or use a view/column.
Variations
- Use the
FORMATfunction for building complex strings with placeholders, e.g.,FORMAT('%s lives in %s', name, city). - Alternative too: use
CONCAT_WSwith a comma and space separator for CSV-like output, orARRAY_TO_STRINGwhen concatenating array elements. - For large-scale concatenation in reporting, consider a generated column or a materialized view that precomputes the full name for faster reads.
Real-world use cases
- Combine first and last name into a single display column in your application's user profile query.
- Generate a log line by concatenating timestamp, severity, and message fields with a consistent delimiter for easy parsing.
- Build a full URL or file path in SQL by joining schema and object names with slashes or backslashes, handling nullable parts.
Key takeaways
CONCATtreats NULL as empty,||propagates NULL — choose based on your need.CONCAT_WSis the safest way to insert separators without double separators from NULLs.- Case and trimming functions like
UPPER,LOWER, andTRIMintegrate cleanly with other string functions. FORMATis the underused tool for complex string templates with placeholders.- Watch for implicit type conversions and performance pitfalls when combining values in queries.
- Practice with your own tables — these functions are intuitive and easy to test.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.