Sự cố với loại hỗn hợp trong chức năng UPSERT


7

Tôi có một chức năng trong PostgreSQL 9.1 được gọi fun_test. Nó có một kiểu tổng hợp làm tham số đầu vào và tôi liên tục gặp lỗi khi tôi gọi nó.

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;

Dưới đây là loại testobj

CREATE TYPE netcen.testobj AS
   (testkey smallint,
    tes text,
    testname text);

Khi tôi gọi hàm:

SELECT netcen.fun_test('(3,khaendra@me.com,khaendra)':: netcen.testobj);

.. Tôi nhận được thông báo lỗi sau:

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

Tôi nên đúc ở đâu?
Định nghĩa của bảng netcen.test:

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

@ Erwin, cảm ơn vì các liên kết. Tôi đã đọc và đã sửa đổi chức năng của mình thành điều này, xin vui lòng xem qua nó và cho tôi biết nếu nó có thể hoạt động tốt với một số khách hàng gọi cùng một chức năng không?

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;

Tôi đã thực hiện bài kiểm tra loại và chỉ sử dụng bảng test! Tôi không biết rằng có thể làm việc!

Câu trả lời:


7

Câu trả lời

Các lỗi xảy ra ở đây:

CASE tmp_code
    WHEN COALESCE(tmp_code,0)=0 THEN

Sẽ phải được

CASE WHEN COALESCE(tmp_code,0)=0 THEN

Bạn đang trộn hai biến thể cú pháp khác nhau của PL / pgSQL CASE("trường hợp đơn giản" so với "trường hợp tìm kiếm") theo cách không tương thích.

một lỗi khác :

update netcen.test set 
test=myobj.test,
testname=myobj.testname  WHERE testkey=myobj.testkey;

Ý bạn là:

UPDATE test
SET    tes = myobj.tes
      ,testname = myobj.testname
WHERE  testkey = myobj.testkey;

Ngoài ra: Không cần CREATE TYPE netcen.testobj ....
Bạn chỉ có thể sử dụng tên bảng làm tên netcen.testloại.

Những gì bạn thực sự muốn

Khi kiểm tra kỹ hơn, bạn dường như đang thử một tác phẩm cổ điển UPSERT.
Biểu mẫu đơn giản trong plpgsql (nếu đồng thời không phải là vấn đề):

CREATE OR REPLACE FUNCTION fun_test(myobj testobj)
  RETURNS boolean 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$ LANGUAGE plpgsql;

Cũng có thể được thực hiện với SQL đơn giản bằng cách sử dụng CTE sửa đổi dữ liệu :

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 1 FROM u);

Cửa sổ thời gian cho một điều kiện cuộc đua có thể là cực kỳ nhỏ với hình thức này (tuyên bố kết hợp duy nhất). Nếu đồng thời vẫn là một vấn đề (tải ghi đồng thời nặng),

Chức năng của bạn được xem xét

Nếu hàm trả về, hàng đã được chèn hoặc cập nhật. Cách khác là một EXCEPTIONloại khác. Giá trị trả về TRUEchỉ là nhiễu. (Có thể thú vị hơn khi trả về TRUE cho INSERT và FALSE cho CẬP NHẬT.) Vì vậy, tôi đã đơn giản hóa:

CREATE OR REPLACE FUNCTION netcen.fun_test_modified(myobj netcen.test)
  RETURNS boolean AS
$BODY$
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
$BODY$  LANGUAGE plpgsql;

Câu trả lời liên quan về SO:

Thông tin thêm về UPSERTbài viết trên blog này của Depesz .


Ý bạn là gì nếu đồng thời không phải là vấn đề?
indago

@indago: Đồng thời là một vấn đề trong cơ sở dữ liệu nơi nhiều khách hàng có thể cố gắng XÁC NHẬN / CẬP NHẬT cùng một lúc. Có một cơ hội rất mỏng cho một điều kiện cuộc đua. Đọc bài viết tôi liên kết đến hoặc thử hướng dẫn ở đây .
Erwin Brandstetter

@ Erwin, đồng thời là một vấn đề vì hơn mười khách hàng sẽ được kết nối và làm việc cùng một lúc trên cơ sở dữ liệu này, đó là lý do tại sao tôi muốn tất cả các chèn và cập nhật được xử lý trong cơ sở dữ liệu bằng các chức năng như thế này. Tôi vẫn có thể sử dụng các chức năng bạn đã cho tôi ở đây? xin vui lòng cho tôi một phiên bản sửa đổi phục vụ đồng thời. Cảm ơn sự giúp đỡ của bạn!
indago

@indago: Tôi đã làm. Theo liên kết ở cuối câu trả lời của tôi.
Erwin Brandstetter

1
@indago: Chắc chắn bạn có thể quay lại text. Cứ làm đi. Nếu bạn gặp vấn đề, hãy đăng một câu hỏi khác , không bình luận ở đây.
Erwin Brandstetter

6

Mặc dù klin đúng về mặt kỹ thuật trong câu trả lời của anh ấy về cách khắc phục chức năng hiện tại của bạn, hãy để tôi đặt câu hỏi cho toàn bộ cách tiếp cận của bạn. Những gì bạn cố gắng đạt được được gọi là 'UPSERT' và với PostgreSQL 9.1 (cung cấp các CTE có thể ghi ), bạn có một cách rất đơn giản để đạt được điều này. Tôi đã bỏ qua định nghĩa hàm vì mục đích rõ ràng, nhưng bạn có thể dễ dàng gói nó trong một hàm ngôn ngữ truy vấn (nghĩa là kết thúc bằng 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
);

Bằng cách này, bạn có thể tránh sử dụng một con trỏ đắt tiền và một EXCEPTIONđiều khoản thậm chí còn đắt hơn .

Lưu ý rằng về cơ bản, đây là giải pháp tương tự như một trong những câu trả lời của Erwin trong câu trả lời của anh ấy (vì lý do lịch sử, hai câu hỏi đã được hợp nhất thành một với tất cả các câu trả lời của họ). Vì vậy, cảnh báo về đồng thời cũng áp dụng cho điều này.


@Deszo, cảm ơn vì cách tiếp cận sạch sẽ, điều này có hỗ trợ đồng thời không?
indago

3

Insetad của

CASE tmp_code
    WHEN COALESCE(tmp_code, 0) = 0 THEN

sử dụng

IF COALESCE(tmp_code, 0) = 0 THEN
    ...
END IF;

Lưu ý rằng "tmp_code" là smallint trong khi "COALESCE (tmp_code, 0) = 0" là boolean.

Một lỗi khác - hàm trả về boolean nhưng không có lệnh trả về.

Nếu netcen.test.testkey là khóa chính, bạn có thể sử dụng ngoại lệ (39.6.6) , đặc biệt là xem Ví dụ 39-2 - đó chỉ là những gì bạn muốn làm.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.