sql >> Database >  >> RDS >> PostgreSQL

Oracle naar PostgreSQL — Cursors en algemene tabeluitdrukkingen

Het doet pijn als je dat doet, dus doe dat niet.

In Oracle worden cursors aangeleerd als onderdeel van het programmeren van 101. In veel (zo niet de meeste) gevallen zijn cursors het eerste dat de Oracle-ontwikkelaar leert. De eerste les begint meestal met:"Er zijn 13 logische structuren, waarvan de eerste de lus is, die als volgt gaat..."

PostgreSQL daarentegen is niet sterk afhankelijk van cursors. Ja, ze bestaan. Er zijn verschillende syntaxissmaken voor het gebruik ervan. Ik zal op een bepaald punt in deze serie artikelen alle belangrijke ontwerpen behandelen. Maar de eerste les in PostgreSQL-cursors is dat er nogal wat (en veel betere) algoritmische alternatieven zijn voor het gebruik van cursors in PostgreSQL. In een carrière van 23 jaar bij PostgreSQL heb ik eigenlijk maar twee keer de behoefte gevonden om cursors te gebruiken. En ik heb er spijt van.

Cursors zijn een dure gewoonte.

Itereren is beter dan herhalen. “Wat is het verschil?”, vraag je je misschien af. Welnu, het verschil is ongeveer O(N) versus O(N^2). Oké, ik zeg dat nog een keer in het Engels. De complexiteit van het gebruik van cursors is dat ze door datasets lopen met hetzelfde patroon als een geneste for-lus. Elke extra dataset verhoogt de complexiteit van het totaal door machtsverheffing. Dat komt omdat elke extra dataset in feite een andere binnenste lus creëert. Twee datasets zijn O(N^2), drie datasets zijn O(N^3), enzovoort. Er een gewoonte van maken om cursors te gebruiken wanneer er betere algoritmen zijn om uit te kiezen, kan kostbaar zijn.

Ze doen dit zonder de optimalisaties die beschikbaar zouden zijn voor functies op een lager niveau van de database zelf. Dat wil zeggen, ze kunnen geen indexen op een significante manier gebruiken, kunnen niet transformeren in subselecties, omhoog trekken in joins of parallelle reads gebruiken. Ze zullen ook niet profiteren van toekomstige optimalisaties die de database beschikbaar heeft. Ik hoop dat je een grootmeester-programmeur bent die altijd het juiste algoritme krijgt en het de eerste keer perfect codeert, omdat je zojuist een van de belangrijkste voordelen van een relationele database hebt verslagen. Prestaties door te vertrouwen op best practices, of in ieder geval de code van iemand anders.

Iedereen is beter dan jij. Misschien niet individueel, maar collectief vrijwel zeker. Afgezien van het declaratieve versus imperatieve argument, stelt codering in een taal die eenmaal is verwijderd uit de onderliggende functiebibliotheek, iedereen in staat om te proberen uw code sneller, beter en efficiënter te laten werken zonder u te raadplegen. En dat is heel, heel goed voor je.

Laten we wat gegevens maken om mee te spelen.

We beginnen met het instellen van enkele gegevens om mee te spelen in de komende artikelen.

Inhoud van cursors.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

Dit geeft ons iets meer dan 600K records om mee te spelen in de itis.hierarchy-tabel, die een taxonomie van de natuurlijke wereld bevat. We zullen deze gegevens gebruiken om verschillende methoden te illustreren voor het omgaan met complexe gegevensinteracties.

Het eerste alternatief.

Mijn favoriete ontwerppatroon om langs recordsets te lopen terwijl ik dure bewerkingen doe, is de Common Table Expression (CTE).

Hier is een voorbeeld van het basisformulier:

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;

Wat de volgende resultaten oplevert:

┌─────────┬────────┬──────────────────────────────────────────────────────────┐
│   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)

Deze query kan eenvoudig worden aangepast om berekeningen uit te voeren. Dat omvat gegevensverrijking, complexe functies of iets anders dat uw hartje begeert.

“Maar kijk!”, roep je uit. 'Er staat RECURSIVE daar in de naam! Doet het niet precies wat je zei niet te doen?” Nou, eigenlijk niet. Onder de motorkap gebruikt het geen recursie in de geneste zin of looping om de "recursie" uit te voeren. Het is gewoon een lineaire lezing van de tabel totdat de ondergeschikte query geen nieuwe resultaten oplevert. En het werkt ook met indexen.

Laten we eens kijken naar het uitvoeringsplan:

┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                              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                      │
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘

Laten we doorgaan en een index maken en kijken hoe dat werkt.

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                                                               │
└─────────────────────────────────────────────────────────────────────────────┘

Nou, dat was bevredigend, nietwaar? En het zou onbetaalbaar zijn geweest om een ​​index te maken in combinatie met een cursor om hetzelfde werk te doen. Deze structuur brengt ons ver genoeg om een ​​vrij complexe boomstructuur te kunnen doorlopen en deze te gebruiken voor eenvoudige opzoekingen.

In de volgende aflevering zullen we het hebben over een andere methode om nog sneller hetzelfde resultaat te krijgen. Voor ons volgende artikel zullen we het hebben over de extensie ltree, en hoe je verbazingwekkend snel naar hiërarchische gegevens kunt kijken. Blijf op de hoogte.


  1. juiste slaapstand-annotatie voor byte[]

  2. Onbewerkte SQL-querystring ophalen uit door PDO voorbereide instructies

  3. MySQL-gebruikersdatabase heeft geen wachtwoordkolommen - MySQL installeren op OSX

  4. Essentiële PostgreSQL-bewaking - deel 2