Problem mit dem zusammengesetzten Typ in einer UPSERT-Funktion

7

Ich habe eine Funktion in PostgreSQL 9.1 aufgerufen fun_test. Es hat einen zusammengesetzten Typ als Eingabeparameter und ich erhalte immer wieder einen Casting-Fehler, wenn ich ihn aufrufe.

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;

Unten ist der Typ testobj

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

Wenn ich die Funktion aufrufe:

SELECT netcen.fun_test('(3,[email protected],khaendra)':: netcen.testobj);

.. Ich erhalte folgende Fehlermeldung:

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

Wo soll ich gießen?
Definition der Tabelle 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, danke für die Links. Ich habe meine Funktion gelesen und habe sie geändert. Bitte gehen Sie sie durch und sagen Sie mir, ob sie mit mehreren Clients, die dieselbe Funktion gleichzeitig aufrufen, gut funktioniert.

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;

Ich habe die Typprüfung abgeschafft und nur den Tisch benutzt test! Ich wusste nicht, dass das funktionieren könnte!

Indago
quelle

Antworten:

7

Antworten

Der Fehler tritt hier auf:

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

Müsste sein

CASE WHEN COALESCE(tmp_code,0)=0 THEN

Sie mischen die beiden verschiedenen Syntaxvarianten von PL / pgSQL CASE("einfacher Fall" vs. "gesuchter Fall") auf inkompatible Weise.

Es gibt einen weiteren Fehler :

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

Hast du gemeint:

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

Auch: Es besteht keine Notwendigkeit für CREATE TYPE netcen.testobj ....
Sie können einfach den Tabellennamen netcen.testals Typnamen verwenden.

Was du wirklich willst

Bei näherer Betrachtung scheinen Sie einen Klassiker zu probieren UPSERT.
Die einfache Form in plpgsql (wenn Parallelität kein Problem ist):

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;

Könnte auch mit einfachem SQL unter Verwendung eines datenmodifizierenden CTE durchgeführt werden :

WITH my_row(testkey, tes, testname) AS (
    SELECT 1::smallint, '[email protected]', '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);

Das Zeitfenster für eine mögliche Rennbedingung ist bei dieser Form extrem klein (einzelne kombinierte Aussage). Wenn die Parallelität immer noch ein Problem darstellt (hohe gleichzeitige Schreiblast),

Ihre Funktion überprüft

Wenn die Funktion zurückkehrt, wurde die Zeile eingefügt oder aktualisiert. Der einzige andere Weg ist ein EXCEPTIONanderer. Der zurückgegebene Wert TRUEist nur Rauschen. (Könnte interessanter sein, TRUE für INSERT und FALSE für UPDATE zurückzugeben.) Also habe ich vereinfacht:

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;

Verwandte Antworten zu SO:

Mehr dazu UPSERTin diesem Blogbeitrag von Depesz .

Erwin Brandstetter
quelle
Was meinst du, wenn Parallelität kein Problem ist?
Indago
@indago: Parallelität ist ein Problem in Datenbanken, in denen mehrere Clients möglicherweise gleichzeitig versuchen, INSERT / UPDATE auszuführen. Es gibt eine sehr geringe Chance für eine Rennbedingung. Lesen Sie den Artikel, auf den ich verweise, oder probieren Sie das Handbuch hier aus .
Erwin Brandstetter
@ Erwin, Parallelität ist natürlich ein Problem, da mehr als zehn Clients verbunden sind und gleichzeitig an dieser Datenbank arbeiten. Deshalb möchte ich, dass alle Einfügungen und Aktualisierungen in der Datenbank von Funktionen wie diesen verarbeitet werden. Kann ich die Funktionen, die Sie mir hier gegeben haben, noch verwenden? Bitte geben Sie mir eine modifizierte Version, die Parallelität ermöglicht. Vielen dank für Deine Hilfe!
Indago
@indago: Ich habe. Folgen Sie dem Link am Ende meiner Antwort.
Erwin Brandstetter
1
@indago: Sicher kannst du zurückkehren text. TU es einfach. Wenn Sie auf Probleme stoßen, posten Sie hier eine andere Frage , keinen Kommentar.
Erwin Brandstetter
6

Während klin in seiner Antwort zur Behebung Ihrer aktuellen Funktion technisch richtig ist, möchte ich Ihren gesamten Ansatz in Frage stellen. Was Sie erreichen möchten, heißt "UPSERT" und mit PostgreSQL 9.1 (das beschreibbare CTEs bietet ) haben Sie eine sehr einfache Möglichkeit, dies zu erreichen. Ich habe die Funktionsdefinition aus Gründen der Übersichtlichkeit weggelassen, aber Sie können sie leicht in eine Abfragesprachenfunktion einschließen (dh mit der Endung 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
);

Auf diese Weise können Sie die Verwendung eines teuren Cursors und einer noch teureren EXCEPTIONKlausel vermeiden .

Beachten Sie, dass dies im Grunde die gleiche Lösung ist wie eine der mehreren Erwin-Antworten (aus historischen Gründen wurden zwei Fragen mit all ihren Antworten zu einer zusammengefasst). Die Warnung vor Parallelität gilt also auch für diese.

dezso
quelle
@ Deszo, danke für den sauberen Ansatz, wird dies Parallelität unterstützen?
Indago
3

Einschub von

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

verwenden

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

Beachten Sie, dass "tmp_code" klein ist, während "COALESCE (tmp_code, 0) = 0" boolesch ist.

Eine andere Fehlerfunktion gibt einen Booleschen Wert zurück, hat jedoch keine Rückgabeanweisung.

Wenn netcen.test.testkey ein Primärschlüssel ist, können Sie die Ausnahme (39.6.6) verwenden , insbesondere Beispiel 39-2 - es ist genau das, was Sie tun möchten.

klin
quelle