Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Объектно-реляционная СУБД PostgreSQL. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
787 Кб
Скачать

2) слова «технологии» и «технологиях» функция to_tsvector превращает в одну и ту же лексему, но запоминает, что этой лексеме соответствуют второе и четвёртое слово документа.

Б) Пусть БД «Литература» содержит таблицу Книги с полем Название. Отыщем все книги, в названии которых содержатся слово «технология» и/или его варианты, и/или пара слов «базы данных».

SELECT * from Книги

WHERE to_tsvector('russian', Название) @@ to_tsquery('russian', 'технологии | базы & данных');

Приведенный выше запрос может выполняться достаточно долго. Для уменьшения времени работы запроса можно создать GIN-индекс. Для этого выполним следующий запрос:

CREATE INDEX idx_name ON Книги

USING GIN(to_tsvector('russian', Название));

Теперь запросы на поиск книг по словам из названий будут выполняться быстрее.

5.4. Геометрические типы

PostgreSQL поддерживает целый ряд базовых геометрических типов: точка, линия, отрезок, прямоугольник, круг и т.д. О них говорилось в разделе Типы данных. Рассмотрим их подробнее.

Точка на плоскости задаётся своими координатами x,y, прямоугольник – координатами двух противоположных вершин. При вводе данных можно указать координаты любой пары противоположных вершин, однако для упрощения работы с прямоугольником PostgreSQL по введенным данным определяет и сохраняет в БД сначала координаты правого верхнего, затем – левого нижнего угла.

Операции над геометрическими объектами:

Операция "=" сравнивает геометрические объекты по их площади; Операция "~=" проверяет тождественность (полное совпадение) объектов.

101

Примеры

box '((0,0),(2,2))' = box '((0,0),(1,4))' вернёт true, так как площади этих прямоугольников равны;

box '((0,0),(2,2))' ~= box '((0,0),(1,4))' вернёт false, так как эти прямоугольники не тождественны;

box '((0,4),(1,0))' ~= box '((0,0),(1,4))' вернёт true, так как эти прямоугольники совпадают, хотя и заданы разными парами вершин.

точка <@ объект или объект @> точка – возвращает true, если указанная точка принадлежит объекту (кругу, прямоугольнику, полигону, замкнутому пути);

прямоугольник1 # прямоугольник2 – возвращает пересечение двух прямоугольников в виде прямоугольника, который может быть вырожденным в случае пересечения по линии или одной точке;

объект1 ?# объект2 – возвращает true, если объекты пересекаются; объект1 <-> объект2 – возвращает расстояние между объектами. Примеры:

SELECT box '((0,4),(1,0))' # box '((-1,0),(3,1))' ;

Результат: (1,1),(0,0)

SELECT '21,3'::point <-> '9,-2'::point;

Результат = 13.

SELECT '21,3,2'::circle <-> '9,-2, 1'::circle;

Результат = 10. Результат равен расстоянию между центрами кругов минус сумма радиусов. Если круги касаются или пересекаются, то результат равен нулю.

Для работы с геометрическими объектами PostgreSQL располагает различными функциями, в том числе:

area(объект) – вычисляет площадь прямоугольника, круга или замкнутого пути (полигона) без самопересечений;

length(объект) – вычисляет длину «линейного» объекта.

102

Пример. Рассмотрим таблицу, содержащую условные планы помещений здания. Для простоты будем считать, что все помещения имеют форму прямоугольника. Расположение помещений на схеме задаются координатами правого верхнего и левого нижнего угла в декартовой системе координат, связанной со зданием.

Создадим сначала таблицу Планы (НомерЭтажа, НомерКомнаты, План):

CREATE TABLE Планы ( НомерЭтажа int NOT NULL, НомерКомнаты int NOT NULL, План box

);

ALTER TABLE Планы

ADD CONSTRAINT pk_Планы

PRIMARY KEY(НомерЭтажа, НомерКомнаты);

Теперь заполним её данными:

INSERT INTO Планы VALUES(1, 12, '40,30,10,0'); INSERT INTO Планы VALUES(1, 13, '50,50,10,30'); INSERT INTO Планы VALUES(2, 14, '35,60,15,25');

Заметим, что при вставке текстовая строка автоматически преобразуется к типу поля (box), однако для наглядности можно использовать явное преобразование, например, '40, 30, 10, 0'::box.

Выполним теперь несколько запросов.

1. Вычислим максимальную площадь помещений здания

SELECT max(area(План)) FROM Планы;

2. Выведем все прямоугольники (планы), которые содержат точку с координатами 25, 45.

SELECT * FROM Планы

WHERE '25,45'::point <@ План;

Результат выполнения запроса приведен на рис. 18.

103

Рис. 18. Результат выполнения запроса

3. Выведем пересечение каждого из прямоугольников с заданным.

SELECT План # '45,55,25,45'::box FROM Планы;

(для наглядности использовано явное преобразование к типу box)

4. Напишем функцию для нахождения общего пересечения всех прямоугольников.

