sql >> Databáze >  >> RDS >> PostgreSQL

Oracle na PostgreSQL — Kurzory a běžné tabulkové výrazy

Bolí to, když to děláte, tak to nedělejte.

V Oracle jsou kurzory vyučovány jako součást programování 101. V mnoha (ne-li ve většině) případů jsou kurzory první věcí, kterou se vývojář Oracle naučí. První třída obvykle začíná slovy:„Existuje 13 logických struktur, z nichž první je smyčka, která probíhá takto…“

PostgreSQL se na druhou stranu příliš nespoléhá na kurzory. Ano, existují. Existuje několik variant syntaxe, jak je používat. Budu se zabývat všemi hlavními návrhy někdy v této sérii článků. Ale první lekce v PostgreSQL kurzorech je, že existuje poměrně málo (a mnohem lepších) algoritmických alternativ k používání kurzorů v PostgreSQL. Ve skutečnosti jsem za 23letou kariéru s PostgreSQL našel potřebu používat kurzory pouze dvakrát. A jednoho z nich lituji.

Kurzory jsou drahý zvyk.

Iterace je lepší než opakování. "Jaký je rozdíl?", můžete se zeptat. Rozdíl je asi O(N) vs. O(N^2). Dobře, řeknu to znovu anglicky. Složitost použití kurzorů spočívá v tom, že procházejí datovými sadami pomocí stejného vzoru jako vnořená smyčka for. Každý další soubor dat zvyšuje složitost součtu umocněním. Je to proto, že každá další sada dat efektivně vytváří další nejvnitřnější smyčku. Dvě sady dat jsou O(N^2), tři sady dat jsou O(N^3) a tak dále. Zvyknout si používat kurzory, když jsou na výběr lepší algoritmy, může být nákladné.

Dělají to bez jakékoli optimalizace, která by byla k dispozici funkcím nižší úrovně samotné databáze. To znamená, že nemohou žádným významným způsobem používat indexy, nemohou se transformovat do dílčích výběrů, přecházet do spojení nebo používat paralelní čtení. Také nebudou mít prospěch z žádných budoucích optimalizací, které má databáze k dispozici. Doufám, že jste velmistr kodér, který vždy získá správný algoritmus a nakóduje jej perfektně napoprvé, protože jste právě porazili jednu z nejdůležitějších výhod relační databáze. Výkon díky osvědčeným postupům nebo alespoň kódu někoho jiného.

Všichni jsou lepší než ty. Možná ne jednotlivě, ale kolektivně téměř jistě. Kromě argumentu deklarativní vs. imperativ, kódování v jazyce, který je jednou odstraněn ze základní knihovny funkcí, umožňuje všem ostatním pokusit se, aby váš kód běžel rychleji, lépe a efektivněji bez konzultace s vámi. A to je pro vás velmi, velmi dobré.

Pojďme vytvořit data, se kterými si budeme hrát.

Začneme nastavením dat, se kterými si budeme hrát v několika následujících článcích.

Obsah kurzoru.bash:

set -o nounset                              # Treat unset variables as an error
# This script assumes that you have PostgreSQL running locally,
#  that you have a database with the same name as the local user,
#  and that you can create all this structure.
#  If not, then:
#   sudo -iu postgres createuser -s $USER
#   createdb

# Clean up from the last run
[[ -f itisPostgreSql.zip ]] && rm itisPostgreSql.zip
subdirs=$(ls -1 itisPostgreSql* | grep : | sed -e 's/://')
for sub in ${subdirs[@]}
do
    rm -rf $sub
done

# Get the newest file
wget https://www.itis.gov/downloads/itisPostgreSql.zip
# Unpack it
unzip itisPostgreSql.zip
# This makes a directory with the stupidest f-ing name possible
#  itisPostgreSqlDDMMYY
subdir=$(\ls -1 itisPostgreSql* | grep : | sed -e 's/://')
# The script wants to create an "ITIS" database.  Let's just make that a schema.
sed -i $subdir/ITIS.sql -e '/"ITIS"/d'  # Cut the lines about making the db
sed -i $subdir/ITIS.sql -e '/-- PostgreSQL database dump/s/.*/CREATE SCHEMA IF NOT EXISTS itis;/'
sed -i $subdir/ITIS.sql -e '/SET search_path = public, pg_catalog;/s/.*/SET search_path TO itis;/'
# ok, we have a schema to put the data in, let's do the import.
#  timeout if we can't connect, fail on error.
PG_TIMEOUT=5 psql -v "ON_ERROR_STOP=1" -f $subdir/ITIS.sql

