PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Więcej moich ulubionych zapytań PostgreSQL – i dlaczego one również mają znaczenie

W poprzednim poście na blogu Moje ulubione zapytania PostgreSQL i dlaczego mają znaczenie, odwiedziłem interesujące mnie zapytania, które mają dla mnie znaczenie, gdy uczę się, rozwijam i dorastam do roli programisty SQL.

Jedna z nich, w szczególności wielowierszowa UPDATE z pojedynczym wyrażeniem CASE, wywołała interesującą rozmowę na Hacker News.

W tym poście na blogu chcę zaobserwować porównania między tym konkretnym zapytaniem a zapytaniem obejmującym wiele pojedynczych instrukcji UPDATE. Na dobre lub na zgubę.

Specyfikacja maszyny/środowiska:

  • Procesor Intel(R) Core(TM) i5-6200U @ 2,30 GHz
  • 8 GB pamięci RAM
  • 1TB pamięci
  • Xubuntu Linux 16.04.3 LTS (Xenial Xerus)
  • PostgreSQL 10.4

Uwaga:na początek utworzyłem tabelę „pomostową” ze wszystkimi kolumnami typu TEKST, aby załadować dane.

Przykładowy zestaw danych, którego używam, znajduje się pod tym linkiem.

Pamiętaj jednak, że w tym przykładzie używane są same dane, ponieważ jest to zestaw o przyzwoitej wielkości z wieloma kolumnami. Wszelkie „analizy” lub AKTUALIZACJE/WSTAWKI do tego zestawu danych nie odzwierciedlają rzeczywistych operacji GPS/GIS „w świecie rzeczywistym” i nie są jako takie przeznaczone.

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)

W tej tabeli mamy około pół miliona wierszy danych.

W tym pierwszym porównaniu Zaktualizuję kolumnę proj_code.

Oto zapytanie eksploracyjne, aby określić jego aktualne wartości:

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

Użyję przycinania, aby usunąć cudzysłowy z wartości i rzucić na INT oraz określić, ile wierszy istnieje dla każdej indywidualnej wartości:

Użyjmy do tego CTE, a następnie WYBIERZ z niego:

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)

Przed uruchomieniem tych testów przejdę dalej i ZMIENIĘ kolumnę proj_code, aby wpisać INTEGER:

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;

I wyczyść tę wartość NULL kolumny (która jest reprezentowana przez ELSE '00' w eksploracyjnym wyrażeniu CASE powyżej), ustawiając ją na dowolną liczbę, 10, za pomocą tej aktualizacji:

UPDATE data_staging
SET proj_code = 10
WHERE proj_code IS NULL;

Teraz wszystkie kolumny proj_code mają wartość INTEGER.

Przejdźmy dalej i uruchom jedno wyrażenie CASE aktualizujące wszystkie wartości kolumny proj_code i zobaczmy, co raportuje czas. Umieszczę wszystkie polecenia w pliku źródłowym .sql dla ułatwienia obsługi.

Oto zawartość pliku:

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;

Uruchommy ten plik i sprawdźmy, jakie raporty czasowe:

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

Nieco ponad pół miliona wierszy w ponad 6 sekund.

Oto dotychczasowe zmiany w tabeli:

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

Cofnę (nie pokazano) te zmiany, abym mógł uruchomić poszczególne instrukcje INSERT, aby je również przetestować.

Poniżej przedstawiono modyfikacje pliku źródłowego .sql dla tej serii porównań:

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;

I te wyniki,

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

Sprawdźmy wartości:

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

I czas (Uwaga:zrobię obliczenia matematyczne w zapytaniu, ponieważ \timing nie zgłosiło całych sekund tego uruchomienia):

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)

Pojedyncza WSTAWKA zajęła około połowę czasu jako pojedynczy PRZYPADEK.

Ten pierwszy test obejmował całą tabelę ze wszystkimi kolumnami. Ciekawią mnie jakiekolwiek różnice w tabeli o tej samej liczbie wierszy, ale mniejszej liczbie kolumn, stąd kolejna seria testów.

Stworzę tabelę z 2 kolumnami (składającą się z typu danych SERIAL dla PRIMARY KEY i INTEGER dla kolumny proj_code) i przesunę dane:

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

(Uwaga:polecenia SQL z pierwszego zestawu operacji są używane z odpowiednimi modyfikacjami. Pomijam je tutaj ze względu na zwięzłość i wyświetlanie na ekranie )

Najpierw uruchomię pojedyncze wyrażenie CASE:

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

A potem poszczególne UPDATE:

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)

Czas pomiędzy obydwoma zestawami operacji na stole z zaledwie 2 kolumnami jest nieco wyrównany.

