PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Oracle към PostgreSQL — Курсори и общи таблични изрази

Боли, когато правиш това, така че не го прави.

В Oracle курсорите се преподават като част от програмиране 101. В много (ако не и повечето) случаи курсорите са първото нещо, което разработчикът на Oracle научава. Първият клас обикновено започва с:„Има 13 логически структури, първата от които е цикълът, който върви така...“

PostgreSQL, от друга страна, не разчита силно на курсори. Да, съществуват. Има няколко варианта на синтаксис за това как да ги използвате. Ще покрия всички основни дизайни в даден момент от тази серия от статии. Но първият урок в PostgreSQL курсорите е, че има доста (и много по-добри) алгоритмични алтернативи за използването на курсори в PostgreSQL. Всъщност, в 23-годишната си кариера с PostgreSQL, всъщност открих нужда да използвам курсори само два пъти. И съжалявам за едно от тях.

Курсорите са скъп навик.

Итерацията е по-добра от цикъла. "Каква е разликата?", може да попитате. Е, разликата е около O(N) спрямо O(N^2). Добре, ще го кажа отново на английски. Сложността на използването на курсори е, че те преминават през набори от данни, използвайки същия модел като вложен цикъл for. Всеки допълнителен набор от данни повишава сложността на общата сума чрез степенуване. Това е така, защото всеки допълнителен набор от данни ефективно създава друг най-вътрешен цикъл. Два набора от данни са O(N^2), три набора от данни са O(N^3) и така нататък. Създаването на навик да използвате курсори, когато има по-добри алгоритми за избор, може да струва скъпо.

Те правят това без никакви оптимизации, които биха били достъпни за функциите на по-ниско ниво на самата база данни. Тоест, те не могат да използват индекси по какъвто и да е значителен начин, не могат да се трансформират в подселекции, да изтеглят нагоре в обединения или да използват паралелно четене. Те също така няма да се възползват от бъдещи оптимизации, които базата данни има. Надявам се, че сте гросмайстор кодер, който винаги получава правилния алгоритъм и го кодира перфектно от първия път, защото току-що победихте едно от най-важните предимства на релационната база данни. Ефективност, като се разчита на най-добрите практики или поне на кода на някой друг.

Всеки е по-добър от теб. Може би не поотделно, но колективно почти сигурно. Освен аргумента декларативен срещу императивен, кодирането на език, който веднъж е премахнат от основната библиотека с функции, позволява на всички останали да се опитват да накарат вашия код да работи по-бързо, по-добре и по-ефективно, без да се консултирате с вас. И това е много, много добре за вас.

Нека направим някои данни, с които да играем.

Ще започнем, като настроим някои данни, с които да играем в следващите няколко статии.

Съдържание на 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

Това ни дава малко над 600 000 записи, с които да играем в таблицата itis.hierarchy, която съдържа таксономия на естествения свят. Ще използваме тези данни, за да илюстрираме различни методи за справяне със сложни взаимодействия с данни.

Първата алтернатива.

Моят предпочитан модел за проектиране за ходене по записи, докато извършвам скъпи операции, е Common Table Expression (CTE).

Ето пример за основната форма:

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;

Което дава следните резултати:

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

Тази заявка се променя лесно за извършване на всякакви изчисления. Това включва обогатяване на данни, сложни функции или всичко друго, което сърцето ви желае.

„Но вижте!”, възкликвате вие. „Пише RECURSIVE точно там в името! Не прави ли точно това, което казахте да не правите?" Е, всъщност не. Под капака той не използва рекурсия във вложения смисъл или цикъл за изпълнение на „рекурсията“. Това е просто линейно четене на таблицата, докато подчинената заявка не успее да върне нови резултати. И работи с индекси.

Нека разгледаме плана за изпълнение:

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

Нека да продължим и да създадем индекс и да видим как работи това.

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

Е, това беше задоволително, нали? И би било изключително трудно да се създаде индекс в комбинация с курсор, за да се извърши същата работа. Тази структура ни отвежда достатъчно далеч, за да можем да обхождаме доста сложна дървовидна структура и да я използваме за лесни търсения.

В следващата част ще говорим за друг метод за получаване на същия резултат още по-бързо. За следващата ни статия ще говорим за разширението ltree и как да разгледаме йерархичните данни невероятно бързо. Останете на линия.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Избройте колони с индекси в PostgreSQL

  2. Entity Framework 6 с Npgsql

  3. Разлика между индекса GiST и GIN

  4. Запазете изхода от sql функция в csv файл (COPY) с динамично име на файл

  5. Как да архивирате и възстановите PostgreSQL база данни