I know that I can create an index that uses concatenation as follows:
CREATE INDEX idx_table1_field1_field2 ON table1((field1 || field2));
But this is not quite what I need. I will try to explain exactly what kind of index I need.
Let's assume there is a table (let's call it table1
) that contains two fields (field1
and field2
) of the character data type, each with a length of 15.
+----------------+----------------+
| field1 | field2 |
| character (15) | character (15) |
+----------------+----------------+
| James | Black |
+----------------+----------------+
| Michael | White |
+----------------+----------------+
| David | Silver |
+----------------+----------------+
Now if I execute the following SQL query:
SELECT field1 || field2, CONCAT(field1, field2) FROM table1
I get the following result:
+------------------+---------------------------------+
| ?column? | concat |
| text | text |
+------------------+---------------------------------+
| JamesBlack | James Black |
+------------------+---------------------------------+
| MichaelWhite | Michael White |
+------------------+---------------------------------+
| DavidSilver | David Silver |
+------------------+---------------------------------+
If I use two vertical bars (||), the server does not check that the name James has 5 characters and does not add 10 spaces to the field length (15).
If I use the CONCAT
function, I see that the field length is not ignored and the server added 10 spaces to the name James.
I desire to create an index that will take the field length into account (how CONCAT
does this).
To create the desired index, I tried to execute the following SQL query:
CREATE INDEX idx_table1_field1_field2 ON table1(CONCAT(field1, field2));
But I got the following error: ERROR: functions in index expression must be marked IMMUTABLE
Is there another way to create the such index?
I know that I can create an index that uses concatenation as follows:
CREATE INDEX idx_table1_field1_field2 ON table1((field1 || field2));
But this is not quite what I need. I will try to explain exactly what kind of index I need.
Let's assume there is a table (let's call it table1
) that contains two fields (field1
and field2
) of the character data type, each with a length of 15.
+----------------+----------------+
| field1 | field2 |
| character (15) | character (15) |
+----------------+----------------+
| James | Black |
+----------------+----------------+
| Michael | White |
+----------------+----------------+
| David | Silver |
+----------------+----------------+
Now if I execute the following SQL query:
SELECT field1 || field2, CONCAT(field1, field2) FROM table1
I get the following result:
+------------------+---------------------------------+
| ?column? | concat |
| text | text |
+------------------+---------------------------------+
| JamesBlack | James Black |
+------------------+---------------------------------+
| MichaelWhite | Michael White |
+------------------+---------------------------------+
| DavidSilver | David Silver |
+------------------+---------------------------------+
If I use two vertical bars (||), the server does not check that the name James has 5 characters and does not add 10 spaces to the field length (15).
If I use the CONCAT
function, I see that the field length is not ignored and the server added 10 spaces to the name James.
I desire to create an index that will take the field length into account (how CONCAT
does this).
To create the desired index, I tried to execute the following SQL query:
CREATE INDEX idx_table1_field1_field2 ON table1(CONCAT(field1, field2));
But I got the following error: ERROR: functions in index expression must be marked IMMUTABLE
Is there another way to create the such index?
Share Improve this question edited Feb 6 at 6:15 Ivan Yuzafatau asked Feb 5 at 22:11 Ivan YuzafatauIvan Yuzafatau 6085 silver badges13 bronze badges 5 |2 Answers
Reset to default 3You can use RPAD() in this, to fill it up with whitespace (the default):
CREATE TABLE bar(field1 char(15), field2 char(15));
CREATE INDEX ON bar ((rpad(field1,15) || rpad(field2, 15)));
INSERT INTO bar(field1, field2) VALUES ('a', 'b');
EXPLAIN(ANALYSE , VERBOSE , BUFFERS , SETTINGS )
SELECT * FROM bar WHERE (rpad(field1,15) || rpad(field2, 15)) = 'a b ';
It's important that you write the WHERE condition just like the index definition, otherwise the index can't be used.
Unless you definitely want fixed length fields, and your example uses names so you probably don't, don't use the character
type. Use text
, or if you have a specific reason to limit your data length, varchar
.
From the PostgreSQL docs...
There is no performance difference among these three types, apart from increased storage space when using the blank-padded type [character], and a few extra CPU cycles to check the length when storing into a length-constrained column. While character(n) has performance advantages in some other database systems, there is no such advantage in PostgreSQL; in fact character(n) is usually the slowest of the three because of its additional storage costs. In most situations text or character varying [varchar] should be used instead.
The padding isn't a property of the data, it's formatting. If you really want that padding, use text
and add it yourself with rpad
.
create index idx_format on table1((rpad(field1, 15) || rpad(field2, 15)));
Or try to make your own immutable character concat.
To explain ||
vs concat
we reference 9.4 String Functions And Operators.
Except where noted, these functions and operators are declared to accept and return type text. They will interchangeably accept character varying arguments. Values of type character will be converted to text before the function or operator is applied, resulting in stripping any trailing spaces in the character value.
||
is defined to take text and returned text, text || text → text
. It will cast your char
to text
losing the trailing whitespace. concat
(which is non-standard) is defined to take any type and return text, concat ( val1 "any" [, val2 "any" [, ...] ] ) → text
.
character(n)
in the first place? 15 is the field length if you choose character. If that's not what you want, usevarchar
like everyone else. – Richard Huxton Commented Feb 5 at 23:05character
is a red flag. You almost always wanttext
. See postgresql.org/docs/current/datatype-character.html – Schwern Commented Feb 5 at 23:25concat
. Or don't usechar
. If you really need the padding, pad it yourself. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. – Schwern Commented Feb 5 at 23:34