CREATE OR REPLACE FUNCTION AllSect() RETURNS box AS $$

DECLARE A box; B box;

BEGIN

SELECT INTO A План FROM Планы LIMIT 1;

FOR B IN (SELECT План FROM Планы) LOOP A = A # B;

END LOOP;

RETURN A; END;

$$ LANGUAGE plpgsql; SELECT AllSect();

5. Напишем функцию, которая получает два прямоугольника и возвращает минимальный накрывающий прямоугольник. Чтобы объяснить это понятие, приведём пример. На рис. 19 изображены два прямоугольника: box '((2,3),(0,0))' и box '((4,2),(1,-1))'. Минимальный накрывающий прямоугольник есть прямоугольник с вершинами с точках A(4,3) и B(0,-1).

104

Рис. 19. Пример минимального накрывающего прямоугольника

Легко видеть, что обе координаты правого верхнего угла (точки A) равны максимальным значениям соответствующих координат верхних правых углов исходных прямоугольников. Аналогично, обе координаты левого нижнего угла (точки B) равны минимальным значениям соответствующих координат левых нижних углов исходных прямоугольников.

Доступ к координатам вершин похож на доступ к элементам массива. Пусть, например, Q есть переменная типа box, а pA и pB – переменные типа point. Присвоим переменной Q конкретное значение:

Q = box(point(50,50), point(10,30));

Тогда pA = Q[0] есть точка с координатами (50,50), pB = Q[1] есть точка с координатами (10,30). Соответственно координаты x, y вершины pA определяются как pA[0], pA[1], а координаты вершины pB определяются как pB[0], pB[1]. Можно также получить координаты вершин прямоугольника без использования «промежуточных» переменных, например, (Q[0])[0], (Q[0])[1] есть координаты точки Q[0], а (Q[1])[0], (Q[1])[1] есть координаты точки Q[1]. Заметим, что круглые скобки в этом случае обязательны, так как, несмотря на некоторое сходство, прямоугольник не является двумерным массивом, и использование синтаксиса Q[0][0] вызывает сообщение об ошибке.

Ниже приведен текст функции. Отметим сразу, что для нахождения наибольших и наименьших значений язык PL/pgSQLе не может использовать функции MAX и MIN, так как это агрегатные функции языка запросов SQL. В

105

PL/pgSQL для этой цели используются функции least (в переводе с английского

– «наименьший») и greatest (в переводе с английского – «наибольший»).

CREATE OR REPLACE FUNCTION qr(Q box, R box) RETURNS box AS $$

DECLARE

xA float; yA float; xB float; yB float; res box;

BEGIN

xA = greatest((Q[0])[0],(R[0])[0]); yA = greatest((Q[0])[1],(R[0])[1]); xB = least((Q[1])[0],(R[1])[0]); yB = least((Q[1])[1],(R[1])[1]);

RETURN box(point(xA,yA), point(xB,yB)); END;

$$ LANGUAGE plpgsql;

Вызовем теперь написанную функцию:

SELECT qr('((0,0), (2,3))'::box, '((1,-1),(4,2))'::box);

и получим ожидаемый результат: "(4,3),(0,-1)" (см. рис. 19).

Заметим, что хотя при вызове координаты вершин передаются в другом порядке, PostgreSQL переформатирует и хранит их в следующем порядке: координаты x,y правого верхнего угла, потом координаты x,y левого нижнего угла прямоугольника.

Для создания сложных геометрических моделей можно использовать связку СУБД PostgreSQL и его расширения PostGIS (аббревиатура GIS расшифровывается как Geographic Information System, в переводе – географические информационные системы, ГИС). Однако рассмотрение возможностей PostGIS выходит за рамки данного пособия.

106

5.5. Наследование

Ещё одной интересной особенностью СУБД PostgreSQL является возможность наследования таблиц. Эта возможность позволяет создавать новые таблицы на основе уже существующих, при этом между таблицами устанавливается связь типа «родитель-наследник».

Синтаксис запроса на создание дочерней таблицы:

CREATE TABLE имя_дочерней_таблицы ( описание дополнительных полей

) INHERITS (имя_родительской_таблицы);

Дочерняя таблица наследует все поля родительской таблицы и добавляет к ним новые, дополнительные поля, однако атрибуты полей родительской таблицы не наследуются дочерней таблицей. Это значит, что ссылки на другие таблицы, ограничения уникальности и другие ограничения в дочерних таблицах следует задавать заново, что создаёт определённые трудности при использовании наследования. Пример наследования таблиц будет рассмотрен в следующей главе.

5.6.Перегрузка операций

ВСУБД PostgreSQL реализована возможность перегрузки операций, т.е., возможность использования имеющихся операций для работы с другими типами данных. Обычно перегрузка операций используется, если программист создаёт свои собственные типы и хочет реализовать операции с объектами этих типов с использованием знаков уже имеющихся в СУБД операций.

Приведём пример. Предположим, что мы хотим «научить» PostgreSQL поэлементно складывать массивы. Напишем функцию, которая получает два одномерных массива и возвращает массив, каждый элемент которого равен сумме соответствующих элементов массивов – аргументов функции. Заметим, что если длины этих массивов разные, то количество элементов в результирующем массиве будет равно длине меньшего массива.

107

CREATE OR REPLACE FUNCTION M_plus_M(ms1 float[], ms2 float[])

RETURNS float[] AS $$ DECLARE

k int; n int; n2 int;

res float[]; BEGIN

n=array_length(ms1,1); n2=array_length(ms2,1); IF n2<n THEN n=n2; END IF; FOR k IN 1..n LOOP

res[k] = ms1[k] + ms2[k]; END LOOP;

RETURN res; END;

$$ LANGUAGE plpgsql;

Теперь можно выполнить любой из вариантов запроса

SELECT M_plus_M(ARRAY[1,8,12], ARRAY[-2,3,1,7]);

или

SELECT M_plus_M('{1,8,12}', '{-2,3,1,7}');

Результат – массив {-1, 11, 13}.

Перегрузим теперь операцию сложения – «приспособим» её для сложения массивов:

CREATE OPERATOR + (

 

 

PROCEDURE = M_plus_M,

-- вызываемая функция

LEFTARG = float[],

--

тип левого операнда

RIGHTARG = float[]

--

тип правого операнда

);

 

 

Теперь, встретив в выражении знак операции сложения, PostgreSQL проанализирует типы операндов и вызовет для выполнения сложения написанную нами функцию M_plus_M. Следовательно, перегрузка операций

108

является не более чем альтернативным способом вызова той же функции. Однако использование перегрузки операции позволяет записать код программы в более простом и понятном виде. Сравните, например, два фрагмента кода, в каждом из которых вычисляется поэлементная сумма четырёх массивов:

res = M_plus_M( M_plus_M( M_plus_M(A, B), C), D);

иres = A + B + C + D;

6.Инструментарий разработчика и администратора

6.1. Анализ выполнения запроса

Исходный текст запроса – это указание, ЧТО хочет получить пользователь, а не КАК это сделать. PostgreSQL сам решает, использовать или не использовать индексы, в каком порядке соединять таблицы, в каком порядке накладывать условия и т.д.

Запросы типа INSERT, UPDATE или DELETE выполняются сразу, если же запрос включает SELECT (т.е., является запросом на выборку), PostgreSQL составляет план запроса, и только после этого запрос может быть выполнен. По существу, план запроса описывает алгоритм его выполнения.

Чтобы увидеть созданный PosgreSQL план запроса, нужно использовать ключевое слово EXPLAIN. Приведём пример.

Пусть в таблице Игры приведены результаты матчей чемпионата мира по футболу. Таблица содержит поля:

НомерИгры – первичный ключ, Команда1, Команда2 – названия команд, сыгравших друг с другом,

Забитых1, Забитых2 – число голов, забитых Командой1, и число голов, забитых Командой2.

Посмотрим, как PosgreSQL будет выполнять запрос «Сколько побед одержала команда 'Бразилия'?»

Текст запроса:

109

EXPLAIN SELECT count(*) As Побед FROM Игры

WHERE Команда1='Бразилия' AND Забитых1>Забитых2 OR Команда2='Бразилия' AND Забитых2>Забитых1;

План запроса:

Aggregate (cost=1.96..1.97 rows=1 width=0)

-> Seq Scan on "Игры" (cost=0.00..1.96 rows=1 width=0)

Filter:

((("Команда1" = 'Бразилия'::text) AND ("Забитых1" > "Забитых2")) OR (("Команда2" = 'Бразилия'::text) AND ("Забитых2" > "Забитых1")))

что в вольном переводе на русский язык означает: применить агрегатную функцию при последовательном просмотре набора записей, удовлетворяющих заданному условию. По существу это переформулированный исходный запрос, но здесь явно выделены этапы выполнения (в плане запроса они перечислены в обратном порядке!).

Первый этап – применение фильтра (Filter) с заданным условием к исходному набору записей.

Второй этап – последовательный просмотр (Seq Scan – от Sequential Scan) полученного набора и вычисление агрегатной функции (Aggregate).

В скобочках приведена предварительная оценка стоимости выполнения (cost) для первой и для всех записей набора, количество просмотренных строк и средний размер строки. Эти данные могут не соответствовать реальным значениям, так как в предложении EXPLAIN запрос на самом деле не выполняется. Чтобы выполнить запрос и получить при этом его план и реальную «стоимость» запроса нужно использовать EXPLAIN ANALYZE в том же запросе. В результате получим:

Aggregate (cost=1.96..1.97 rows=1 width=0)

(actual time=0.056..0.057 rows=1 loops=1)

-> Seq Scan on "Игры" (cost=0.00..1.96 rows=1 width=0) (actual time=0.015..0.042 rows=2 loops=1)

110

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]