UPSERT 函数中的复合类型问题

ind*_*ago 7 postgresql plpgsql postgresql-9.1 upsert

我在 PostgreSQL 9.1 中有一个名为fun_test. 它有一个复合类型作为输入参数,我在调用它时不断收到转换错误。

CREATE OR REPLACE FUNCTION netcen.fun_test(myobj netcen.testobj)
  RETURNS boolean AS
$BODY$
DECLARE 
tmp_code smallint;
cur_member refcursor;
BEGIN
-- Check if the member exists first
OPEN cur_member FOR 
EXECUTE 'SELECT testkey FROM netcen.test WHERE testkey=' || myobj.testkey ;
FETCH cur_member INTO tmp_code;
CLOSE cur_member;
CASE tmp_code
    WHEN COALESCE(tmp_code,0)=0 THEN
    -- Record not found INSERT a new record
    -- will skip user defined validation for now
    insert into netcen.test values(myobj.testkey,
    myobj.tes,
    myobj.testname);

    ELSE
    -- Record found UPDATE the record
    update netcen.test set 
    test=myobj.test,
    testname=myobj.testname  WHERE testkey=myobj.testkey;

END CASE;
END;$BODY$
  LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

下面是类型 testobj

CREATE TYPE netcen.testobj AS
   (testkey smallint,
    tes text,
    testname text);
Run Code Online (Sandbox Code Playgroud)

当我调用函数时:

SELECT netcen.fun_test('(3,khaendra@me.com,khaendra)':: netcen.testobj);
Run Code Online (Sandbox Code Playgroud)

.. 我收到以下错误消息:

ERROR:  operator does not exist: smallint = boolean
LINE 1: SELECT "__Case__Variable_8__" IN (COALESCE(tmp_code,0)=0)
                                      ^
HINT:  No operator matches the given name and argument type(s).
       You might need to add explicit type casts.
QUERY:  SELECT "__Case__Variable_8__" IN (COALESCE(tmp_code,0)=0)
CONTEXT:  PL/pgSQL function "fun_test" line 11 at CASE
Run Code Online (Sandbox Code Playgroud)

我应该在哪里投?
表的定义netcen.test

CREATE TABLE netcen.test    (
  testkey smallint NOT NULL DEFAULT 0,
  tes netcen.dom_email_validation,
  testname text,
  CONSTRAINT key PRIMARY KEY (testkey)
)
Run Code Online (Sandbox Code Playgroud)

@ Erwin,感谢您的链接。我阅读并修改了我的函数,请仔细阅读并告诉我它是否可以与多个客户端同时调用相同的函数一起工作?

CREATE OR REPLACE FUNCTION netcen.fun_test_modified(myobj netcen.test)
  RETURNS boolean AS
$BODY$
DECLARE 
myoutput boolean :=false;
BEGIN
    update netcen.test set 
    tes=myobj.tes,
    testname=myobj.testname  WHERE testkey=myobj.testkey;
IF FOUND THEN
        myoutput:= TRUE;
        RETURN myoutput;
    END IF;
    BEGIN
        INSERT INTO netcen.test values(myobj.testkey,
    myobj.tes,
    myobj.testname);
    myoutput:= TRUE;
    EXCEPTION WHEN OTHERS THEN
        update netcen.test set 
    tes=myobj.tes,
    testname=myobj.testname  WHERE testkey=myobj.testkey;
    myoutput:= TRUE;
    END;
    RETURN myoutput;
END;
$BODY$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

我已经取消了类型测试,只使用了表格test!我不知道这可以工作!

Erw*_*ter 7

回答

错误发生在这里:

CASE tmp_code
    WHEN COALESCE(tmp_code,0)=0 THEN 
Run Code Online (Sandbox Code Playgroud)

像这样工作:

CASE WHEN COALESCE(tmp_code,0)=0 THEN
Run Code Online (Sandbox Code Playgroud)

您以不兼容的方式混合了两种不同的PL/pgSQLCASE语法变体(“简单”与“搜索”)。

还有一个错误

update netcen.test set 
test=myobj.test,
testname=myobj.testname  WHERE testkey=myobj.testkey;
Run Code Online (Sandbox Code Playgroud)

