PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Oracle ke PostgreSQL — Kursor dan Ekspresi Tabel Umum

Sakit ketika Anda melakukan itu, jadi jangan lakukan itu.

Di Oracle, kursor diajarkan sebagai bagian dari pemrograman 101. Dalam banyak kasus (jika tidak sebagian besar), kursor adalah hal pertama yang dipelajari pengembang Oracle. Kelas pertama biasanya dimulai dengan:“Ada 13 struktur logika, yang pertama adalah perulangan, seperti ini…”

PostgreSQL, di sisi lain, tidak terlalu bergantung pada kursor. Ya, mereka ada. Ada beberapa rasa sintaks untuk cara menggunakannya. Saya akan membahas semua desain utama di beberapa titik dalam seri artikel ini. Tetapi pelajaran pertama dalam kursor PostgreSQL adalah bahwa ada beberapa (dan jauh lebih baik) alternatif algoritmik untuk menggunakan kursor di PostgreSQL. Faktanya, dalam 23 tahun berkarir dengan PostgreSQL, saya hanya benar-benar merasa perlu menggunakan kursor dua kali. Dan saya menyesali salah satunya.

Cursors adalah kebiasaan yang mahal.

Iterasi lebih baik daripada perulangan. “Apa bedanya?”, Anda mungkin bertanya. Nah, perbedaannya adalah tentang O(N) vs. O(N^2). Oke, saya akan mengatakannya lagi dalam bahasa Inggris. Kompleksitas menggunakan kursor adalah bahwa mereka mengulang set data menggunakan pola yang sama seperti loop for bersarang. Setiap kumpulan data tambahan meningkatkan kompleksitas total dengan eksponensial. Itu karena setiap kumpulan data tambahan secara efektif membuat loop terdalam lainnya. Dua set data adalah O(N^2), tiga set data adalah O(N^3) dan seterusnya. Membiasakan menggunakan kursor saat ada algoritme yang lebih baik untuk dipilih bisa memakan biaya.

Mereka melakukan ini tanpa pengoptimalan apa pun yang akan tersedia untuk fungsi tingkat yang lebih rendah dari database itu sendiri. Artinya, mereka tidak dapat menggunakan indeks secara signifikan, tidak dapat berubah menjadi sub-pilihan, menarik ke dalam gabungan, atau menggunakan pembacaan paralel. Mereka juga tidak akan mendapat manfaat dari pengoptimalan di masa mendatang yang tersedia dari database. Saya harap Anda adalah seorang grandmaster coder yang selalu mendapatkan algoritme yang benar dan mengkodekannya dengan sempurna pertama kali, karena Anda baru saja mengalahkan salah satu manfaat terpenting dari database relasional. Performa dengan mengandalkan praktik terbaik, atau setidaknya kode orang lain.

Semua orang lebih baik dari Anda. Mungkin tidak secara individu, tetapi secara kolektif hampir pasti. Selain argumen deklaratif vs. imperatif, pengkodean dalam bahasa yang pernah dihapus dari pustaka fungsi yang mendasarinya memungkinkan semua orang mencoba membuat kode Anda berjalan lebih cepat, lebih baik, dan lebih efisien tanpa berkonsultasi dengan Anda. Dan itu sangat, sangat baik untukmu.

Mari kita membuat beberapa data untuk dimainkan.

Kami akan mulai dengan menyiapkan beberapa data untuk dimainkan di beberapa artikel berikutnya.

Isi 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

Ini memberi kita sedikit lebih dari 600 ribu record untuk dimainkan di tabel itis.hierarchy, yang berisi taksonomi alam. Kami akan menggunakan data ini untuk mengilustrasikan berbagai metode dalam menangani interaksi data yang kompleks.

Alternatif pertama.

Pola desain utama saya untuk berjalan di sepanjang kumpulan rekaman sambil melakukan operasi yang mahal adalah Common Table Expression (CTE).

Berikut adalah contoh bentuk dasarnya:

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;

Yang menghasilkan hasil berikut:

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

Kueri ini mudah dimodifikasi untuk melakukan perhitungan apa pun. Itu termasuk pengayaan data, fungsi kompleks, atau apa pun yang diinginkan hati Anda.

"Tapi lihat!", Anda berseru. “Ada tulisan RECURSIVE di sana atas nama! Bukankah itu melakukan persis apa yang Anda katakan untuk tidak dilakukan? ” Yah, sebenarnya tidak. Di bawah tenda, itu tidak menggunakan rekursi dalam arti bersarang atau perulangan untuk melakukan "rekursi". Ini hanya pembacaan linier tabel hingga kueri bawahan gagal mengembalikan hasil baru. Dan itu bekerja dengan indeks juga.

Mari kita lihat rencana eksekusinya:

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

Mari kita lanjutkan dan buat indeks, dan lihat cara kerjanya.

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

Nah, itu memuaskan, bukan? Dan akan sangat sulit untuk membuat indeks dalam kombinasi dengan kursor untuk melakukan pekerjaan yang sama. Struktur ini membawa kita cukup jauh untuk dapat berjalan di struktur pohon yang cukup rumit dan menggunakannya untuk pencarian sederhana.

Dalam angsuran berikutnya, kita akan berbicara tentang metode lain untuk mendapatkan hasil yang sama lebih cepat. Untuk artikel kami berikutnya, kami akan berbicara tentang ltree ekstensi, dan cara melihat data hierarkis dengan sangat cepat. Pantau terus.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana cara mengatur kunci utama kenaikan otomatis di PostgreSQL?

  2. PostgreSQL:Mengapa psql tidak dapat terhubung ke server?

  3. Mengumumkan repmgr 2.0RC2

  4. psql:FATAL:peran postgres tidak ada

  5. Cara menyembunyikan dekorasi set hasil dalam output Psql