Powiem, że użycie wyrażenia CASE jest nieco łatwiejsze do napisania, ale niekoniecznie jest to najlepszy wybór na każdą okazję. Podobnie jak w przypadku niektórych komentarzy dotyczących wspomnianego powyżej wątku Hacker News, zwykle „zależy to” od wielu czynników, które mogą, ale nie muszą, być optymalnym wyborem.

Zdaję sobie sprawę, że te testy są w najlepszym razie subiektywne. Jeden z nich, w tabeli z 11 kolumnami, podczas gdy drugi miał tylko 2 kolumny, z których obie były typu danych liczbowych.

Wyrażenie CASE dla aktualizacji wielu wierszy jest nadal jednym z moich ulubionych zapytań, choćby ze względu na łatwość pisania w kontrolowanym środowisku, w którym inną alternatywą jest wiele pojedynczych zapytań UPDATE.

Jednak teraz widzę, gdzie nie zawsze jest to optymalny wybór, ponieważ nadal się rozwijam i uczę.

Jak mówi stare powiedzenie:„Pół tuzina w jednej ręce, 6 w drugiej ”.

Dodatkowe ulubione zapytanie — przy użyciu kursora PLpgSQL CURSOR

Zacząłem przechowywać i śledzić wszystkie statystyki moich ćwiczeń (wędrówek po szlakach) za pomocą PostgreSQL na moim lokalnym komputerze programistycznym. W grę wchodzi wiele tabel, tak jak w przypadku każdej znormalizowanej bazy danych.

Jednak pod koniec miesiąca chcę przechowywać statystyki konkretnych kolumn we własnej, oddzielnej tabeli.

Oto tabela „miesięczna”, której użyję:

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                   |           |          |

Skoncentruję się na wynikach May za pomocą tego zapytania SELECT:

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;

A oto 3 przykładowe wiersze zwrócone z tego zapytania:

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)

Prawdę mówiąc, mogę wypełnić docelową tabelę hiking_month_total za pomocą powyższego zapytania SELECT w instrukcji INSERT.

Ale gdzie jest w tym zabawa?

Zapomnij o nudzie dla funkcji PLpgSQL z kursorem CURSOR.

Wymyśliłem tę funkcję, aby wykonać WSTAWIANIE za pomocą KURSORA:

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;

Wywołajmy funkcję month_total_stats(), aby wykonać INSERT:

fitness=> SELECT monthly_total_stats();
monthly_total_stats
---------------------
(1 row)

Ponieważ funkcja jest zdefiniowana jako RETURNS void, widzimy, że do wywołującego nie jest zwracana żadna wartość.

W tej chwili nie interesują mnie żadne zwracane wartości,

tylko, że funkcja wykonuje określoną operację, wypełniając tabelę hike_month_total.

Zapytam o liczbę rekordów w tabeli docelowej, potwierdzając, że zawiera ona dane:

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

Funkcja month_total_stats() działa, ale być może lepszym przypadkiem użycia dla CURSOR jest przewijanie dużej liczby rekordów. Może stół z około pół miliona płyt?

Ten następny KURSOR jest powiązany z zapytaniem ukierunkowanym na tabelę data_staging z serii porównań w powyższej sekcji:

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;

Następnie, aby użyć tego KURSORA, operuj w ramach TRANSAKCJI (wskazanej w dokumentacji tutaj).

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

Co więc możesz zrobić z tym „”?

Oto tylko kilka rzeczy:

Możemy zwrócić pierwszy wiersz z KURSORA, używając opcji First lub ABSOLUTE 1:

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)

Chcesz wiersz prawie w połowie zestawu wyników? (Zakładając, że wiemy, że około pół miliona wierszy jest powiązanych z KURSOREM.)

Czy możesz być tak „konkretny” z KURSOREM?

Tak.

Możemy pozycjonować i FETCH wartości dla rekordu w wierszu 234888 (tylko losowa liczba, którą wybrałem):

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)

Po umieszczeniu tam możemy przesunąć KURSORA 'do tyłu':

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

Czyli to samo co:

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)

Następnie możemy przenieść KURSORA z powrotem do ABSOLUTE 234888 za pomocą:

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

Przydatna wskazówka:aby zmienić położenie CURSOR, użyj MOVE zamiast FETCH, jeśli nie potrzebujesz wartości z tego wiersza.

Zobacz ten fragment z dokumentacji:

"MOVE zmienia położenie kursora bez pobierania jakichkolwiek danych. MOVE działa dokładnie tak samo, jak polecenie FETCH, z wyjątkiem tego, że ustawia tylko kursor i nie zwraca wierszy."

Nazwa „” jest ogólna i zamiast tego można ją nazwać.

Ponownie przyjrzę się moim statystykom fitness, aby napisać funkcję i nazwać KURSORA wraz z potencjalnym przypadkiem użycia „w świecie rzeczywistym”.

KURSOR skieruje na tę dodatkową tabelę, która przechowuje wyniki nie ograniczone do miesiąca maja (w zasadzie wszystkie, które zebrałem do tej pory), jak w poprzednim przykładzie:

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

Następnie wypełnij go danymi:

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

Teraz z poniższą funkcją PLpgSQL, UTWÓRZ „nazwany” KURSOR:

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

Nazwę ten KURSOR „statystykami”:

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

Załóżmy, że chcę, aby wiersz „12” był powiązany z KURSOREM.

Mogę umieścić KURSORA w tym wierszu, pobierając te wyniki za pomocą poniższego polecenia:

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)

Na potrzeby tego wpisu na blogu wyobraź sobie, że wiem z pierwszej ręki, że wartość kolumny tempa dla tego wiersza jest nieprawidłowa.

Szczególnie pamiętam, że tego dnia byłem "martwy na nogach zmęczony" i podczas tej wędrówki utrzymywałem tylko tempo 3,0. (Hej, zdarza się.)

OK, po prostu Zaktualizuję tabelę cp_hiking_total, aby odzwierciedlić tę zmianę.

Bez wątpienia stosunkowo proste. Nudne…

A co powiesz na statystyki CURSOR?

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

Aby ta zmiana była trwała, wydaj COMMIT:

fitness=> COMMIT;
COMMIT

Zróbmy zapytanie i zobaczmy, że UPDATE odzwierciedlone w tabeli cp_hiking_total:

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)

Jak fajnie to jest?

Poruszaj się w obrębie zestawu wyników KURSORA i w razie potrzeby uruchom UPDATE.

Całkiem potężny, jeśli mnie pytasz. I wygodne.

Trochę „ostrzeżeń” i informacji z dokumentacji tego typu KURSORA:

"Ogólnie zaleca się używanie FOR UPDATE, jeśli kursor ma być używany z UPDATE ... WHERE CURRENT OF lub DELETE ... WHERE CURRENT OF. Użycie FOR UPDATE uniemożliwia innym sesjom zmianę wierszy między godzinami są pobierane i kiedy są aktualizowane. Bez FOR UPDATE kolejne polecenie WHERE CURRENT OF nie zadziała, jeśli wiersz został zmieniony od czasu utworzenia kursora.

Kolejnym powodem użycia FOR UPDATE jest to, że bez niego kolejne WHERE CURRENT OF może się nie powieść, jeśli zapytanie kursora nie spełnia reguł standardu SQL dotyczących „prostej aktualizacji” (w szczególności kursor musi odwoływać się tylko do jednej tabeli i nie używaj grupowania ani ORDER BY). Kursory, których nie można po prostu aktualizować, mogą działać lub nie, w zależności od szczegółów wyboru planu; więc w najgorszym przypadku aplikacja może działać podczas testów, a potem nie działać w środowisku produkcyjnym”.

Używając CURSOR, którego tu użyłem, postępowałem zgodnie ze standardowymi regułami SQL (z powyższych fragmentów) w aspekcie:Odwoływałem się tylko do jednej tabeli, bez grupowania lub klauzuli ORDER by.

Dlaczego to ma znaczenie.

Podobnie jak w przypadku wielu operacji, zapytań lub zadań w PostgreSQL (i ogólnie w SQL), zazwyczaj istnieje więcej niż jeden sposób osiągnięcia celu końcowego. Jest to jeden z głównych powodów, dla których pociąga mnie SQL i staram się dowiedzieć więcej.

Mam nadzieję, że w tym kolejnym wpisie na blogu przedstawiłem pewien wgląd w to, dlaczego wielowierszowa UPDATE z CASE została uwzględniona jako jedno z moich ulubionych zapytań w pierwszym towarzyszącym wpisie na blogu. Po prostu posiadanie tego jako opcji jest dla mnie warte zachodu.

Dodatkowo, eksploracja CURSORS, do przechodzenia przez duże zestawy wyników. Wykonywanie operacji DML, takich jak AKTUALIZACJE i/lub USUWANIE, z odpowiednim typem CURSOR, to tylko „wisienka na torcie”. Z niecierpliwością czekam na dalsze ich przestudiowanie, aby uzyskać więcej przypadków użycia.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Zrozumienie ograniczeń sprawdzania w PostgreSQL

  2. Docelowy czas przywracania Pgbackrest

  3. Konwencje nazewnictwa PostgreSQL

  4. Zwróć wiersze pasujące do elementów tablicy wejściowej w funkcji plpgsql

  5. Dodanie kolumny jako klucza obcego powoduje, że kolumna ERROR, do której odwołuje się ograniczenie klucza obcego, nie istnieje