Understanding PostgreSQL’s REGEXP_INSTR() Function

In PostgreSQL, the regexp_instr() function returns the starting or ending position of the N‘th match of a POSIX regular expression pattern to a string. If there’s no match, it returns zero.

We pass the string and pattern as arguments. The function also accepts some optional arguments that allow us to be specific with how the function works.

Syntax

The syntax goes like this:

regexp_instr ( string text, pattern text [, start integer [, N integer [, endoption integer [, flags text [, subexpr integer ] ] ] ] ] ) 

So the first two arguments are required, and the subsequent ones are optional.

Example

Here’s a basic example to demonstrate:

SELECT regexp_instr('A cat called Ash', 'A.');

Result:

1

Here, I checked the starting position of the A character followed by any other character (the dot matches any character).

Here’s what happens if I switch it around so that the dot comes first:

SELECT regexp_instr('A cat called Ash', '.A');

Result:

13

This time it matched the A from Ash. Actually, it matched the A and its preceding character (the dot matches any character). In this case the preceding character is the space character, which is at position 13.

Change the Start Position

We can add a third argument to specify where to start the search within the string:

SELECT regexp_instr('A cat called Ash', 'A.', 2);

Result:

14

Here, I specified that the search should start at position 2, and so this resulted in it missing the first A character. In this case it went straight to the A of Ash, which is at position 14.

Specify the Nth Match

We can add another argument that specifies which match we’re interested in. For example, if there are two matches, we can specify that we’re only interested in the second match. Or if there are ten matches, we could specify the fifth, and so on.

Here’s an example that demonstrates this:

SELECT 
    regexp_instr('A cat called Ash', 'A.', 1, 1) AS "1st",
    regexp_instr('A cat called Ash', 'A.', 1, 2) AS "2nd";

Result:

 1st | 2nd 
-----+-----
1 | 14

The first column returns the position of the first match, and the second column returns the position of the second match.

The Endoption Argument

The next argument we can specify is the endoption argument. If this is omitted or specified as 0, the function returns the position of the first character of the match. Otherwise, this argument must be 1, which results in the function returning the position of the character that follows the match.

Example:

SELECT 
    regexp_instr('A cat called Ash', 'A.', 1, 2) AS "Omitted",
    regexp_instr('A cat called Ash', 'A.', 1, 2, 0) AS "0",
    regexp_instr('A cat called Ash', 'A.', 1, 2, 1) AS "1";

Result:

 Omitted | 0  | 1  
---------+----+----
14 | 14 | 16

Here, I omitted the endoption argument in the first column, I specified 0 in the second column, and 1 in the third. We can see that the first two columns returned the position of the first character of the match, but the third column returned the position of the character that follows the match.

Here it is again, but using a pattern of A.. (i.e. with an additional dot):

SELECT 
    regexp_instr('A cat called Ash', 'A..', 1, 2) AS "Omitted",
    regexp_instr('A cat called Ash', 'A..', 1, 2, 0) AS "0",
    regexp_instr('A cat called Ash', 'A..', 1, 2, 1) AS "1";

Result:

 Omitted | 0  | 1  
---------+----+----
14 | 14 | 17

The first two columns remain the same as in the previous example, but the third column is now 17. That’s because the two dots matched the following two characters after the A character, which is one more character than in the previous example.

Add a Flag

We can also add a flag to change the behaviour of the function. For example, we can specify that the search is case-insensitive by including the i flag:

SELECT regexp_instr('A cat called Ash', 'A.', 1, 2, 0, 'i');

Result:

4

This time the result is 4. That’s because the search matched both uppercase and lowercase characters (i.e. both a and A). It returned the second match because we specified 2 as the fourth argument.

Here’s what happens if we change the fourth argument to 3:

SELECT regexp_instr('A cat called Ash', 'A.', 1, 3, 0, 'i');

Result:

8

The i flag is just one of many flags we can use. The following table contains the flags we can use here:

OptionDescription
bRest of RE is a BRE
cCase-sensitive matching (overrides operator type)
eRest of RE is an ERE
iCase-insensitive matching (overrides operator type)
mHistorical synonym for n
nNewline-sensitive matching
pPartial newline-sensitive matching
qRest of RE is a literal (“quoted”) string, all ordinary characters
sNon-newline-sensitive matching (default)
tTight syntax (default)
wInverse partial newline-sensitive matching
xExpanded syntax

Source: https://www.postgresql.org/docs/current/functions-matching.html#POSIX-METASYNTAX

Specify a Subexpression

If our pattern contains any parenthesised subexpressions, we can specify which one to use by passing an integer as the last argument after the flag:

SELECT 
    regexp_instr('A cat called Ash', 'A(.)(..)(...)', 1, 2, 0, 'i') AS "Omitted",
    regexp_instr('A cat called Ash', 'A(.)(..)(...)', 1, 2, 0, 'i', 0) AS "0",
    regexp_instr('A cat called Ash', 'A(.)(..)(...)', 1, 2, 0, 'i', 1) AS "1",
    regexp_instr('A cat called Ash', 'A(.)(..)(...)', 1, 2, 0, 'i', 2) AS "2",
    regexp_instr('A cat called Ash', 'A(.)(..)(...)', 1, 2, 0, 'i', 3) AS "3";

Result:

 Omitted | 0 | 1 | 2  | 3  
---------+---+---+----+----
8 | 8 | 9 | 10 | 12

When we omit the subexpression argument or we specify 0, the result identifies the position of the whole match regardless of parenthesised subexpressions. Otherwise our integer specifies which subexpression to use.

More About POSIX Regular Expressions in PostgreSQL

See the PostgreSQL documentation for an overview of how POSIX regular expressions work and the various options available when using the regex_instr() function.