PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Mehr meiner bevorzugten PostgreSQL-Abfragen – und warum sie ebenfalls wichtig sind

In einem früheren Blogbeitrag Meine bevorzugten PostgreSQL-Abfragen und warum sie wichtig sind, habe ich interessante Abfragen besucht, die für mich von Bedeutung sind, während ich lerne, mich entwickle und in eine SQL-Entwicklerrolle hineinwachse.

Eines davon, insbesondere ein mehrzeiliges UPDATE mit einem einzigen CASE-Ausdruck, löste eine interessante Unterhaltung auf Hacker News aus.

In diesem Blogbeitrag möchte ich Vergleiche zwischen dieser speziellen Abfrage und einer Abfrage mit mehreren einzelnen UPDATE-Anweisungen beobachten. Zum Guten oder zum Bösen.

Maschinen-/Umgebungsspezifikationen:

  • Intel(R) Core(TM) i5-6200U CPU @ 2,30 GHz
  • 8 GB Arbeitsspeicher
  • 1 TB Speicher
  • Xubuntu Linux 16.04.3 LTS (Xenial Xerus)
  • PostgreSQL 10.4

Hinweis:Zu Beginn habe ich eine 'Staging'-Tabelle mit allen Spalten vom Typ TEXT erstellt, um die Daten zu laden.

Den von mir verwendeten Beispieldatensatz finden Sie unter diesem Link hier.

Denken Sie jedoch daran, dass die Daten selbst in diesem Beispiel verwendet werden, da es sich um einen recht großen Satz mit mehreren Spalten handelt. Jegliche 'Analyse' oder UPDATES/INSERTS zu diesem Datensatz spiegeln nicht den tatsächlichen GPS/GIS-Betrieb in der 'realen Welt' wider und sind nicht als solche gedacht.

location=# \d data_staging;
               Table "public.data_staging"
    Column     |  Type   | Collation | Nullable | Default 
---------------+---------+-----------+----------+---------
 segment_num   | text    |           |          | 
 point_seg_num | text    |           |          | 
 latitude      | text    |           |          | 
 longitude     | text    |           |          | 
 nad_year_cd   | text    |           |          | 
 proj_code     | text    |           |          | 
 x_cord_loc    | text    |           |          | 
 y_cord_loc    | text    |           |          | 
 last_rev_date | text    |           |          | 
 version_date  | text    |           |          | 
 asbuilt_flag  | text    |           |          | 

location=# SELECT COUNT(*) FROM data_staging;
count
--------
546895
(1 row)

Wir haben etwa eine halbe Million Datenzeilen in dieser Tabelle.

Für diesen ersten Vergleich werde ich die proj_code-Spalte AKTUALISIEREN.

Hier ist eine explorative Abfrage, um die aktuellen Werte zu ermitteln:

location=# SELECT DISTINCT proj_code FROM data_staging;
proj_code
-----------
"70"
""
"72"
"71"
"51"
"15"
"16"
(7 rows)

Ich werde trim verwenden, um Anführungszeichen aus den Werten zu entfernen und in ein INT umzuwandeln und zu bestimmen, wie viele Zeilen für jeden einzelnen Wert vorhanden sind:

Lassen Sie uns dafür einen CTE verwenden, dann SELECT daraus:

location=# WITH cleaned_nums AS (
SELECT NULLIF(trim(both '"' FROM proj_code), '') AS p_code FROM data_staging
)
SELECT COUNT(*),
CASE
WHEN p_code::int = 70 THEN '70'
WHEN p_code::int = 72 THEN '72'
WHEN p_code::int = 71 THEN '71'
WHEN p_code::int = 51 THEN '51'
WHEN p_code::int = 15 THEN '15'
WHEN p_code::int = 16 THEN '16'
ELSE '00'
END AS proj_code_num
FROM cleaned_nums
GROUP BY p_code
ORDER BY p_code DESC;
count  | proj_code_num
--------+---------------
353087 | 0
139057 | 72
25460  | 71
3254   | 70
1      | 51
12648  | 16
13388  | 15
(7 rows)

Bevor ich diese Tests ausführe, ändere ich die Spalte proj_code, um INTEGER:

einzugeben
BEGIN;
ALTER TABLE data_staging ALTER COLUMN proj_code SET DATA TYPE INTEGER USING NULLIF(trim(both '"' FROM proj_code), '')::INTEGER;
SAVEPOINT my_save;
COMMIT;

Und bereinigen Sie diesen NULL-Spaltenwert (der durch ELSE '00' im obigen explorativen CASE-Ausdruck dargestellt wird), indem Sie ihn mit diesem UPDATE:

auf eine beliebige Zahl, 10, setzen
UPDATE data_staging
SET proj_code = 10
WHERE proj_code IS NULL;

Jetzt haben alle proj_code-Spalten einen INTEGER-Wert.

Lassen Sie uns fortfahren und einen einzelnen CASE-Ausdruck ausführen, der alle proj_code-Spaltenwerte aktualisiert, und sehen, was das Timing meldet. Ich werde alle Befehle zur einfacheren Handhabung in einer .sql-Quelldatei platzieren.

Hier sind die Dateiinhalte:

BEGIN;
\timing on
UPDATE data_staging
SET proj_code =
(
CASE proj_code
WHEN 72 THEN 7272
WHEN 71 THEN 7171
WHEN 15 THEN 1515
WHEN 51 THEN 5151
WHEN 70 THEN 7070
WHEN 10 THEN 1010
WHEN 16 THEN 1616
END
)
WHERE proj_code IN (72, 71, 15, 51, 70, 10, 16);
SAVEPOINT my_save;

Lassen Sie uns diese Datei ausführen und prüfen, was das Timing meldet:

location=# \i /case_insert.sql
BEGIN
Time: 0.265 ms
Timing is on.
UPDATE 546895
Time: 6779.596 ms (00:06.780)
SAVEPOINT
Time: 0.300 ms

Etwas mehr als eine halbe Million Zeilen in mehr als 6 Sekunden.

Hier sind die bisherigen Änderungen in der Tabelle:

location=# SELECT DISTINCT proj_code FROM data_staging;
proj_code
-----------
7070
1616
1010
7171
1515
7272
5151
(7 rows)

Ich werde diese Änderungen rückgängig machen (nicht gezeigt), damit ich einzelne INSERT-Anweisungen ausführen kann, um diese ebenfalls zu testen.

Nachfolgend sind die Änderungen an der .sql-Quelldatei für diese Vergleichsreihe aufgeführt:

BEGIN;
\timing on

UPDATE data_staging
SET proj_code = 7222
WHERE proj_code = 72;

UPDATE data_staging
SET proj_code = 7171
WHERE proj_code = 71;

UPDATE data_staging
SET proj_code = 1515
WHERE proj_code = 15;

UPDATE data_staging
SET proj_code = 5151
WHERE proj_code = 51;

UPDATE data_staging
SET proj_code = 7070
WHERE proj_code = 70;

UPDATE data_staging
SET proj_code = 1010
WHERE proj_code = 10;

UPDATE data_staging
SET proj_code = 1616
WHERE proj_code = 16;
SAVEPOINT my_save;

Und diese Ergebnisse,

location=# \i /case_insert.sql
BEGIN
Time: 0.264 ms
Timing is on.
UPDATE 139057
Time: 795.610 ms
UPDATE 25460
Time: 116.268 ms
UPDATE 13388
Time: 239.007 ms
UPDATE 1
Time: 72.699 ms
UPDATE 3254
Time: 162.199 ms
UPDATE 353087
Time: 1987.857 ms (00:01.988)
UPDATE 12648
Time: 321.223 ms
SAVEPOINT
Time: 0.108 ms

Lassen Sie uns die Werte überprüfen:

location=# SELECT DISTINCT proj_code FROM data_staging;
proj_code
-----------
7222
1616
7070
1010
7171
1515
5151
(7 rows)

Und das Timing (Anmerkung:Ich werde in einer Abfrage nachrechnen, da \timing bei diesem Durchlauf keine ganzen Sekunden gemeldet hat):

location=# SELECT round((795.610 + 116.268 + 239.007 + 72.699 + 162.199 + 1987.857 + 321.223) / 1000, 3) AS seconds;
seconds
---------
3.695
(1 row)

Die einzelnen INSERT's dauerten etwa halb so lange wie die einzelnen CASE.

Dieser erste Test umfasste die gesamte Tabelle mit allen Spalten. Ich bin neugierig auf Unterschiede in einer Tabelle mit der gleichen Anzahl von Zeilen, aber weniger Spalten, daher die nächste Testreihe.

Ich erstelle eine Tabelle mit 2 Spalten (zusammengesetzt aus einem SERIAL-Datentyp für den PRIMARY KEY und einem INTEGER für die proj_code-Spalte) und bewege die Daten:

location=# CREATE TABLE proj_nums(n_id SERIAL PRIMARY KEY, proj_code INTEGER);
CREATE TABLE
location=# INSERT INTO proj_nums(proj_code) SELECT proj_code FROM data_staging;
INSERT 0 546895

(Hinweis:SQL-Befehle aus dem ersten Satz von Operationen werden mit der/den entsprechenden Modifikation(en) verwendet. Ich lasse sie hier aus Gründen der Kürze und Anzeige auf dem Bildschirm weg. )

Ich führe zuerst den einzelnen CASE-Ausdruck aus:

location=# \i /case_insert.sql
BEGIN
Timing is on.
UPDATE 546895
Time: 4355.332 ms (00:04.355)
SAVEPOINT
Time: 0.137 ms

Und dann die einzelnen UPDATE's:

location=# \i /case_insert.sql
BEGIN
Time: 0.282 ms
Timing is on.
UPDATE 139057
Time: 1042.133 ms (00:01.042)
UPDATE 25460
Time: 123.337 ms
UPDATE 13388
Time: 212.698 ms
UPDATE 1
Time: 43.107 ms
UPDATE 3254
Time: 52.669 ms
UPDATE 353087
Time: 2787.295 ms (00:02.787)
UPDATE 12648
Time: 99.813 ms
SAVEPOINT
Time: 0.059 ms
location=# SELECT round((1042.133 + 123.337 + 212.698 + 43.107 + 52.669 + 2787.295 + 99.813) / 1000, 3) AS seconds;
seconds
---------
4.361
(1 row)

Das Timing ist zwischen beiden Gruppen von Operationen in der Tabelle mit nur 2 Spalten etwas gleichmäßig.

Ich werde sagen, dass die Verwendung des CASE-Ausdrucks etwas einfacher zu tippen ist, aber nicht unbedingt die beste Wahl für alle Gelegenheiten. Wie bereits in einigen Kommentaren zum Hacker News-Thread, auf den oben verwiesen wurde, "hängt" es normalerweise "nur" von vielen Faktoren ab, welche die optimale Wahl sein kann oder nicht.

Mir ist klar, dass diese Tests bestenfalls subjektiv sind. Eine davon auf einer Tabelle mit 11 Spalten, während die andere nur 2 Spalten hatte, die beide vom Datentyp Zahl waren.

Der CASE-Ausdruck für mehrere Zeilenaktualisierungen ist immer noch eine meiner Lieblingsabfragen, wenn auch nur wegen der einfachen Eingabe in einer kontrollierten Umgebung, in der viele einzelne UPDATE-Abfragen die andere Alternative sind.

Allerdings sehe ich jetzt, wo es nicht immer die optimale Wahl ist, während ich weiter wachse und lerne.

Wie das alte Sprichwort sagt:"Ein halbes Dutzend in der einen Hand, 6 in der anderen ."

Eine zusätzliche bevorzugte Abfrage - Verwendung von PLpgSQL CURSORs

Ich habe damit begonnen, alle meine Trainingsstatistiken (Wandern) mit PostgreSQL auf meinem lokalen Entwicklungscomputer zu speichern und zu verfolgen. Wie bei jeder normalisierten Datenbank sind mehrere Tabellen beteiligt.

Am Monatsende möchte ich jedoch die Statistiken bestimmter Spalten in einer eigenen, separaten Tabelle speichern.

Hier ist die 'monatliche' Tabelle, die ich verwenden werde:

fitness=> \d hiking_month_total;
                     Table "public.hiking_month_total"
     Column      |          Type          | Collation | Nullable | Default 
-----------------+------------------------+-----------+----------+---------
 day_hiked       | date                   |           |          | 
 calories_burned | numeric(4,1)           |           |          | 
 miles           | numeric(4,2)           |           |          | 
 duration        | time without time zone |           |          | 
 pace            | numeric(2,1)           |           |          | 
 trail_hiked     | text                   |           |          | 
 shoes_worn      | text                   |           |          |

Ich werde mich mit dieser SELECT-Abfrage auf die Ergebnisse von May konzentrieren:

fitness=> SELECT hs.day_walked, hs.cal_burned, hs.miles_walked, hs.duration, hs.mph, tr.name, sb.name_brand
fitness-> FROM hiking_stats AS hs
fitness-> INNER JOIN hiking_trail AS ht
fitness-> ON hs.hike_id = ht.th_id
fitness-> INNER JOIN trail_route AS tr
fitness-> ON ht.tr_id = tr.trail_id
fitness-> INNER JOIN shoe_brand AS sb
fitness-> ON hs.shoe_id = sb.shoe_id
fitness-> WHERE extract(month FROM hs.day_walked) = 5
fitness-> ORDER BY hs.day_walked ASC;

Und hier sind 3 Beispielzeilen, die von dieser Abfrage zurückgegeben werden:

day_walked | cal_burned | miles_walked | duration | mph | name | name_brand
------------+------------+--------------+----------+-----+------------------------+---------------------------------------
2018-05-02 | 311.2 | 3.27 | 00:57:13 | 3.4 | Tree Trail-extended | New Balance Trail Runners-All Terrain
2018-05-03 | 320.8 | 3.38 | 00:58:59 | 3.4 | Sandy Trail-Drive | New Balance Trail Runners-All Terrain
2018-05-04 | 291.3 | 3.01 | 00:53:33 | 3.4 | House-Power Line Route | Keen Koven WP(keen-dry)
(3 rows)

Um ehrlich zu sein, ich kann die Zieltabelle Hiking_month_total mit der obigen SELECT-Abfrage in einer INSERT-Anweisung füllen.

Aber wo bleibt da der Spaß?

Ich verzichte stattdessen auf Langeweile für eine PLpgSQL-Funktion mit einem CURSOR.

Ich habe mir diese Funktion ausgedacht, um das INSERT mit einem CURSOR auszuführen:

CREATE OR REPLACE function monthly_total_stats()
RETURNS void
AS $month_stats$
DECLARE
v_day_walked date;
v_cal_burned numeric(4, 1);
v_miles_walked numeric(4, 2);
v_duration time without time zone;
v_mph numeric(2, 1);
v_name text;
v_name_brand text;
v_cur CURSOR for SELECT hs.day_walked, hs.cal_burned, hs.miles_walked, hs.duration, hs.mph, tr.name, sb.name_brand
FROM hiking_stats AS hs
INNER JOIN hiking_trail AS ht
ON hs.hike_id = ht.th_id
INNER JOIN trail_route AS tr
ON ht.tr_id = tr.trail_id
INNER JOIN shoe_brand AS sb
ON hs.shoe_id = sb.shoe_id
WHERE extract(month FROM hs.day_walked) = 5
ORDER BY hs.day_walked ASC;
BEGIN
OPEN v_cur;
<<get_stats>>
LOOP
FETCH v_cur INTO v_day_walked, v_cal_burned, v_miles_walked, v_duration, v_mph, v_name, v_name_brand;
EXIT WHEN NOT FOUND;
INSERT INTO hiking_month_total(day_hiked, calories_burned, miles,
duration, pace, trail_hiked, shoes_worn)
VALUES(v_day_walked, v_cal_burned, v_miles_walked, v_duration, v_mph, v_name, v_name_brand);
END LOOP get_stats;
CLOSE v_cur;
END;
$month_stats$ LANGUAGE PLpgSQL;

