遇到一个问题,其中now()表现不同于现在'在Postgres中的函数中使用时。
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
想法?
答案 0 :(得分:7)
这不是一个错误,它是一个功能......这里有两点。
现在替换'
让我们看一下文档(Date/Time Functions and Operators):
所有日期/时间数据类型现在也接受特殊的文字值 指定当前日期和时间(再次,解释为 交易开始时间)。因此,以下三个都返回相同 结果:
SELECT CURRENT_TIMESTAMP;
SELECT now();
SELECT TIMESTAMP' now&#39 ;; - 与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 Statement):
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()
被调用两次。
答案 1 :(得分:4)
系统将在常量为时立即转换为时间戳 解析,这样当需要默认值时,就可以了 将使用表创建!
虽然这并不完全适用于您的情况,但它可以某种方式解释您的过程的行为。如文档中所述,PostgreSQL会在解析时将'now'
转换为时间戳。由于它是一个过程,这将在第一次调用该过程后发生。对于每个连续调用,该值将是相同的,因为它已在第一次解析后转换。所以基本上发生的是你执行select 1 from test_date_bug_function(1);
将'now'
转换为你程序中现在持久的时间戳,然后select 1 from test_date_bug_function(2);
只使用已转换的值。
如果您还没有确信,可以尝试执行(只是程序之外的语句):
UPDATE test_date_bug SET date2 = 'now' WHERE id = 1;
UPDATE test_date_bug SET date2 = 'now' WHERE id = 2;
看到你将达到预期的行为。这是因为每个UPDATE
都是新编译的,而过程是持久的,这使得转换后的值也是持久的。