Postgres now()vs'now'in function

Dar*_*ard 1 postgresql datetime function

遇到在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);
Run Code Online (Sandbox Code Playgroud)

等几秒钟

select 1 from test_date_bug_function(2);
Run Code Online (Sandbox Code Playgroud)

结果:

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)
Run Code Online (Sandbox Code Playgroud)

我不希望第2行的date2与第1行的date2相同.

更换

 UPDATE test_date_bug SET date2 = 'now' WHERE id = id_param;
Run Code Online (Sandbox Code Playgroud)

同

 UPDATE test_date_bug SET date2 = now() WHERE id = id_param;
Run Code Online (Sandbox Code Playgroud)

按照我的预期设置新日期:

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
Run Code Online (Sandbox Code Playgroud)

想法?

Ego*_*gov 7

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

  1. 替换'现在'

    我们来看看文档(日期/时间函数和运算符):

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

    SELECT CURRENT_TIMESTAMP;

    SELECT now();

    选择TIMESTAMP'现在'; - 与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)
    
    Run Code Online (Sandbox Code Playgroud)

    在此示例中,'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)
    
    Run Code Online (Sandbox Code Playgroud)

这就是发生的事情.'now'转换为时间戳一次(当解析准备好的语句时),now()并被调用两次.


Iva*_*kov 5

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

系统解析完常量后会立即将now转换为时间戳,这样当需要默认值时,就会使用建表的时间!

虽然这并不完全适用于您的情况,但它可以以某种方式解释您的程序的行为。正如文档中所述,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; 
Run Code Online (Sandbox Code Playgroud)

看看你是否会实现预期的行为。这是因为每个UPDATE都是新编译的,而过程是持久的,这使得转换后的值也持久。