Rufen wir die Funktionmonthly_total_stats() auf, um INSERT:

auszuführen
fitness=> SELECT monthly_total_stats();
monthly_total_stats
---------------------
(1 row)

Da die Funktion RETURNS void definiert ist, können wir sehen, dass kein Wert an den Aufrufer zurückgegeben wird.

Momentan interessiere ich mich nicht speziell für Rückgabewerte,

nur dass die Funktion die definierte Operation ausführt und die Tabelle walking_month_total füllt.

Ich werde die Anzahl der Datensätze in der Zieltabelle abfragen und bestätigen, dass sie Daten enthält:

fitness=> SELECT COUNT(*) FROM hiking_month_total;
count
-------
25
(1 row)

Die Funktion month_total_stats() funktioniert, aber vielleicht ist ein besserer Anwendungsfall für einen CURSOR das Scrollen durch eine große Anzahl von Datensätzen. Vielleicht eine Tabelle mit rund einer halben Million Datensätze?

Dieser nächste CURSOR ist mit einer Abfrage verbunden, die auf die Tabelle data_staging aus der Vergleichsreihe im obigen Abschnitt abzielt:

CREATE OR REPLACE FUNCTION location_curs()
RETURNS refcursor
AS $location$
DECLARE
v_cur refcursor;
BEGIN
OPEN v_cur for SELECT segment_num, latitude, longitude, proj_code, asbuilt_flag FROM data_staging;
RETURN v_cur;
END;
$location$ LANGUAGE PLpgSQL;

Um diesen CURSOR zu verwenden, müssen Sie dann innerhalb einer TRANSAKTION arbeiten (hier in der Dokumentation angegeben).

location=# BEGIN;
BEGIN
location=# SELECT location_curs();
location_curs 
--------------------
<unnamed portal 1>
(1 row)

Was können Sie also mit diesem "" machen?

Hier sind nur ein paar Dinge:

Wir können die erste Zeile aus dem CURSOR zurückgeben, indem wir entweder first oder ABSOLUTE 1:

verwenden
location=# FETCH first FROM "<unnamed portal 1>";
segment_num | latitude | longitude | proj_code | asbuilt_flag 
-------------+------------------+-------------------+-----------+--------------
" 3571" | " 29.0202942600" | " -90.2908612800" | 72 | "Y"
(1 row)

location=# FETCH ABSOLUTE 1 FROM "<unnamed portal 1>";
segment_num | latitude | longitude | proj_code | asbuilt_flag 
-------------+------------------+-------------------+-----------+--------------
" 3571" | " 29.0202942600" | " -90.2908612800" | 72 | "Y"
(1 row)

Willst du eine Reihe fast zur Hälfte der Ergebnismenge? (Angenommen, wir wissen, dass schätzungsweise eine halbe Million Zeilen an den CURSOR gebunden sind.)

Können Sie mit einem CURSOR so 'spezifisch' sein?

Ja.

Wir können die Werte für den Datensatz in Zeile 234888 positionieren und abrufen (nur eine Zufallszahl, die ich gewählt habe):

location=# FETCH ABSOLUTE 234888 FROM "<unnamed portal 1>";
segment_num | latitude | longitude | proj_code | asbuilt_flag 
-------------+------------------+-------------------+-----------+--------------
" 11261" | " 28.1159541400" | " -90.7778003500" | 10 | "Y"
(1 row)

Einmal dort positioniert, können wir den CURSOR 'eins nach hinten' bewegen:

location=# FETCH BACKWARD FROM "<unnamed portal 1>";
segment_num | latitude | longitude | proj_code | asbuilt_flag 
-------------+------------------+-------------------+-----------+--------------
" 11261" | " 28.1159358200" | " -90.7778242300" | 10 | "Y"
(1 row)

Was dasselbe ist wie:

location=# FETCH ABSOLUTE 234887 FROM "<unnamed portal 1>";
segment_num | latitude | longitude | proj_code | asbuilt_flag 
-------------+------------------+-------------------+-----------+--------------
" 11261" | " 28.1159358200" | " -90.7778242300" | 10 | "Y"
(1 row)

Dann können wir den CURSOR direkt zurück zum ABSOLUTE 234888 bewegen mit:

location=# FETCH FORWARD FROM "<unnamed portal 1>";
segment_num | latitude | longitude | proj_code | asbuilt_flag 
-------------+------------------+-------------------+-----------+--------------
" 11261" | " 28.1159541400" | " -90.7778003500" | 10 | "Y"
(1 row)

Praktischer Tipp:Um den CURSOR neu zu positionieren, verwenden Sie MOVE anstelle von FETCH, wenn Sie die Werte aus dieser Zeile nicht benötigen.

Siehe diese Passage aus der Dokumentation:

"MOVE positioniert einen Cursor neu, ohne Daten abzurufen. MOVE funktioniert genau wie der FETCH-Befehl, außer dass er nur den Cursor positioniert und keine Zeilen zurückgibt."

Der Name „“ ist generisch und kann stattdessen „benannt“ sein.

Ich werde meine Fitnessstatistikdaten erneut aufrufen, um eine Funktion zu schreiben und den CURSOR zu benennen, zusammen mit einem potenziellen Anwendungsfall aus der "realen Welt".

Der CURSOR zielt auf diese zusätzliche Tabelle ab, die Ergebnisse speichert, die nicht auf den Monat Mai beschränkt sind (im Grunde alle, die ich bisher gesammelt habe), wie im vorherigen Beispiel:

fitness=> CREATE TABLE cp_hiking_total AS SELECT * FROM hiking_month_total WITH NO DATA;
CREATE TABLE AS

Füllen Sie es dann mit Daten:

fitness=> INSERT INTO cp_hiking_total 
SELECT hs.day_walked, hs.cal_burned, hs.miles_walked, hs.duration, hs.mph, tr.name, sb.name_brand
FROM hiking_stats AS hs
INNER JOIN hiking_trail AS ht
ON hs.hike_id = ht.th_id
INNER JOIN trail_route AS tr
ON ht.tr_id = tr.trail_id
INNER JOIN shoe_brand AS sb
ON hs.shoe_id = sb.shoe_id
ORDER BY hs.day_walked ASC;
INSERT 0 51

Erstellen Sie nun mit der folgenden PLpgSQL-Funktion einen „benannten“ CURSOR:

CREATE OR REPLACE FUNCTION stats_cursor(refcursor)
RETURNS refcursor
AS $$
BEGIN
OPEN $1 FOR
SELECT *
FROM cp_hiking_total;
RETURN $1;
END;
$$ LANGUAGE plpgsql;

Ich nenne diesen CURSOR „Statistiken“:

fitness=> BEGIN;
BEGIN
fitness=> SELECT stats_cursor('stats');
stats_cursor 
--------------
stats
(1 row)

Angenommen, ich möchte die '12.' Zeile an den CURSOR binden.

Ich kann den CURSOR auf dieser Zeile positionieren und diese Ergebnisse mit dem folgenden Befehl abrufen:

fitness=> FETCH ABSOLUTE 12 FROM stats;
day_hiked | calories_burned | miles | duration | pace | trail_hiked | shoes_worn 
------------+-----------------+-------+----------+------+---------------------+---------------------------------------
2018-05-02 | 311.2 | 3.27 | 00:57:13 | 3.4 | Tree Trail-extended | New Balance Trail Runners-All Terrain
(1 row)

Stellen Sie sich für die Zwecke dieses Blogbeitrags vor, ich wüsste aus erster Hand, dass der Wert der Pace-Spalte für diese Zeile falsch ist.

Ich erinnere mich besonders, dass ich an diesem Tag „todmüde“ war und während dieser Wanderung nur ein Tempo von 3,0 beibehalten habe. (Hey, es passiert.)

