【问题标题】:Postgresql 8.4 update query syntax error in plpgsql functionPostgresql 8.4 更新 plpgsql 函数中的查询语法错误
【发布时间】:2017-12-12 00:55:12
【问题描述】:

我正在使用 PostgreSQL 8.4 并创建一个 plpgsql 函数。在这个函数的主体中,我有一个更新记录的查询。

...
  UPDATE device_syncfiles SET 
  state_code = 1, updated_at = NOW() at time zone 'UTC'
  WHERE 
  ((state_code = 2 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > 3600) OR 
  (state_code = 3 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > 3600));
...

当我将此函数加载到数据库中时,出现语法错误

ERROR:  syntax error at or near "$1"
LINE 1: UPDATE device_syncfiles SET  $1  = 1,  $2  = NOW() at time z...
                                     ^
QUERY:  UPDATE device_syncfiles SET  $1  = 1,  $2  = NOW() at time zone 'UTC' WHERE (( $1  = 2 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' -  $2 ::timestamp without time zone)) >  $3 ) OR ( $1  = 3 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' -  $2 ::timestamp without time zone)) >  $4 ))
CONTEXT:  SQL statement in PL/PgSQL function "syncfile_get" near line 19

我找不到这个查询的任何问题。这里有什么问题?

更新:(缺少信息)

表:device_syncfiles

  id PK integer auto inc
  user_id integer FK
  file_name character varying(255) NOT NULL,
  state_code integer NOT NULL FK,
  md5 character varying(255) NOT NULL,
  msg character varying(255),
  created_at timestamp without time zone,
  updated_at timestamp without time zone

函数:syncfile_get()

CREATE OR REPLACE FUNCTION syncfile_get()
  RETURNS TABLE(id integer, user_id integer, file_name character varying, state_code integer, md5 character varying, created_at timestamp without time zone, updated_at timestamp without time zone) AS
$BODY$
      DECLARE
        _device_syncfile_id integer;
        _download_timeout integer;
        _processing_timeout integer;
      BEGIN
        -- GET all timeout info
        SELECT state_timeout INTO _download_timeout FROM device_syncfile_states 
        WHERE state_name = 'downloading';
        SELECT state_timeout INTO _processing_timeout FROM device_syncfile_states 
        WHERE state_name = 'processing';
        -- GET syncfile id
        _device_syncfile_id = NULL;

        -- Reset timed out file to idel state
        UPDATE device_syncfiles SET 
        state_code = 1, updated_at = NOW() at time zone 'UTC'
        WHERE 
        ((state_code = 2 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > _download_timeout) OR 
        (state_code = 3 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > _processing_timeout));

        -- GET the id of one idel/timed out file => result could be a integer or NULL
        SELECT device_syncfiles.id INTO _device_syncfile_id FROM device_syncfiles 
        WHERE 
        device_syncfiles.state_code = 1 OR 
        (device_syncfiles.state_code = 2 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - device_syncfiles.updated_at::timestamp without time zone)) > _download_timeout) OR
        (device_syncfiles.state_code = 3 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - device_syncfiles.updated_at::timestamp without time zone)) > _processing_timeout) 
        LIMIT 1;

        -- WHEN NULL skip state update and return empty set of record
        -- Otherwise return the set of record with the id found in last step
        IF _device_syncfile_id IS NOT NULL THEN
          PERFORM syncfile_update(_device_syncfile_id, 2, NULL);
        END IF;

        RETURN QUERY SELECT 
        device_syncfiles.id,
        device_syncfiles.user_id ,
        device_syncfiles.file_name ,
        device_syncfiles.state_code ,
        device_syncfiles.md5 ,
        device_syncfiles.created_at ,
        device_syncfiles.updated_at 
        FROM device_syncfiles WHERE device_syncfiles.id = _device_syncfile_id;
      END;
      $BODY$
  LANGUAGE plpgsql VOLATILE

【问题讨论】:

    标签: postgresql plpgsql postgresql-8.4


    【解决方案1】:

    很多问题。

    0.

    我正在使用 Postgresql 8.4

    Postgres 8.4 于 2014 年 7 月达到 EOL。Consider upgrading to a current version. 紧急。

    1.

    您的问题没有透露完整的功能(至少是页眉和页脚),也没有透露任何表格定义和一些示例数据来帮助我们帮助您。

    我必须做出假设,我有根据的猜测是您有一个名为state_code 的函数参数,它与相同的列名冲突。在 三个 处。基础知识:

    您必须知道在RETURNS TABLE 子句中声明的所有字段实际上也是OUT 参数。 (如第一个链接的第一句话所述。)所以你的 Q 更新证实了我的假设。

    2.

    您的错误消息在此处报告这些实例的第一个

    UPDATE device_syncfiles SET 
      state_code = 1 ...

    这是 0. 的结果。您正在被您长期死去和被遗忘的 Postgres 版本绊倒,其中函数创建时的表面语法检查用于检测 UPDATE 语句的目标列和函数参数之间的命名冲突。这很愚蠢,后来被删除:那些目标列不能与主体上的函数参数冲突。

    您在 Postgres 8.4 中重现的错误:dbfiddle here

    Postgres 9.4 中不会发生同样的情况:dbfiddle here

    修复,最好重命名函数参数以避免冲突。相关:

    3.

    还有两个实例:

    WHERE 
      ((state_code = 2 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > 3600) OR 
       (state_code = 3 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > 3600));

    任何版本都需要修复。 Postgres 无法判断是解析为函数参数还是表列。最好对所有列进行表限定,以避免与参数名称发生任何可能的冲突。 (UPDATE 目标列除外,它们不需要也不允许表限定。)

    4.

    那仍然是猪身上的口红。像这样改进查询:

    UPDATE device_syncfiles d
    SET    state_code = 1
         , updated_at = NOW() AT TIME ZONE 'UTC'
    WHERE  d.state_code IN (2, 3)
    AND    d.updated_at < (now() - interval '1 hour') AT TIME ZONE 'UTC';
    

    更短、更快,可以使用updated_at上的索引。

    5.

    最后考虑使用timestamp with time zone而不是timestamp without time zone开头:

    【讨论】:

    • 感谢您的回答。我无法升级 db,因为基础架构更改会产生太多副作用。而且我很确定变量名称与表列不冲突,并且 state_code 是 device_syncfiles 表中的列之一。我尝试隔离运行此更新查询及其工作,因此当此查询在函数中时无法正常工作。
    • 更新:我创建了一个 void 函数,它只包含这个更新查询说 A() 然后我调用:在原始函数中执行 A()。然后所有功能正常工作。那么认为这是 8.4 中的 postgresql 错误吗?谢谢
    • @Lester:如果您认真对待这一点,请编辑您的问题以提供缺失的信息。
    • @Lester:在RETURNS TABLE 中声明的所有字段都是OUT 参数,在函数体中的所有SQL 查询中可见。跟我猜的一模一样。我在上面澄清了一些。
    • 太棒了!问题解决了!非常感谢!你是个传奇!
    猜你喜欢
    • 2017-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-19
    • 1970-01-01
    • 2014-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多