To mysql combine columns into string like an address or a full name, use CONCAT (explicit separators) or CONCAT_WS (one shared separator that’s repeated, NULL-aware). Both are built into MySQL and MariaDB — no PHP-side post-processing needed. This guide covers both, when to use which, the NULL behavior that catches people out, and when to store the concatenated value for indexed filtering.
Last verified: 2026-04-23 on MySQL 8.0 and MariaDB 10.11. Originally published 2022-11-06, rewritten and updated 2026-04-23.
TL;DR
-- CONCAT_WS — one shared separator, skips NULLs
SELECT
id,
CONCAT_WS(', ', house_no, street, city, country) AS address
FROM user_details;
-- CONCAT — explicit separator per join, NULL-sensitive
SELECT
id,
CONCAT(first_name, ' ', last_name) AS full_name
FROM users;
CONCAT — explicit separators
SELECT
id,
CONCAT(house_no, ', ', street, ', ', city, ', ', country) AS address
FROM user_details;
CONCAT joins every argument in order. You write each separator inline (', '), giving you full control over what sits between each column. Useful when the separators differ: CONCAT(first_name, ' ', last_name, ', ', title).
Caveat: if any argument is NULL, the whole result is NULL. For nullable columns you need to COALESCE each one: CONCAT(COALESCE(col1, ''), ', ', COALESCE(col2, '')). Quickly tedious.
CONCAT_WS — shared separator, NULL-aware
SELECT
id,
CONCAT_WS(', ', house_no, street, city, country) AS address
FROM user_details;
CONCAT_WS(separator, arg1, arg2, ...) — “concatenate with separator.” The separator is the first argument; subsequent arguments are joined with it in between. Two advantages over CONCAT:
- No need to repeat the separator — write it once.
- NULL values are skipped automatically. A row with
house_no = NULLstill produces a clean"Main St, Dhaka, BD"instead ofNULL.
For addresses, names, breadcrumbs, tag lists — anywhere you’re joining with the same separator — CONCAT_WS is the one to reach for.

Inside Laravel / WordPress queries
Reaching for CONCAT inside Eloquent or $wpdb runs into the same gotcha: the ORM’s select() escapes identifiers, so plain concatenation strings get treated as column names. Wrap in DB::raw() or use selectRaw():
// Laravel
use Illuminate\Support\Facades\DB;
User::selectRaw("id, CONCAT_WS(', ', house_no, street, city, country) AS address")
->where('user_id', $userId)
->get();
See the full walkthrough in the “Unknown column ‘CONCAT'” fix in Laravel.
Indexing and performance
Concatenation is computed at query time, so WHERE CONCAT_WS(',', street, city) LIKE '%Dhaka%' forces a full table scan — the database has to build the string for every row before comparing. Two fixes:
- Filter on the source columns instead —
WHERE city = 'Dhaka'uses an index oncity;WHERE CONCAT_WS(...) LIKE '%Dhaka%'does not. - Store and index — add a
VARCHARcolumn (or a MySQL 8.0 generated column), populate with the concatenation, index it. Now the lookup is O(log n).
-- Generated column (MySQL 8+)
ALTER TABLE user_details
ADD COLUMN full_address VARCHAR(512)
GENERATED ALWAYS AS (CONCAT_WS(', ', house_no, street, city, country))
STORED,
ADD INDEX idx_full_address (full_address);
The value auto-maintains as the source columns change, and WHERE full_address LIKE 'Dhaka%' now uses the index.
Frequently asked questions
CONCAT or CONCAT_WS — which should I use for mysql combine columns into string? CONCAT_WS (‘with separator’) is the right default when you’re joining with the same separator — addresses, names, breadcrumb paths. CONCAT is better when each separator is different (e.g. CONCAT(first, ' ', last, ', ', title)). CONCAT_WS also automatically skips NULL values, where CONCAT returns NULL if any argument is NULL.
CONCAT('A', NULL, 'B') returns NULL — the whole expression. CONCAT_WS(',', 'A', NULL, 'B') returns 'A,B' — NULL values are skipped silently. For address strings where a row might be missing address_2, this is exactly what you want. For cases where you want to substitute a default instead of skipping, wrap with COALESCE: CONCAT_WS(',', col1, COALESCE(col2, 'N/A')).
No — computing a concatenation requires reading every selected row, so filtering by the concatenated value forces a full table scan. If you filter on addresses frequently, store the composed address in a column (or a generated column) and index that. See the generated-column pattern for the stored-computed approach.
CONCAT_WS(CHAR(10), line1, line2, line3) — CHAR(10) is ASCII line feed. For a CRLF pair (Windows-style): CHAR(13, 10). Usable anywhere you’d put a literal separator.
GROUP_CONCAT instead? Different tool for a different problem. GROUP_CONCAT concatenates values across rows in a group — useful for ‘give me a comma-separated list of tags for each post.’ CONCAT/CONCAT_WS works within a single row. For combining columns of one row into one string, stick with CONCAT_WS.
Related guides
- How to Fix “Unknown column ‘CONCAT'” in Laravel — using CONCAT from Eloquent.
- How to Extract Only the Digits from a String in MySQL — sibling string-function pattern.
- How to Remove a Specific String from a Column in MySQL — other string transforms.
- How to Prepare a %LIKE% SQL Statement in WordPress — pairing with LIKE searches.
References
MySQL CONCAT and CONCAT_WS: dev.mysql.com/doc/refman/8.0/en/string-functions.