简体   繁体   English

如何在 Postgres 中使用 upsert

[英]How to use upsert with Postgres

I want to convert this code in Postgres to something shorter that will do the same.我想将 Postgres 中的这段代码转换为更短的代码,可以做同样的事情。 I read about upsert but I couldn't understand a good way to implement that on my code.我读到了 upsert,但我不明白在我的代码上实现它的好方法。 What I wrote works fine, but I want to find a more elegant way to write it.我写的东西很好,但我想找到一种更优雅的方式来写它。 Hope someone here can help me: This is the query:希望这里有人可以帮助我:这是查询:

CREATE OR REPLACE FUNCTION insert_table(
    in_guid    character varying,
    in_x_value character varying,
    in_y_value character varying
)
RETURNS TABLE(response boolean) LANGUAGE 'plpgsql'

DECLARE _id integer;

BEGIN
    -- guid exists and it's been 10 minutes from created_date:
    IF ((SELECT COUNT (*) FROM public.tbl_client_location WHERE guid = in_guid AND created_date < NOW() - INTERVAL '10 MINUTE') > 0) THEN
        RETURN QUERY (SELECT FALSE);
    
    -- guid exists but 10 minutes hasen't passed yet:
    ELSEIF ((SELECT COUNT (*) FROM public.tbl_client_location WHERE guid = in_guid) > 0) THEN
    
        UPDATE
            public.tbl_client_location
        SET
            x_value = in_x_value,
            y_value = in_y_value,
            updated_date = now()
        WHERE
            guid = in_guid;

        RETURN QUERY (SELECT TRUE);
    
    -- guid not exist:
    ELSE
       
        INSERT INTO public.tbl_client_location
            ( guid   , x_value   , y_value    )
        VALUES
            ( in_guid, in_x_value, in_y_value )
        RETURNING id INTO _id;  

        RETURN QUERY (SELECT TRUE);

    END IF;
END

This can indeed be a lot simpler :这确实可以简单得多

CREATE OR REPLACE FUNCTION insert_table(in_guid text
                                      , in_x_value text
                                      , in_y_value text
                                      , OUT response bool)  -- ④
  -- RETURNS record  -- optional noise  -- ④
  LANGUAGE plpgsql AS  -- ①
$func$  -- ②
-- DECLARE
   -- _id integer;  -- what for?
BEGIN
   INSERT INTO tbl AS t
          (   guid,    x_value,    y_value)
   VALUES (in_guid, in_x_value, in_y_value)
   ON CONFLICT (guid) DO UPDATE  -- guid exists
   SET    (         x_value,          y_value, updated_date)
        = (EXCLUDED.x_value, EXCLUDED.y_value, now())  -- ⑤
   WHERE  t.created_date >= now() - interval '10 minutes'  -- ③ have not passed yet
   -- RETURNING id INTO _id  -- what for?
   ;
   response := FOUND;  -- ⑥
END
$func$;

Assuming guid is defined UNIQUE or PRIMARY KEY , and created_date is defined NOT NULL DEFAULT now() .假设guid定义为UNIQUEPRIMARY KEY ,并且created_date定义为NOT NULL DEFAULT now()

① Language name is an identifier - better without quotes. ① 语言名称是一个标识符——最好不带引号。

② Quotes around function body were missing (invalid command). ② 缺少 function 主体周围的引号(无效命令)。 See:看:

UPDATE only if 10 min have not passed yet. ③ 仅在 10 分钟未过时才UPDATE Keep in mind that timestamps are those from the beginning of the respective transactions.请记住,时间戳是从相应事务开始的时间戳。 So keep transactions short and simple.因此,请保持交易简短明了。 See:看:

④ A function with OUT parameter(s) and no RETURNS clause returns a single row ( record ) automatically. ④ 带有OUT参数且没有RETURNS子句的 function 自动返回单行( record )。 Your original was declared as set-returning function (0-n returned rows), which didn't make sense.您的原始文件被声明为 set-returning function (0-n 返回行),这没有意义。 See:看:

⑤ It's generally better to use the special EXCLUDED row than to spell out values again. ⑤ 通常最好使用特殊的EXCLUDED行而不是再次拼出值。 See:看:

⑤ Also using short syntax for updating multiple columns. ⑤ 也使用短语法来更新多列。 See:看:

⑥ To see whether a row was written use the special variable FOUND . ⑥ 要查看是否写入了行,请使用特殊变量FOUND Subtle difference: different from your original, you get true or false after the fact , saying that a row has actually been written (or not).细微的区别:与你原来的不同,你在事实之后得到truefalse ,说一行实际上已经被写入(或没有)。 In your original, the INSERT or UPDATE might still be skipped (without raising an exception) by a trigger or rule, and the function result would be misleading in this case.在您的原始文件中,触发器或规则可能仍会跳过INSERTUPDATE (不引发异常),并且 function 结果在这种情况下会产生误导。 See:看:

Further reading:进一步阅读:


You might just run the single SQL statement instead, providing your values once :您可能只运行单个 SQL 语句,提供您的值一次

INSERT INTO tbl AS t(guid, x_value,y_value)
VALUES ($in_guid, $in_x_value, $in_y_value)  -- your values here, once
ON CONFLICT (guid) DO UPDATE
SET    (x_value,y_value, updated_date)
     = (EXCLUDED.x_value, EXCLUDED.y_value, now())
WHERE  t.created_date >= now() - interval '10 minutes';

I finally solved it.我终于解决了。 I made another function that'll be called and checked if it's already exists and the time and then I can do upsert without any problems.我制作了另一个 function 将被调用并检查它是否已经存在以及时间,然后我可以毫无问题地进行 upsert。 That's what I did at the end:这就是我最后所做的:

CREATE OR REPLACE FUNCTION fnc_check_table(
    in_guid character varying)
    RETURNS TABLE(response boolean) 
    LANGUAGE 'plpgsql'

    COST 100
    VOLATILE 
    ROWS 1000
AS $BODY$
BEGIN
IF EXISTS (SELECT FROM tbl WHERE guid = in_guid AND created_date < NOW() - INTERVAL '10 MINUTE' ) THEN
RETURN QUERY (SELECT FALSE);
ELSEIF EXISTS (SELECT FROM tbl WHERE guid = in_guid AND created_date > NOW() - INTERVAL '10 MINUTE') THEN
RETURN QUERY (SELECT TRUE);
ELSE
RETURN QUERY (SELECT TRUE);
END IF;

END
$BODY$;



CREATE OR REPLACE FUNCTION fnc_insert_table(
    in_guid character varying,
    in_x_value character varying,
    in_y_value character varying)
    RETURNS TABLE(response boolean) 
    LANGUAGE 'plpgsql'

    COST 100
    VOLATILE 
    ROWS 1000
AS $BODY$
BEGIN
IF (fnc_check_table(in_guid)) THEN
    INSERT INTO tbl (guid, x_value, y_value)
    VALUES (in_guid,in_x_value,in_y_value)
    ON CONFLICT (guid)
    DO UPDATE SET x_value=in_x_value, y_value=in_y_value, updated_date=now();
    RETURN QUERY (SELECT TRUE);
ELSE
    RETURN QUERY (SELECT FALSE);
END IF;
END
$BODY$;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM