Bạn rất đau khi làm điều đó, vì vậy đừng làm vậy.
Trong Oracle, con trỏ được dạy như một phần của lập trình 101. Trong nhiều trường hợp (nếu không phải là hầu hết), con trỏ là thứ đầu tiên mà nhà phát triển Oracle học được. Lớp đầu tiên thường bắt đầu bằng:“Có 13 cấu trúc logic, cấu trúc đầu tiên là vòng lặp, diễn ra như thế này…”
Mặt khác, PostgreSQL không phụ thuộc nhiều vào con trỏ. Vâng, chúng tồn tại. Có một số hương vị cú pháp cho cách sử dụng chúng. Tôi sẽ đề cập đến tất cả các thiết kế chính tại một số điểm trong loạt bài viết này. Nhưng bài học đầu tiên về con trỏ PostgreSQL là có một vài lựa chọn thay thế thuật toán (và tốt hơn nhiều) để sử dụng con trỏ trong PostgreSQL. Trên thực tế, trong 23 năm làm việc với PostgreSQL, tôi chỉ thực sự nhận thấy nhu cầu sử dụng con trỏ hai lần. Và tôi rất tiếc một trong số đó.
Con trỏ là một thói quen tốn kém.
Lặp lại tốt hơn lặp lại. "Sự khác biệt là gì?", Bạn có thể hỏi. Chà, sự khác biệt là về O (N) so với O (N ^ 2). Được, tôi sẽ nói lại điều đó bằng tiếng Anh. Sự phức tạp của việc sử dụng con trỏ là chúng lặp qua các tập dữ liệu bằng cách sử dụng cùng một mẫu như một vòng lặp for lồng nhau. Mỗi tập dữ liệu bổ sung làm tăng độ phức tạp của tổng số theo lũy thừa. Đó là bởi vì mỗi tập dữ liệu bổ sung tạo ra một vòng lặp trong cùng khác một cách hiệu quả. Hai tập dữ liệu là O (N ^ 2), ba tập dữ liệu là O (N ^ 3), v.v. Tập thói quen sử dụng con trỏ khi có các thuật toán tốt hơn để lựa chọn có thể tốn kém.
Họ làm điều này mà không có bất kỳ tối ưu hóa nào có sẵn cho các chức năng cấp thấp hơn của chính cơ sở dữ liệu. Có nghĩa là, họ không thể sử dụng chỉ mục theo bất kỳ cách nào đáng kể, không thể chuyển đổi thành các lựa chọn phụ, kéo lên thành liên kết hoặc sử dụng đọc song song. Họ cũng sẽ không được hưởng lợi từ bất kỳ tối ưu hóa nào trong tương lai mà cơ sở dữ liệu có sẵn. Tôi hy vọng bạn là một lập trình viên đại kiện tướng luôn có được thuật toán chính xác và viết mã nó một cách hoàn hảo ngay lần đầu tiên, bởi vì bạn vừa đánh bại một trong những lợi ích quan trọng nhất của cơ sở dữ liệu quan hệ. Hiệu suất bằng cách dựa trên các phương pháp hay nhất hoặc ít nhất là mã của người khác.
Mọi người đều tốt hơn bạn. Có thể không riêng lẻ, nhưng tập thể gần như chắc chắn. Ngoài đối số khai báo so với mệnh lệnh, viết mã bằng ngôn ngữ đã từng bị xóa khỏi thư viện hàm cơ bản cho phép mọi người khác cố gắng làm cho mã của bạn chạy nhanh hơn, tốt hơn và hiệu quả hơn mà không cần hỏi ý kiến bạn. Và điều đó rất, rất tốt cho bạn.
Hãy tạo một số dữ liệu để chơi.
Chúng ta sẽ bắt đầu bằng cách thiết lập một số dữ liệu để xem xét trong một vài bài viết tiếp theo.
Nội dung của 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
Điều này cung cấp cho chúng tôi một chút hơn 600 nghìn bản ghi để chơi trong bảng itis.hierarchy, bảng này chứa phân loại của thế giới tự nhiên. Chúng tôi sẽ sử dụng dữ liệu này để minh họa nhiều phương pháp xử lý các tương tác dữ liệu phức tạp.
Giải pháp thay thế đầu tiên.
Mẫu thiết kế cần thiết của tôi để đi dọc các tập bản ghi trong khi thực hiện các thao tác tốn kém là Biểu thức Bảng Chung (CTE).
Đây là một ví dụ về biểu mẫu cơ bản:
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;
Điều nào tạo ra các kết quả sau:
┌─────────┬────────┬──────────────────────────────────────────────────────────┐
│ 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)
Truy vấn này có thể dễ dàng sửa đổi để thực hiện bất kỳ phép tính nào. Điều đó bao gồm làm giàu dữ liệu, các chức năng phức tạp hoặc bất kỳ thứ gì khác mà trái tim bạn mong muốn.
“Nhưng hãy nhìn xem!”, Bạn thốt lên. “Nó nói rằng RECURSIVE
ngay có trong tên! Có phải nó đang làm chính xác những gì bạn đã nói là không nên làm không? " Thực ra là không. Bên dưới, nó không sử dụng đệ quy theo nghĩa lồng nhau hoặc vòng lặp để thực hiện "đệ quy". Nó chỉ là một phép đọc tuyến tính của bảng cho đến khi truy vấn cấp dưới không trả lại bất kỳ kết quả mới nào. Và nó cũng hoạt động với các chỉ mục.
Hãy xem kế hoạch thực hiện:
┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 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 │
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘
Hãy tiếp tục và tạo một chỉ mục và xem nó hoạt động như thế nào.
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 │
└─────────────────────────────────────────────────────────────────────────────┘
Chà, điều đó thật hài lòng, phải không? Và thật khó để tạo một chỉ mục kết hợp với con trỏ để thực hiện công việc tương tự. Cấu trúc này giúp chúng ta đủ xa để có thể đi qua một cấu trúc cây khá phức tạp và sử dụng nó để tra cứu đơn giản.
Trong phần tiếp theo, chúng ta sẽ nói về một phương pháp khác để đạt được kết quả tương tự thậm chí còn nhanh hơn. Đối với bài viết tiếp theo của chúng tôi, chúng tôi sẽ nói về ltree tiện ích mở rộng và cách xem dữ liệu phân cấp một cách nhanh chóng đáng kinh ngạc. Hãy theo dõi.