Home / Functions / Extracting and Cleaning Text: LEFT, MID, SEARCH, SUBSTITUTE, CHAR and TEXTSPLIT

Extracting and Cleaning Text: LEFT, MID, SEARCH, SUBSTITUTE, CHAR and TEXTSPLIT

Data OrganizationAnalytics9 min readMicrosoft 365 for Windows

The problem

Systems love codes. A product code carries the market, the therapeutic area and the strength. A study reference carries a country and a sequence number. A dossier file name carries a product and a date. All that information is there, but locked inside a single cell, and the question you need to answer (sales by area, count by market, everything above a strength) needs it in a column of its own.

The other half of the problem is dirt. Data exported from an ERP, copied from a PDF or pasted from a web page arrives with extra spaces, line breaks inside cells and curly quotes that look right but do not match anything. A lookup fails and nobody can see why, because the two cells look identical.

Both problems have the same answer: Excel's text functions, which can cut a string at a position, find a delimiter, replace a piece, or split everything in one go. This article uses the companion file F09_Extracting_Cleaning_Text.xlsx.

How Excel sees text

A text is a sequence of characters, and every character has a position, starting at 1. Spaces, hyphens and invisible characters all count. Almost every function below works with those positions: it asks for the first N characters, for N characters starting at position P, or for the position where something is found.

One consequence catches beginners. The functions return text, even when the characters are digits. The strength 020 cut out of a code is the text 020, not the number 20, and it will not add up or sort as a number. VALUE converts it.

Cutting at a fixed position

When every code has the same structure, three functions are enough. LEFT takes characters from the start, RIGHT from the end, MID from any position:

=LEFT(text, [num_chars])
=RIGHT(text, [num_chars])
=MID(text, start_num, num_chars)

The Fixed Position sheet has ten codes such as UKCAR020: two letters for the market, three for the area (CAR, ONC, HEM, NEU) and three digits for the strength. The positions are kept in cells I2:I5, so that the formulas contain no numbers, and for UKCAR020 they read:

=LEFT(A2,$I$2)       returns UK
=MID(A2,$I$3,$I$4)   returns CAR
=RIGHT(A2,$I$5)      returns 020
=VALUE(D2)           returns 20

LEN, which returns the number of characters, is a useful companion: a column of =LEN(A2) immediately shows the code that is one character too long or too short.

A trap with dates

LEFT, MID and RIGHT work on what the cell contains, not on what it shows. A real date is a serial number, so applying LEFT to a date reads the digits of that number. In the Date Trap sheet, A2 holds 13-May-25, which is the serial number 45790. =LEFT(A2,2) returns 45, not 13. To read the text you see, convert it first:

=LEFT(TEXT(A2,"dd-mmm-yy"),2)

which returns 13.

Cutting at a variable position

Fixed positions are a luxury. In the Variable Position sheet the strings are brand and dose joined by a hyphen, and the lengths vary: Velorin-150, Cardexa-75, Oncavir-1200, Hemaxa-5. Here the information is identified by a delimiter, so the first step is to find its position. SEARCH does that:

=SEARCH(find_text, within_text, [start_num])

With the delimiter stored in H2, the position for the first row is =SEARCH($H$2,A2), which returns 8. The brand is everything before that position and the dose everything after it:

=LEFT(A2,B2-1)               returns Velorin
=MID(A2,B2+1,LEN(A2))        returns 150

The length argument of MID deserves a note. We do not need to know how many characters the dose has: asking for as many as the whole string contains simply returns everything to the end. LEN(A2) is a convenient way to say "all the rest".

SEARCH or FIND

Excel has two functions for finding text, and they differ in one respect. SEARCH ignores case and accepts the wildcards ? and , FIND is case-sensitive and does not. Both return #VALUE! when the text is not found. Rows 10 and 11 show it. The text Net price list - VELORIN 20 mg* searched for velorin gives 18 with SEARCH, and FIND, wrapped in IFERROR, reports "Not found". Searched for VELORIN, both return 18.

=IFERROR(FIND(B10,A10),"Not found")

Use FIND when case carries meaning, for example when a code in capitals must be told apart from a word, and SEARCH in all other cases.

Two delimiters

A string with two delimiters, such as UK|Cardiology|020, needs the delimiter to be found twice. The trick is the start_num argument: search again starting just after the first hit. In the Two Delimiters sheet the pipe sits in P2:

=SEARCH($P$2,A2)                      returns 3
=SEARCH($P$2,A2,B2+1)                 returns 14
=MID(A2,B2+1,C2-B2-1)                 returns Cardiology
=MID(A2,C2+1,LEN(A2))                 returns 020

The middle part starts one position after the first pipe, and its length is the distance between the two pipes minus one.

SEARCH finds the hyphen wherever it falls; below, FIND misses "velorin" because it reads capitals as different letters.
SEARCH finds the hyphen wherever it falls; below, FIND misses "velorin" because it reads capitals as different letters.

The modern way: TEXTBEFORE, TEXTAFTER and TEXTSPLIT

The three functions below are available in Microsoft 365 and Excel 2024. They do the same job without counting positions. TEXTBEFORE returns what precedes a delimiter, TEXTAFTER what follows it, and TEXTSPLIT cuts the whole string at every delimiter and spills the pieces into neighboring cells:

=TEXTBEFORE(text, delimiter, [instance_num], [match_mode], [match_end], [if_not_found])
=TEXTAFTER(text, delimiter, [instance_num], [match_mode], [match_end], [if_not_found])
=TEXTSPLIT(text, col_delimiter, [row_delimiter], [ignore_empty], [match_mode], [pad_with])

In the same sheet, columns G:I and K:M reproduce the results above:

=TEXTBEFORE(A2,$P$2)                           returns UK
=TEXTBEFORE(TEXTAFTER(A2,$P$2),$P$2)           returns Cardiology
=TEXTAFTER(A2,$P$2,2)                          returns 020
=TEXTSPLIT(A2,$P$2)                            returns UK, Cardiology, 020 in three cells

Three details are useful. A negative instance_num counts from the end of the text, so TEXTAFTER(A2,$P$2,-1) always returns the last piece, however many delimiters there are. By default the match is case-sensitive, and setting match_mode to 1 makes it case-insensitive. And when the delimiter is not found, TEXTBEFORE and TEXTAFTER return #N/A unless you supply if_not_found.

The cells that TEXTSPLIT fills must be free, otherwise Excel returns #SPILL!. If your file must also work in Excel 2021 or earlier, stay with the SEARCH and MID pattern: it works everywhere.

TEXTSPLIT in K2 spills the three parts of each code across K:M, one row per code.
TEXTSPLIT in K2 spills the three parts of each code across K:M, one row per code.

Cleaning imported text

The most common dirt has four forms, and the Cleaning sheet contains one example of each in A2:A6: extra spaces, a line break inside the cell, a non-breaking space and curly quotes. A single formula in column C cleans them all. First the tools.

TRIM removes spaces at the start and the end of a text and reduces repeated spaces inside it to one. The first raw text, " Velorin 20 mg ", has 19 characters; TRIM brings it to 13.

SUBSTITUTE replaces text wherever it appears, whether once or everywhere:

=SUBSTITUTE(text, old_text, new_text, [instance_num])

Without instance_num every occurrence is replaced; with it only that one. It is case-sensitive. In the SUBSTITUTE sheet, with the old and new text in F2:F3, =SUBSTITUTE(A2,$F$2,$F$3) corrects Hemaxa 5mg to Hemaxa 5 mg, and for UK-CAR-020 the formula =SUBSTITUTE(A8,$F$4,$F$5,$F$6) replaces only the second hyphen and returns UK-CAR/020, while the same formula without the last argument returns UK/CAR/020. Do not confuse it with REPLACE, which works by position and does not look at the content.

The difficulty with invisible characters is knowing what to replace. Two functions help. UNICODE returns the code number of the first character of a text, and CHAR or UNICHAR does the reverse:

=UNICODE(LEFT(A5,1))     returns 8220, the opening curly quote
=CHAR(34)                returns the straight quote "
=CHAR(10)                returns a line feed

Here are the characters that cause most of the damage:

Code Character Typical source
10 Line feed Alt+Enter inside a cell, or a text export
34 Straight quote What formulas and databases expect
160 Non-breaking space Web pages and PDFs; TRIM does not remove it
8220, 8221 Curly quotes Word processors and e-mail

The cleaning formula, with the codes held in cells H2:H6 so that the formula has no hard-coded numbers, nests four replacements and finishes with TRIM:

=TRIM(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(A2,UNICHAR($H$2),CHAR($H$4)),UNICHAR($H$3),CHAR($H$4)),UNICHAR($H$5)," "),CHAR($H$6)," "))

Reading from the inside out: opening curly quotes become straight quotes, closing curly quotes the same, the non-breaking space becomes a normal space, the line feed becomes a space, and TRIM tidies the spaces that result. All five raw texts come out clean, so that Cardexa, line break, 40 mg becomes Cardexa 40 mg and "Hemaxa" 5 mg keeps its quotes, now straight.

Notice the line feed is replaced by a space and not simply deleted. CLEAN, which removes non-printing characters, would delete it and join the words into Cardexa40 mg.

The cleaning formula in C2 turns a 19-character raw text into a clean one of 13 characters.
The cleaning formula in C2 turns a 19-character raw text into a clean one of 13 characters.

CODE, CHAR and the character set

CODE returns the number of the first character of a text, so =CODE("@") returns 64, and CHAR returns the character for a number from 1 to 255. A word of caution: those numbers are not really ASCII. The original ASCII table, which dates back to the 1960s, defines only 128 characters. Codes above 127 in Excel for Windows come from the system's character set, so =CHAR(177) returns the plus-minus sign on a Windows computer set to English, but another system could return something else. UNICHAR and UNICODE use the universal Unicode numbers and are safe everywhere: =UNICHAR(8364) returns the euro sign, which CHAR cannot produce.

A final use of CHAR(10) is writing a line break inside a formula: =A10&CHAR(C10)&B10 joins two texts on two lines, as long as Wrap Text is on (Ctrl+1, tab Alignment).

Common mistakes

Companion file

Illustrative data only. Not a decision tool.

For further reading