遇到在Postgres中的函数中使用now()与'now'不同的问题.
drop table if exists test_date_bug; CREATE TABLE test_date_bug ( id serial NOT NULL, date1 timestamp with time zone NOT NULL DEFAULT current_timestamp, date2 timestamp with time zone NOT NULL DEFAULT 'infinity' ) WITH ( OIDS=FALSE ); drop function if exists test_date_bug_function(id_param bigint); CREATE OR REPLACE FUNCTION test_date_bug_function(id_param bigint) RETURNS void AS $$ BEGIN UPDATE test_date_bug SET date2 = 'now' WHERE id = id_param; END; $$ LANGUAGE 'plpgsql' VOLATILE SECURITY DEFINER SET search_path = public, pg_temp; insert into test_date_bug DEFAULT VALUES; insert into test_date_bug DEFAULT VALUES; insert into test_date_bug DEFAULT VALUES; select 1 from test_date_bug_function(1);
等几秒钟
select 1 from test_date_bug_function(2);
结果:
select * from test_date_bug; id | date1 | date2 ----+-------------------------------+------------------------------- 3 | 2015-12-10 12:42:01.931554-06 | infinity 1 | 2015-12-10 12:42:01.334465-06 | 2015-12-10 12:42:09.491183-06 2 | 2015-12-10 12:42:01.335665-06 | 2015-12-10 12:42:09.491183-06 (3 rows)
我不希望第2行的date2与第1行的date2相同.
更换
UPDATE test_date_bug SET date2 = 'now' WHERE id = id_param;
同
UPDATE test_date_bug SET date2 = now() WHERE id = id_param;
按照我的预期设置新日期:
select * from test_date_bug; id | date1 | date2 ----+-------------------------------+------------------------------- 3 | 2015-12-10 12:43:29.480242-06 | infinity 1 | 2015-12-10 12:43:28.451195-06 | 2015-12-10 12:43:38.496625-06 2 | 2015-12-10 12:43:28.451786-06 | 2015-12-10 12:43:43.447715-06
想法?
这不是一个错误,它是一个功能......这里有两点.
替换'现在'
我们来看看文档(日期/时间函数和运算符):
所有日期/时间数据类型现在也接受特殊文字值以指定当前日期和时间(再次,解释为事务开始时间).因此,以下三个都返回相同的结果:
SELECT CURRENT_TIMESTAMP;
SELECT now();
选择TIMESTAMP'现在'; - 与DEFAULT一起使用不正确
提示:在创建表时,您不希望在指定DEFAULT子句时使用第三种形式.一旦解析了常量,系统现在将转换为时间戳,因此当需要默认值时,将使用表创建的时间!在使用默认值之前,不会评估前两个表单,因为它们是函数调用.因此,它们将提供所需的默认行为到行插入的时间.
因此'now'
在解析时转换为时间戳.
准备好的陈述
好的,但它在功能方面意味着什么?每次调用函数时都很容易证明函数被解释:
t=# create function test() returns timestamp as $$ begin return 'now'; end; $$ language plpgsql; CREATE FUNCTION t=# select test(); test ---------------------------- 2015-12-11 11:14:43.479809 (1 row) t=# select test(); test ---------------------------- 2015-12-11 11:14:47.350266 (1 row)
在此示例中,'now'
行为与您预期的一样.
有什么不同?您的函数使用SQL语句,而test()则不使用.让我们再看一下文档(PL/pgSQL Plan Caching):
当每个表达式和SQL命令首先在函数中执行时,PL/pgSQL解释器会解析并分析命令以创建预准备语句.
在这里(准备声明):
PREPARE创建一个准备好的声明.预准备语句是服务器端对象,可用于优化性能.执行PREPARE语句时,将解析,分析和重写指定的语句.随后发出EXECUTE命令时,将计划并执行准备好的语句.这种分工避免了重复的分析分析工作,同时允许执行计划依赖于提供的特定参数值.
因此'now'
,在解析准备好的语句时将其转换为时间戳.让我们通过在函数之外创建一个预准备语句来证明这一点:
t=# prepare s(integer) as UPDATE test_date_bug SET date2 = 'now' WHERE id = $1; PREPARE t=# execute s(1); UPDATE 1 t=# execute s(2); UPDATE 1 t=# select * from test_date_bug; id | date1 | date2 ----+-------------------------------+------------------------------- 3 | 2015-12-11 11:01:38.491656+03 | infinity 1 | 2015-12-11 11:01:37.91818+03 | 2015-12-11 11:40:44.339623+03 2 | 2015-12-11 11:01:37.931056+03 | 2015-12-11 11:40:44.339623+03 (3 rows)
这就是发生的事情.'now'
转换为时间戳一次(当解析准备好的语句时),now()
并被调用两次.