そうすると痛いので、やめましょう。
Oracleでは、カーソルはプログラミング101の一部として教えられます。多くの場合(ほとんどではないにしても)、カーソルはOracle開発者が最初に学ぶことです。通常、最初のクラスは次のように始まります。「13の論理構造があり、最初のクラスはループです。これは次のようになります…」
一方、PostgreSQLはカーソルに大きく依存していません。はい、それらは存在します。それらの使用方法には、いくつかの構文フレーバーがあります。この記事シリーズのある時点で、すべての主要な設計について説明します。しかし、PostgreSQLカーソルの最初の教訓は、PostgreSQLでカーソルを使用する代わりに、かなりの数の(そしてはるかに優れた)アルゴリズムの選択肢があるということです。実際、PostgreSQLでの23年間のキャリアの中で、私は実際にカーソルを2回使用する必要があることに気づきました。そして、そのうちの1つを後悔しています。
反復はループよりも優れています。 「違いは何ですか?」とあなたは尋ねるかもしれません。違いは、O(N)とO(N ^ 2)の違いです。わかりました、もう一度英語で言います。カーソルを使用することの複雑さは、ネストされたforループと同じパターンを使用してデータセットをループすることです。データセットを追加するたびに、べき乗によって合計の複雑さが増します。これは、追加のデータセットごとに別の最も内側のループが効果的に作成されるためです。 2つのデータセットはO(N ^ 2)、3つのデータセットはO(N ^ 3)などです。より良いアルゴリズムを選択できるときにカーソルを使用する習慣を身につけると、コストがかかる可能性があります。
これらは、データベース自体の下位レベルの機能で利用できる最適化なしでこれを実行します。つまり、インデックスを重要な方法で使用したり、副選択に変換したり、結合にプルアップしたり、並列読み取りを使用したりすることはできません。また、データベースで利用できる将来の最適化の恩恵を受けることもありません。リレーショナルデータベースの最も重要な利点の1つを打ち負かしたばかりなので、常に正しいアルゴリズムを取得し、それを最初に完全にコーディングするグランドマスターコーダーであることを願っています。ベストプラクティス、または少なくとも他の誰かのコードに依存することによるパフォーマンス。
誰もがあなたよりも優れています。個別ではないかもしれませんが、ほぼ確実に集合的に。宣言型と命令型の議論は別として、基礎となる関数ライブラリから一度削除された言語でコーディングすることで、他の誰もがあなたに相談することなく、コードをより速く、より良く、より効率的に実行できるようになります。そして、それはあなたにとって非常に良いことです。
次のいくつかの記事で使用するデータを設定することから始めます。
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
これにより、自然界の分類法が含まれているitis.hierarchyテーブルで再生できる60万を少し超えるレコードが得られます。このデータを使用して、複雑なデータの相互作用を処理するさまざまな方法を説明します。
高価な操作をしながらレコードセットを歩くための私の頼りになる設計パターンは、共通テーブル式(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と、階層データを驚くほどすばやく確認する方法について説明します。しばらくお待ちください。