【问题标题】:ERROR: functions in index expression must be marked IMMUTABLE in Postgres错误:索引表达式中的函数必须在 Postgres 中标记为 IMMUTABLE
【发布时间】:2011-05-12 03:35:54
【问题描述】:

我想创建一个多列表达式索引,但是当我创建索引时,输出如下信息:

--detail message 
wapgrowth=> create index CONCURRENTLY idx_test on  tmp_table using btree (skyid, to_char(create_time, 'YYYY-MM-DD'), actiontype );
ERROR:  functions in index expression must be marked IMMUTABLE


--table ddl
wapgrowth=> \d tmp_table
               Table "wapgrowth.tmp_table"
   Column    |            Type             |   Modifiers   
-------------+-----------------------------+---------------
 id          | integer                     | not null
 actiontype  | character varying(20)       | 
 apptype     | character varying(20)       | 
 score       | integer                     | 
 create_time | timestamp without time zone | default now()
 skyid       | integer                     | 
Indexes:

【问题讨论】:

  • 如果您面对的是 char 字段,只需将 char 字段名称放在引号中,例如“field”

标签: sql postgresql


【解决方案1】:

根据黑客邮件列表中的这个帖子:

http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg86725.html

这是预期行为,因为 to_char 取决于 LC_MESSAGES 设置

在您的情况下,这显然没有意义,因为您使用的格式永远不会依赖于语言环境,因此如果您确实需要在索引中使用文本表示,您可以创建自己的 to_char() 函数并将其标记为不可变:

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;

如果您必须将其用作索引中的文本(并且不能像 Sam 建议的那样使用转换为日期),您将需要创建自己的格式化函数,您可以将其标记为不可变。然后可以在索引中使用它。

但要使 Postgres 使用索引,您还需要在 SQL 语句中调用 my_to_char()。使用内置to_char()时识别不出来

但我确实认为 Sam 的建议在索引中使用直接日期可能会更好

【讨论】:

  • 感谢您的建议,将函数“to_char”更改为“IMMUTABLE”后,现在可以了,但是这样做有什么风险吗?
  • skytf=# alter function to_char(timestamp with time zone, text) IMMUTABLE;改变功能
【解决方案2】:

这解释得更详细:

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

基本上时区取决于服务器,因此如果有人更改它,结果可能会改变。但是如果你锁定时区:

date(timezone('UTC', create_time)

它会起作用的。

【讨论】:

  • 最佳答案,因为无论时间戳设置如何,它都可以工作
  • 太好了,包装我的日期/时间列 - timezone('UTC', date_time_column) - 就是我需要做的(即无需声明不可变函数)。谢谢!
【解决方案3】:

to_char 的 timestamp-without-timezone 不是不可变函数,因为转换取决于您的本地时区设置。这意味着索引不能移植到不同时区的另一台计算机上,Postgres 也不允许这样做。我认为如果您将 create_time 声明为带时区的时间,问题就会消失。

【讨论】:

  • 我已经尝试在带有时区的时间戳列上创建表达式索引,但它仍然不起作用!
【解决方案4】:

不要使用 to_char 将时间戳格式化为 YYYY-MM-DD,而是尝试将时间戳转换为日期类型,这将产生相同的效果:

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

【讨论】:

  • 感谢您的建议,我尝试了该方法,但也没有用。
  • 您使用的是什么版本的 postgres?它使用 9.0.3 版对我有用。
  • skytf=> 使用 btree (skyid, cast(create_time as date)) 在 test_39 上同时创建索引 idx_test;错误:索引表达式中的函数必须标记为 IMMUTABLE
  • 我无法解释为什么将时间戳转换为日期对您不起作用,但请尝试使用 date_trunc() 函数。它是不可变的(对于没有时区的时间戳)。 create index CONCURRENTLY idx_test2 on tmp_table using btree (skyid, date_trunc('day', create_time), actiontype );
  • skytf=# alter function to_char(timestamp with time zone, text) IMMUTABLE;改变功能
【解决方案5】:

在一天结束时,您似乎正在尝试索引 create_time 的“YYYY-MM-DD”表示。为什么不只是 INDEX create_time?问题是 to_char() 是可变的,因为语言环境变量可能会改变,这会改变 to_char() 的输出。

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

如果您可以控制架构,则可以添加一个新列(例如 create_date TEXT)和 INDEX,然后设置一个处理插入的触发器。事实上,如果您创建了一种将 TIMESTAMP WITHOUT TIME ZONE 转换为 TEXT 的方法,您可以以恒定的方式对其进行索引。 a_horse_with_no_name 的建议很好,因为我认为您并不关心语言环境。

您遇到的问题是所有 DATE 和 TIME 处理代码都遵循区域设置,这不是 IMMUTABLE,因此您不能轻松地 INDEX 依赖于这些数据类型的函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-01-23
    • 2023-03-29
    • 2019-07-11
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    • 2012-11-17
    • 1970-01-01
    相关资源
    最近更新 更多