To nám dává něco přes 600 000 záznamů, se kterými si můžeme hrát v tabulce itis.hierarchy, která obsahuje taxonomii přírodního světa. Tato data použijeme k ilustraci různých metod řešení složitých datových interakcí.

První alternativa.

Můj vzor návrhu pro procházení po sadách záznamů při provádění drahých operací je Common Table Expression (CTE).

Zde je příklad základního formuláře:

WITH RECURSIVE fauna AS (
    SELECT tsn, parent_tsn, tsn::text taxonomy
    FROM itis.hierarchy
    WHERE parent_tsn = 0
    UNION ALL
    SELECT h1.tsn, h1.parent_tsn, f.taxonomy || '.' || h1.tsn
    FROM itis.hierarchy h1
    JOIN fauna f
    ON h1.parent_tsn = f.tsn
    )
SELECT *
FROM fauna
ORDER BY taxonomy;

Což vede k následujícím výsledkům:

┌─────────┬────────┬──────────────────────────────────────────────────────────┐
│   tsn   │ parent │             taxonomy                                     │
│         │ tsn    │                                                          │
├─────────┼────────┼──────────────────────────────────────────────────────────┤
│  202422 │      0 │202422                                                    │
│  846491 │ 202422 │202422.846491                                             │
│  660046 │ 846491 │202422.846491.660046                                      │
│  846497 │ 660046 │202422.846491.660046.846497                               │
│  846508 │ 846497 │202422.846491.660046.846497.846508                        │
│  846553 │ 846508 │202422.846491.660046.846497.846508.846553                 │
│  954935 │ 846553 │202422.846491.660046.846497.846508.846553.954935          │
│    5549 │ 954935 │202422.846491.660046.846497.846508.846553.954935.5549     │
│    5550 │   5549 │202422.846491.660046.846497.846508.846553.954935.5549.5550│
│  954936 │ 846553 │202422.846491.660046.846497.846508.846553.954936          │
│  954904 │ 660046 │202422.846491.660046.954904                               │
│  846509 │ 954904 │202422.846491.660046.954904.846509                        │
│   11473 │ 846509 │202422.846491.660046.954904.846509.11473                  │
│   11474 │  11473 │202422.846491.660046.954904.846509.11473.11474            │
│   11475 │  11474 │202422.846491.660046.954904.846509.11473.11474.11475      │
│   ...   │        │...snip...                                                │
└─────────┴────────┴──────────────────────────────────────────────────────────┘
(601187 rows)

Tento dotaz je snadno modifikovatelný pro provádění libovolných výpočtů. To zahrnuje obohacování dat, komplexní funkce nebo cokoli jiného, ​​po čem vaše srdce touží.

"Ale podívej!" zvoláš. „Je tam uvedeno RECURSIVE přímo v názvu! Nedělá to přesně to, co jsi řekl, že ne?" No, vlastně ne. Pod kapotou nepoužívá k provádění „rekurze“ rekurzi ve vnořeném smyslu ani smyčkování. Je to jen lineární čtení tabulky, dokud podřízený dotaz nedokáže vrátit žádné nové výsledky. A funguje to také s indexy.

Podívejme se na plán provádění:

┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                              QUERY PLAN                                              │
├──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Sort  (cost=211750.51..211840.16 rows=35858 width=40)                                                │
│   Output: fauna.tsn, fauna.parent_tsn, fauna.taxonomy                                                │
│   Sort Key: fauna.taxonomy                                                                           │
│   CTE fauna                                                                                          │
│     ->  Recursive Union  (cost=1000.00..208320.69 rows=35858 width=40)                               │
│           ->  Gather  (cost=1000.00..15045.02 rows=18 width=40)                                      │
│                 Output: hierarchy.tsn, hierarchy.parent_tsn, ((hierarchy.tsn)::text)                 │
│                 Workers Planned: 2                                                                   │
│                 ->  Parallel Seq Scan on itis.hierarchy  (cost=0.00..14043.22 rows=8 width=40)       │
│                       Output: hierarchy.tsn, hierarchy.parent_tsn, (hierarchy.tsn)::text             │
│                       Filter: (hierarchy.parent_tsn = 0)                                             │
│           ->  Hash Join  (cost=5.85..19255.85 rows=3584 width=40)                                    │
│                 Output: h1.tsn, h1.parent_tsn, ((f.taxonomy || '.'::text) || (h1.tsn)::text)         │
│                 Hash Cond: (h1.parent_tsn = f.tsn)                                                   │
│                 ->  Seq Scan on itis.hierarchy h1  (cost=0.00..16923.87 rows=601187 width=8)         │
│                       Output: h1.hierarchy_string, h1.tsn, h1.parent_tsn, h1.level, h1.childrencount │
│                 ->  Hash  (cost=3.60..3.60 rows=180 width=36)                                        │
│                       Output: f.taxonomy, f.tsn                                                      │
│                       ->  WorkTable Scan on fauna f  (cost=0.00..3.60 rows=180 width=36)             │
│                             Output: f.taxonomy, f.tsn                                                │
│   ->  CTE Scan on fauna  (cost=0.00..717.16 rows=35858 width=40)                                     │
│         Output: fauna.tsn, fauna.parent_tsn, fauna.taxonomy                                          │
│ JIT:                                                                                                 │
│   Functions: 13                                                                                      │
│   Options: Inlining false, Optimization false, Expressions true, Deforming true                      │
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘

Pojďme si vytvořit index a uvidíme, jak to funguje.

CREATE UNIQUE INDEX taxonomy_parents ON itis.hierarchy (parent_tsn, tsn);

┌─────────────────────────────────────────────────────────────────────────────┐
│                             QUERY PLAN                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│Sort  (cost=135148.13..135237.77 rows=35858 width=40)                        │
│  Output: fauna.tsn, fauna.parent_tsn, fauna.taxonomy                        │
│  Sort Key: fauna.taxonomy                                                   │
│  CTE fauna                                                                  │
│    ->  Recursive Union  (cost=4.56..131718.31 rows=35858 width=40)          │
│          ->  Bitmap Heap Scan on itis.hierarchy  (cost=4.56..74.69 rows=18) │
│              Output: hierarchy.tsn, hierarchy.parent_tsn, (hierarchy.tsn)   │
│                Recheck Cond: (hierarchy.parent_tsn = 0)                     │
│                ->  Bitmap Index Scan on taxonomy_parents                    │
│                                                   (cost=0.00..4.56 rows=18) │
│                      Index Cond: (hierarchy.parent_tsn = 0)                 │
│          ->  Nested Loop  (cost=0.42..13092.65 rows=3584 width=40)          │
│                Output: h1.tsn, h1.parent_tsn,((f.taxonomy || '.')||(h1.tsn))│
│                ->  WorkTable Scan on fauna f  (cost=0.00..3.60 rows=180)    │
│                      Output: f.tsn, f.parent_tsn, f.taxonomy                │
│                ->  Index Only Scan using taxonomy_parents on itis.hierarchy │
│                                   h1  (cost=0.42..72.32 rows=20 width=8)    │
│                      Output: h1.parent_tsn, h1.tsn                          │
│                      Index Cond: (h1.parent_tsn = f.tsn)                    │
│  ->  CTE Scan on fauna  (cost=0.00..717.16 rows=35858 width=40)             │
│        Output: fauna.tsn, fauna.parent_tsn, fauna.taxonomy                  │
│JIT:                                                                         │
│  Functions: 6                                                               │
└─────────────────────────────────────────────────────────────────────────────┘

No, to bylo zadostiučinění, ne? A bylo by neúnosně těžké vytvořit index v kombinaci s kurzorem, který by vykonával stejnou práci. Tato struktura nás dostane dostatečně daleko, abychom byli schopni projít poměrně složitou stromovou strukturou a použít ji pro jednoduché vyhledávání.

V příštím díle si povíme o další metodě, jak dosáhnout stejného výsledku ještě rychleji. V našem dalším článku budeme hovořit o rozšíření ltree a o tom, jak úžasně rychle nahlížet na hierarchická data. Zůstaňte naladěni.


  1. 5 tipů, jak udržet databázi v bezpečí

  2. Udržování pořádku v dotazu MySQL IN

  3. Vyberte hodnotu prvku xml v Oracle

  4. Nelze odečíst data-časy, které nejsou naivní a využívající offset