你的意思是:

UPDATE test
SET    tes = myobj.tes
     , testname = myobj.testname
WHERE  testkey = myobj.testkey;
Run Code Online (Sandbox Code Playgroud)

也没有必要CREATE TYPE netcen.testobj ...。您可以使用表名netcen.test作为类型名。

你真的想“UPSERT”

在 Postgres 9.5 或更高版本中,使用新的 UPSERT ( INSERT ... ON CONFLICT DO UPDATE)。喜欢:


对于旧版本,您可以模拟UPSERT. plpgsql中的简单形式,没有并发:

CREATE OR REPLACE FUNCTION fun_test(myobj testobj)
  RETURNS boolean
  LANGUAGE plpgsql AS
$func$
BEGIN    
   UPDATE test
   SET    tes = myobj.tes
        , testname = myobj.testname
   WHERE  testkey = myobj.testkey;

   IF FOUND THEN
      RETURN FALSE;
   ELSE
      INSERT INTO test SELECT (myobj).*;
      RETURN TRUE;
   END IF;
END
$func$;
Run Code Online (Sandbox Code Playgroud)

可能只是带有数据修改 CTE 的普通 SQL :

WITH my_row(testkey, tes, testname) AS (
    SELECT 1::smallint, 'khaendra@me.net', 'khaendra'
    )
, u AS (
    UPDATE test t
    SET    tes = m.tes
         , testname = m.testname
    FROM   my_row m
    WHERE  t.testkey = m.testkey
    RETURNING t.testkey
    )
INSERT INTO test (testkey, tes, testname)
SELECT * FROM my_row
WHERE  NOT EXISTS (SELECT FROM u);
Run Code Online (Sandbox Code Playgroud)

使用这种形式(单个组合语句),可能出现竞争条件的时间窗口非常小。如果并发仍然是一个问题(重并发写入负载),那么......

您的功能已审核

如果函数返回,则该行已被插入或更新。唯一的另一种方式是一种EXCEPTION不同的方式。返回的值true只是噪音。(返回trueforINSERTfalsefor可能更有趣UPDATE。)所以我简化了:

CREATE OR REPLACE FUNCTION netcen.fun_test_modified(myobj netcen.test)
  RETURNS boolean
  LANGUAGE plpgsql AS
$func$
BEGIN
   UPDATE netcen.test
   SET    tes = myobj.tes
        , testname = myobj.testname
   WHERE  testkey = myobj.testkey;

   IF FOUND THEN
      RETURN true;
   END IF;

   BEGIN
      INSERT INTO netcen.test 
      SELECT (myobj).*;  -- simpler form, parenthesis needed.

   EXCEPTION WHEN unique_violation THEN   -- cleaner
      UPDATE netcen.test
      SET    tes = myobj.tes
           , testname = myobj.testname
      WHERE  testkey = myobj.testkey;
   END;
   RETURN true;
END
$func$;
Run Code Online (Sandbox Code Playgroud)

SO的相关答案:

Depesz 在 UPSERT 上发表的博文


dez*_*zso 6

虽然 klin 在他关于如何修复您当前功能的回答中在技术上是正确的,但让我质疑您的整个方法。您尝试实现的目标称为“UPSERT”,使用 PostgreSQL 9.1(提供可写 CTE),您可以通过一种非常简单的方法来实现这一目标。为了清楚起见,我省略了函数定义,但您可以轻松地将其包装在查询语言函数中(即以 结尾LANGUAGE sql):

WITH upd AS (
    UPDATE netcen.test 
    SET (test, testname) = ($1.test, $1.testname)
    WHERE testkey =  $1.testkey
    RETURNING *
)
INSERT INTO netcen.test (
    testkey,
    test, 
    testname
)
VALUES (
    $1.testkey,
    $1.tes,
    $1.testname
)
WHERE NOT EXISTS (
    SELECT 1
    FROM upd
);
Run Code Online (Sandbox Code Playgroud)

这样您就可以避免使用昂贵的游标和更昂贵的EXCEPTION子句。

请注意,这与 Erwin 在其答案中提供的几个解决方案之一基本相同(由于历史原因,两个问题与所有答案合并为一个)。所以关于并发的警告也适用于这一点。