ERROR: functions in index expression must be marked IMMUTABLE in Postgres

Solution 1:

According to this thread in the hackers mailing list:

http://www.mail-archive.com/[email protected]/msg86725.html

this is intended behaviour as to_char depends on the LC_MESSAGES setting

In your case this apparently doesn't make sense as the format you are using will never depend on the locale, so if you do need to use the text representation in the index, you can create your own to_char() function and mark it as immutable:

CREATE OR REPLACE FUNCTION my_to_char(some_time timestamp) 
  RETURNS text
AS
$BODY$
    select to_char($1, 'yyyy-mm-dd');
$BODY$
LANGUAGE sql
IMMUTABLE;

If you have to use it as a text in the index (and cannot use the cast to a date as Sam suggested) you will need to create your own formatting function that you can mark as immutable. That can then be used in the index.

But to make Postgres use the index you will need to call my_to_char() in your SQL statements as well. It will not recognize it when you use the built-in to_char()

But I do think Sam's suggestion using a straight date in the index is probably better

Solution 2:

This explains more in detail:

https://www.postgresql.org/message-id/CAKFQuwbcMfesmNkm19mXFLXP14sP5BiPsR1GSkY1suKiM1rukg%40mail.gmail.com

basically the timezone depends on the server, and thus the result might change if somebody changes it. But if you lock the timezone:

date(timezone('UTC', create_time)

it will work.

Solution 3:

to_char of a timestamp-without-timezone is not an immutable function, because the conversion depends on your local time-zone setting. That means the index wouldn't be portable to another computer in a different time zone, and Postgres won't allow it. I think the problem will go away if you declare create_time as a time with timezone.

Solution 4:

Instead of using to_char to format your timestamp as YYYY-MM-DD, try casting your timestamp to a date type, which will have the same effect:

create index CONCURRENTLY idx_test on  tmp_table using btree (skyid, cast(create_time as date), actiontype );

Solution 5:

At the end of the day it looks like you're trying to index a "YYYY-MM-DD" representation of create_time. Why not just INDEX create_time? The problem is to_char() is MUTABLE because the locale environment variable could change, which changes the output of to_char().

http://www.postgresql.org/docs/current/static/charset.html

If you have control over the schema, you could add a new column (e.g. create_date TEXT) and INDEX that, then setup a trigger that handles the inserts. In fact, if you created a way of converting your TIMESTAMP WITHOUT TIME ZONE to TEXT, you could INDEX that in a constant way. a_horse_with_no_name's suggestion was a good one since I don't think you care about locale.

The problem you're running in to is all DATE and TIME handling code obeys locale, which isn't IMMUTABLE, therefore you can't easily INDEX functions that rely on those data types.