sql >> Databasteknik >  >> RDS >> PostgreSQL

Oracle till PostgreSQL — Markörer och vanliga tabelluttryck

Det gör ont när du gör det, så gör inte det.

I Oracle lärs markörer ut som en del av programmering 101. I många (om inte de flesta) fall är markörer det första som Oracle-utvecklaren lär sig. Den första klassen börjar vanligtvis med:"Det finns 13 logiska strukturer, varav den första är loopen, som går så här..."

PostgreSQL, å andra sidan, är inte mycket beroende av markörer. Ja, de finns. Det finns flera syntaxsmaker för hur man använder dem. Jag kommer att täcka alla de stora designerna någon gång i den här artikelserien. Men den första lektionen i PostgreSQL-markörer är att det finns en hel del (och mycket bättre) algoritmiska alternativ till att använda markörer i PostgreSQL. Faktum är att under en 23-årig karriär med PostgreSQL har jag faktiskt bara funnit ett behov av att använda markörer två gånger. Och jag ångrar en av dem.

Markörer är en dyr vana.

Iteration är bättre än looping. "Vad är skillnaden?", kanske du frågar dig. Tja, skillnaden handlar om O(N) mot O(N^2). Ok, jag säger det igen på engelska. Komplexiteten med att använda markörer är att de går igenom datamängder med samma mönster som en kapslad för loop. Varje ytterligare datamängd ökar komplexiteten hos totalen genom exponentiering. Det beror på att varje ytterligare datamängd effektivt skapar en annan innersta loop. Två datamängder är O(N^2), tre datamängder är O(N^3) och så vidare. Att ta för vana att använda markörer när det finns bättre algoritmer att välja mellan kan bli kostsamt.

De gör detta utan någon av de optimeringar som skulle vara tillgängliga för lägre nivåfunktioner i själva databasen. Det vill säga, de kan inte använda index på något nämnvärt sätt, kan inte omvandla till sub-select, dra upp till joins eller använda parallella läsningar. De kommer inte heller att dra nytta av några framtida optimeringar som databasen har tillgänglig. Jag hoppas att du är en stormästarkodare som alltid får rätt algoritm och kodar den perfekt första gången, för du har precis besegrat en av de viktigaste fördelarna med en relationsdatabas. Prestanda genom att förlita sig på bästa praxis, eller åtminstone någon annans kod.

Alla är bättre än du. Kanske inte individuellt, men kollektivt nästan säkert. Bortsett från det deklarativa kontra imperativa argumentet tillåter kodning på ett språk som en gång tagits bort från det underliggande funktionsbiblioteket alla andra att försöka få din kod att köras snabbare, bättre och mer effektivt utan att rådfråga dig. Och det är väldigt, väldigt bra för dig.

Låt oss skapa lite data att leka med.

Vi börjar med att ställa in lite data att leka med under de kommande artiklarna.

Innehåll i 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

Detta ger oss lite över 600 000 poster att leka med i tabellen itis.hierarchy, som innehåller en klassificering av den naturliga världen. Vi kommer att använda dessa data för att illustrera olika metoder för att hantera komplexa datainteraktioner.

Det första alternativet.

Mitt bästa designmönster för att gå längs rekordsäten medan jag gör dyra operationer är Common Table Expression (CTE).

Här är ett exempel på grundformen:

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;

Vilket ger följande resultat:

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

Denna fråga är lätt att modifiera för att utföra alla beräkningar. Det inkluderar databerikning, komplexa funktioner eller något annat ditt hjärta önskar.

"Men titta!", utbrister du. "Det står RECURSIVE precis där i namnet! Gör den inte exakt det du sa att du inte skulle göra?" Tja, faktiskt nej. Under huven använder den inte rekursion i kapslad mening eller looping för att utföra "rekursionen". Det är bara en linjär läsning av tabellen tills den underordnade frågan inte ger några nya resultat. Och det fungerar med index också.

Låt oss titta på genomförandeplanen:

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

Låt oss gå vidare och skapa ett index och se hur det fungerar.

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

Tja, det var tillfredsställande, eller hur? Och det skulle ha varit oöverkomligt svårt att skapa ett index i kombination med en markör för att göra samma arbete. Denna struktur tar oss tillräckligt långt för att kunna gå en ganska komplex trädstruktur och använda den för enkla uppslagningar.

I nästa avsnitt kommer vi att prata om en annan metod för att få samma resultat ännu snabbare. För vår nästa artikel kommer vi att prata om tillägget ltree och hur man kan titta på hierarkiska data otroligt snabbt. Håll utkik.


  1. Postgresql infoga trigger för att ställa in värde

  2. Hur kan jag få antalet dagar mellan 2 datum i Oracle 11g?

  3. 3 sätt att kontrollera kolumndatatyp i Oracle

  4. Hur kan jag generera en unik sträng per post i en tabell i Postgres?