Postgres now()vs' now'在功能上

时间:2015-12-10 18:52:42

标签: postgresql function datetime

遇到一个问题,其中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

想法?

2 个答案:

答案 0 :(得分:7)

这不是一个错误,它是一个功能......这里有两点。

  1. 现在替换'

    让我们看一下文档(Date/Time Functions and Operators):

      

    所有日期/时间数据类型现在也接受特殊的文字值   指定当前日期和时间(再次,解释为   交易开始时间)。因此,以下三个都返回相同   结果:

         

    SELECT CURRENT_TIMESTAMP;

         

    SELECT now();

         

    SELECT TIMESTAMP' now&#39 ;; - 与DEFAULT

    一起使用不正确      

    提示:在创建表时,您不希望在指定DEFAULT子句时使用第三种形式。系统现在将转换为   解析常量时的时间戳,以便当时   需要默认值,将使用表创建的时间!   在默认值为之前,不会评估前两个表单   使用,因为它们是函数调用。因此他们会给予期望   违约到行插入时的行为。

    因此'now'会在解析时转换为时间戳。

  2. 准备好的陈述

    好的,但它在功能方面意味着什么?每次调用函数时都很容易证明函数被解释:

    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)
    
  3. 发生了什么。 'now'被转换为时间戳一次(当解析准备好的语句时),now()被调用两次。

答案 1 :(得分:4)

Egor已经指出了原因,但由于你似乎不相信我会进一步讨论这个问题。文档中的重要部分如下:

  

系统将在常量为时立即转换为时间戳   解析,这样当需要默认值时,就可以了   将使用表创建!

虽然这并不完全适用于您的情况,但它可以某种方式解释您的过程的行为。如文档中所述,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都是新编译的,而过程是持久的,这使得转换后的值也是持久的。