Cursor oder Alternative

ukulele

Datenbank-Guru
Beiträge
5.107
Bisher habe ich, um Datensätze zu durchlaufen und "zu verarbeiten", in MSSQL öfter mal zum Cursor gegriffen. Nützlich ist das vor allem wenn der Code zur Verarbeitung der Information hin und wieder mal auf unerwartete Fehler trifft und man wissen muss wo es scheitert.

Ich hab so einen Beispielcode mal umgebaut aber PG verhält sich nicht wie von mir erwartet:
Code:
CREATE TEMP TABLE vLog(
       pk UUID NOT NULL,
       error VARCHAR(100) NOT NULL
       );

DO $$
DECLARE   vPK UUID;
       vData_html VARCHAR(10485760);
       vData_xml XML;
BEGIN
   DECLARE   cursor3 CURSOR FOR
   SELECT   pk,
           data_html
   FROM   "public"."files"
   WHERE   data_xml IS NULL
   LIMIT 3;

   BEGIN
   OPEN cursor3;
   FETCH cursor3 INTO vPK,vData_html;

       INSERT INTO vLog(pk,error) VALUES(vPK,'test');

   CLOSE   cursor3;
   END;
END $$;

SELECT   pk,error
FROM   vLog
ORDER BY pk,error;

DROP TABLE vLog;
PG sagt mir erfolgreich ausgeführt. Ich bekomme aber keine Daten aus dem Select angezeigt.

Man kann in PG wohl keinen Select oder Print oder etwas in der Form innerhalb des Cursors machen, wird mein Select aus dem selben Grund ignoriert? Gibt es eine sinnvollere Vorgehensweise?
 
Werbung:
liefert denn dein Select für den Cursor Daten?

Code:
test=*# \d files
                 Table "public.files"
  Column   |  Type   | Collation | Nullable | Default
-----------+---------+-----------+----------+---------
 pk        | integer |           |          |
 data_html | text    |           |          |
 data_xml  | text    |           |          |

test=*# select * from files;
 pk | data_html | data_xml
----+-----------+----------
  1 | a         |
  2 | a         |
  3 | a         |
  4 | a         |
(4 rows)

test=*# DO $$           
DECLARE   vPK int;
       vData_html VARCHAR(10485760);
       vData_xml XML;
BEGIN
   DECLARE   cursor3 CURSOR FOR
   SELECT   pk,
           data_html
   FROM   "public"."files"
   WHERE   data_xml IS NULL
   LIMIT 3;

   BEGIN
   OPEN cursor3;
   FETCH cursor3 INTO vPK,vData_html;

       INSERT INTO vLog(pk,error) VALUES(vPK,'test');

   CLOSE   cursor3;
   END;
END $$;
DO
test=*# SELECT   pk,error
FROM   vLog
ORDER BY pk,error;
 pk | error
----+-------
  1 | test
  1 | test
(2 rows)

test=*#

(2 mal ausgeführt, daher 2 Einträge)
 
Hmm ja, der Select im Cursor liefert 3 Datensätze. Wenn ich das in PGadmin 4 ausführe kommt als Messages:
DROP TABLE Query returned successfully in 267 msec.
und Data Output bleibt leer.
 
Wieso ein Cursor? Auch in SQL Server ist das normalerweise keine gute Idee.
Das kann man doch mit einem einfach INSERT INTO ... SELECT ... erledigen.

Das Äquivalent zu PRINT wäre ein RAISE NOTICE in PL/pgSQL
 
Der Cursor sorgt nur dafür das die Datensätze nach einander, einzeln abgearbeitet werden. Bei einem Fehler für einen Datensatz wird die ID + Fehlermeldung geloggt und er macht den Rest dennoch weiter. Wenn bei INSERT INTO irgend ein Teil meines Codes mist erzeugt bekomme ich zwar einen Fehler aber eben nicht die Info in welchen Ausgangsdatensatz.
 
Mit RAISE NOTICE bekomme ich eine UUID ausgespuckt, fehlt da irgendwie noch ein FETCH NEXT ... INTO ... im Cursor?

PS: Scheinbar ist hier PGadmin 4 schuld. akretschmer womit hast du das laufen lassen?
 
Der Cursor sorgt nur dafür das die Datensätze nach einander, einzeln abgearbeitet werden. Bei einem Fehler für einen Datensatz wird die ID + Fehlermeldung geloggt und er macht den Rest dennoch weiter.
Das ist eine falsche Annahme. Das läuft alles in einer Transaktion. Und die Definition einer Transaktion ist nunmal, dass alles erfolgreich ist, oder nichts.
Wenn Du dieses Verhalten willst, musst Du mit Savepoints arbeiten
 
Okay stimmt, die Transaktion darf natürlich nicht scheitern. Aber ich kann schon, Datensatz für Datensatz anhand eigener Kriterien, entscheiden ob die Daten valide sind. In MSSQL gibt es z.B. try_convert(), damit kann ich testen ob es sich grundsätzlich um valides XML handelt. Wenn nicht schreibe ich in meine Log-Tabelle und gucke mir die Datensätze händisch an.
 
Im Grunde macht man das via savepoints in Postgres. try_convert() könnte man mit einer eigenen PL/pgSQL Funktion simulieren die einen CAST versucht, die Exception fängt und dann einfach NULL zurückgibt. Exception handling ist aber teuer, und damit wäre die Funktion relativ langsam (ob das ein Problem ist oder nicht kann ich nicht beurteilen). Wenn es nur um Zahlen geht, würde ich vermutlich eher zu einem Test via CASE und einem regex greifen
 
Werbung:
try_convert() war auch erstmal ein Beispiel, es gibt noch andere Validierungen, auch mit LIKE bzw. regexp. Bei dem Cursor (oder Loop oder was auch immer ich am Ende nehme) geht es nur darum alle Datensätze zu durchlaufen, für jeden Datensatz mehrere Prüfungen anzustoßen und ggf. fehlerhafte Datensätze zu identifizieren das ich sie mir angucken kann.
 
Zurück
Oben