Okay, ich aktualisiere einfach die cp_hiking_total-Tabelle, um diese Änderung widerzuspiegeln.

Relativ einfach, kein Zweifel. Langweilig…

Wie wäre es stattdessen mit dem Statistik-CURSOR?

fitness=> UPDATE cp_hiking_total
fitness-> SET pace = 3.0
fitness-> WHERE CURRENT OF stats;
UPDATE 1

Um diese Änderung dauerhaft zu machen, geben Sie COMMIT:

aus
fitness=> COMMIT;
COMMIT

Lassen Sie uns abfragen und sehen, dass sich UPDATE in der Tabelle cp_hiking_total widerspiegelt:

fitness=> SELECT * FROM cp_hiking_total
fitness-> WHERE day_hiked = '2018-05-02';
day_hiked | calories_burned | miles | duration | pace | trail_hiked | shoes_worn 
------------+-----------------+-------+----------+------+---------------------+---------------------------------------
2018-05-02 | 311.2 | 3.27 | 00:57:13 | 3.0 | Tree Trail-extended | New Balance Trail Runners-All Terrain
(1 row)

Wie cool ist das?

Bewegen Sie sich innerhalb der Ergebnismenge des CURSORs und führen Sie bei Bedarf ein UPDATE durch.

Ziemlich mächtig, wenn Sie mich fragen. Und bequem.

Einige „Vorsicht“ und Informationen aus der Dokumentation zu diesem CURSOR-Typ:

"Es wird allgemein empfohlen, FOR UPDATE zu verwenden, wenn der Cursor mit UPDATE ... WHERE CURRENT OF oder DELETE ... WHERE CURRENT OF verwendet werden soll. Die Verwendung von FOR UPDATE verhindert, dass andere Sitzungen die Zeilen zwischenzeitlich ändern Sie werden abgerufen und die Zeit, zu der sie aktualisiert werden.Ohne FOR UPDATE hat ein nachfolgender WHERE CURRENT OF-Befehl keine Wirkung, wenn die Zeile geändert wurde, seit der Cursor erstellt wurde.

Ein weiterer Grund für die Verwendung von FOR UPDATE ist, dass ohne es ein nachfolgendes WHERE CURRENT OF fehlschlagen könnte, wenn die Cursor-Abfrage nicht den Regeln des SQL-Standards für „einfach aktualisierbar“ entspricht (insbesondere muss der Cursor nur auf eine Tabelle verweisen und Verwenden Sie keine Gruppierung oder ORDER BY). Cursor, die nicht einfach aktualisierbar sind, können abhängig von den Details der Planauswahl funktionieren oder nicht; Im schlimmsten Fall könnte eine Anwendung also beim Testen funktionieren und dann in der Produktion fehlschlagen."

Mit dem CURSOR, den ich hier verwendet habe, habe ich die SQL-Standardregeln (aus den obigen Passagen) in folgendem Aspekt befolgt:Ich habe nur auf eine Tabelle verwiesen, ohne Gruppierung oder ORDER by-Klausel.

Warum es wichtig ist.

Wie bei zahlreichen Operationen, Abfragen oder Aufgaben in PostgreSQL (und SQL im Allgemeinen) gibt es normalerweise mehr als einen Weg, um Ihr Endziel zu erreichen und zu erreichen. Das ist einer der Hauptgründe, warum ich mich zu SQL hingezogen fühle und danach strebe, mehr zu lernen.

Ich hoffe, dass ich durch diesen nachfolgenden Blog-Beitrag einen Einblick gegeben habe, warum das mehrzeilige UPDATE mit CASE als eine meiner Lieblingsabfragen in diesem ersten begleitenden Blog-Beitrag enthalten war. Es lohnt sich für mich einfach, es als Option zu haben.

Erkunden Sie außerdem CURSORS, um große Ergebnismengen zu durchlaufen. Das Ausführen von DML-Operationen wie UPDATES und/oder DELETES mit dem richtigen CURSOR-Typ ist nur das „Sahnehäubchen“. Ich bin bestrebt, sie für weitere Anwendungsfälle weiter zu studieren.