W naszym ostatnim artykule o kursorach w PostgreSQL, mówiliśmy o ommonable wyrażeniach (CTE). Dziś nadal odkrywamy nowe alternatywy dla kursorów, korzystając z mniej znanej funkcji PostgreSQL.
Wykorzystamy dane, które zaimportowaliśmy w poprzednim artykule (link powyżej). Poczekam chwilę, abyś wykonał tam procedurę.
Zrozumiałeś? Dobrze.
Dane są wykresem taksonomii świata przyrody. Jako przypomnienie z podstaw biologii liceum, dane te są zorganizowane przez Karola Linneusza w Królestwo, Gromadę, Klasę, Porządek, Rodzinę, Rodzaj i Gatunek. Oczywiście nauka posunęła się nieznacznie do przodu w ciągu ostatnich 250 lat, więc wykres taksonomiczny ma głębokość 21 poziomów. Drzewo hierarchii znajdujemy w tabeli, która (nie dziwi) nazywa się itis.hierarchy
.
Tematem tego artykułu jest wykorzystanie ltrees w PostgreSQL. W szczególności, jak ich używać do bardzo wydajnego przemierzania złożonego zestawu rekordów. W tym sensie możemy uznać je za kolejny surogat kursorów.
Dane nie są selekcjonowane (niestety dla nas) w formacie l-drzewa, więc zamierzamy je trochę przerobić ze względu na artykuł.
Najpierw musisz zainstalować ltree w bazie danych, której używasz do śledzenia tego artykułu. Oczywiście, aby instalować rozszerzenia, musisz być superużytkownikiem.
CREATE EXTENSION IF NOT EXISTS ltree;
Teraz użyjemy tego rozszerzenia, aby zapewnić bardzo wydajne wyszukiwania. Będziemy musieli przekształcić dane w tabelę przeglądową. Aby przeprowadzić tę transformację, użyjemy techniki CTE, którą omówiliśmy w poprzednim artykule. Po drodze do drzewa taksonomicznego dodamy nazwy łacińskie i nazwy angielskie. Pomoże nam to wyszukać elementy według numerów, nazw łacińskich lub nazw angielskich.
-- We need a little helper function to strip out illegal label names. CREATE OR REPLACE FUNCTION strip_label(thelabel text) RETURNS TEXT AS $$ -- make sure all the characters in the label are legal SELECT SELECT regexp_replace( regexp_replace( regexp_replace( regexp_replace( -- strip anything not alnum (yes, this could be way more accurate) thelabel, '[^[:alnum:]]', '_','g'), -- consolidate underscores '_+', '_', 'g'), -- strip leading/trailing underscores '^_*', '', 'g'), '_*$', '', 'g'); $$ LANGUAGE sql; CREATE MATERIALIZED VIEW itis.world_view AS WITH RECURSIVE world AS ( -- Start with the basic kingdoms SELECT h1.tsn, h1.parent_tsn, h1.tsn::text numeric_taxonomy, -- There is no guarantee that there will be a textual name COALESCE(l1.completename,h1.tsn::text,'')::text latin_taxonomy, -- and again no guarantee of a common english name COALESCE(v1.vernacular_name, lower(l1.completename),h1.tsn::text,'unk')::text english_taxonomy FROM itis.hierarchy h1 LEFT JOIN itis.longnames l1 ON h1.tsn = l1.tsn LEFT JOIN itis.vernaculars v1 ON (h1.tsn, 'English') = (v1.tsn, v1.language) WHERE h1.parent_tsn = 0 UNION ALL SELECT h1.tsn, h1.parent_tsn, w1.numeric_taxonomy || '.' || h1.tsn, w1.latin_taxonomy || '.' || COALESCE(strip_label(l1.completename), h1.tsn::text,'unk'), w1.english_taxonomy || '.' || strip_label(COALESCE(v1.vernacular_name, lower(l1.completename), h1.tsn::text, 'unk')) FROM itis.hierarchy h1 JOIN world w1 ON h1.parent_tsn = w1.tsn LEFT JOIN itis.longnames l1 ON h1.tsn = l1.tsn LEFT JOIN -- just change this to "itis.vernaculars v1" to allow mulitples and all languages. (Millions of records.) (SELECT tsn, min(vernacular_name) vernacular_name FROM itis.vernaculars WHERE language = 'English' GROUP BY tsn) v1 ON (h1.tsn) = (v1.tsn) ) SELECT w2.tsn, w2.parent_tsn, w2.numeric_taxonomy::ltree, w2.latin_taxonomy::ltree latin_taxonomy, w2.english_taxonomy::ltree english_taxonomy FROM world w2 ORDER BY w2.numeric_taxonomy WITH NO DATA;
Zatrzymajmy się na chwilę i powąchajmy kwiaty w tym zapytaniu. Na początek stworzyliśmy go bez wypełniania danymi. Daje nam to szansę na załatwienie wszelkich problemów składniowych przed wygenerowaniem dużej ilości bezużytecznych danych. Używamy iteracyjnej natury wspólnego wyrażenia tabelowego, aby złożyć tutaj dość głęboką strukturę i możemy ją łatwo rozszerzyć, aby obejmowała więcej języków, dodając dane do tabeli wernakularnej. Zmaterializowany widok ma również kilka interesujących cech wydajności. Obcina i odbudowuje tabelę za każdym razem, gdy REFRESH MATERIALIZED VIEW
nazywa się.
To, co zamierzamy teraz zrobić, to odświeżyć nasz światopogląd. Przede wszystkim dlatego, że od czasu do czasu jest to zdrowe. Ale w tym przypadku to, co faktycznie robi, to wypełnienie zmaterializowanego widoku danymi z itis
schemat.
REFRESH MATERIALIZED VIEW itis.world_view;
Utworzenie ponad 600 000 wierszy z danych zajmie kilka minut.
Kilka pierwszych wierszy będzie wyglądać tak:
┌────────────┬─────────┬───────────────────────────────────────────────────────────────────────────────┐
│ parent_tsn │ tsn │ english_taxonomy │
├────────────┼─────────┼───────────────────────────────────────────────────────────────────────────────┤
│ 768374 │ 1009037 │ animals.bilateria.protostomia.ecdysozoa.arthropods.hexapods.insects.winged_in…│
│ │ │…sects.modern_wing_folding_insects.holometabola.ants.ants.aculeata.apoid_wasps…│
│ │ │….cicadakillers.crabroninae.larrini.gastrosericina.gastrosericus.gastrosericus…│
│ │ │…_xanthophilus │
│ 768374 │ 1009038 │ animals.bilateria.protostomia.ecdysozoa.arthropods.hexapods.insects.winged_in…│
│ │ │…sects.modern_wing_folding_insects.holometabola.ants.ants.aculeata.apoid_wasps…│
│ │ │….cicadakillers.crabroninae.larrini.gastrosericina.gastrosericus.gastrosericus…│
│ │ │…_zoyphion │
│ 768374 │ 1009039 │ animals.bilateria.protostomia.ecdysozoa.arthropods.hexapods.insects.winged_in…│
│ │ │…sects.modern_wing_folding_insects.holometabola.ants.ants.aculeata.apoid_wasps…│
│ │ │….cicadakillers.crabroninae.larrini.gastrosericina.gastrosericus.gastrosericus…│
│ │ │…_zyx │
│ 768216 │ 768387 │ animals.bilateria.protostomia.ecdysozoa.arthropods.hexapods.insects.winged_in…│
│ │ │…sects.modern_wing_folding_insects.holometabola.ants.ants.aculeata.apoid_wasps…│
│ │ │….cicadakillers.crabroninae.larrini.gastrosericina.holotachysphex │
│ 768387 │ 1009040 │ animals.bilateria.protostomia.ecdysozoa.arthropods.hexapods.insects.winged_in…│
│ │ │…sects.modern_wing_folding_insects.holometabola.ants.ants.aculeata.apoid_wasps…│
│ │ │….cicadakillers.crabroninae.larrini.gastrosericina.holotachysphex.holotachysph…│
│ │ │…ex_holognathus │
└────────────┴─────────┴───────────────────────────────────────────────────────────────────────────────┘
W taksonomii wykres wyglądałby mniej więcej tak:
Oczywiście miałby on głębokość 21 poziomów i łącznie ponad 600 000 rekordów.
Teraz przechodzimy do części zabawnej! Drzewa zapewniają sposób na wykonanie bardzo złożonych zapytań w hierarchii. Pomoc do tego znajduje się w dokumentacji PostgreSQL, więc nie będziemy się tutaj zagłębiać. Dla (bardzo szybkiego) zrozumienia każdy segment drzewa nazywa się etykietą. Tak więc to drzewo kingdom.phylum.class.order.family.genus.species
ma 7 etykiet.
Zapytania przeciwko ltree używają specjalnej notacji, która przypomina wyrażenia regularne w ograniczonej formie.
Oto prosty przykład:Animalia.*.Homo_sapiens
Zatem zapytanie mające na celu odnalezienie ludzkości na świecie wyglądałoby tak:
SELECT tsn, parent_tsn, latin_taxonomy, english_taxonomy
FROM itis.world_view WHERE latin_taxonomy ~ 'Animalia.*.Homo_sapiens';
Co daje oczekiwane:
┌────────┬────────────┬────────────────────────────────────────────────┬─────────────────────────────────────────────┐
│ tsn │ parent_tsn │ latin_taxonomy │ english_taxonomy │
├────────┼────────────┼────────────────────────────────────────────────┼─────────────────────────────────────────────┤
│ 180092 │ 180091 │ Animalia.Bilateria.Deuterostomia.Chordata.Vert…│ animals.bilateria.deuterostomia.chordates.v…│
│ │ │…ebrata.Gnathostomata.Tetrapoda.Mammalia.Theria…│…ertebrates.gnathostomata.tetrapoda.mammals.…│
│ │ │….Eutheria.Primates.Haplorrhini.Simiiformes.Hom…│…theria.eutheria.primates.haplorrhini.simiif…│
│ │ │…inoidea.Hominidae.Homininae.Homo.Homo_sapiens │…ormes.hominoidea.Great_Apes.African_apes.ho…│
│ │ │ │…minoids.Human │
└────────┴────────────┴────────────────────────────────────────────────┴─────────────────────────────────────────────┘
Oczywiście PostgreSQL nigdy by tego nie porzucił. Istnieje obszerny zestaw operatorów, indeksów, przekształceń i przykładów.
Przyjrzyj się szerokiemu wachlarzowi możliwości, które odblokowuje ta technika.
Teraz wyobraź sobie tę technikę stosowaną do innych złożonych typów danych, takich jak numery części, numery identyfikacyjne pojazdów, struktury zestawień materiałowych lub dowolny inny system klasyfikacji. Nie jest konieczne udostępnianie tej struktury użytkownikowi końcowemu ze względu na zbyt skomplikowaną krzywą uczenia się, aby używać jej bezpośrednio. Ale jest całkowicie możliwe zbudowanie ekranu „wyszukiwania” opartego na takiej strukturze, która jest bardzo potężna i ukrywa złożoność implementacji.
W naszym następnym artykule z tej serii będziemy badać użycie wtyczek w językach. W kontekście wyszukiwania alternatyw dla kursorów w PostgreSQL, użyjemy wybranego przez nas języka, aby zamodelować dane w sposób najbardziej odpowiedni dla naszych potrzeb. Do zobaczenia następnym razem!