
Лаб. 6 СУБД
.docxЛабораторная работа 6
«Язык программирования PL/pgSQL.
Процедуры, функции, триггеры»
-- explain analyze
select fc.student_id, s.surname, s.name, s.patronymic, avg(mark)
from field_comprehension fc
inner join student s on s.student_id = fc.student_id
group by fc.student_id, s.surname, s.name, s.patronymic
order by student_id
-- \timing
do
$$
declare
start_time timestamp;
end_time timestamp;
duration interval;
stud record;
begin
start_time := clock_timestamp();
for stud in
select fc.student_id, s.surname, s.name, s.patronymic, avg(mark) as avg_m
from field_comprehension fc
inner join student s on s.student_id = fc.student_id
group by fc.student_id, s.surname, s.name, s.patronymic
order by student_id
loop
raise notice 'Студенческий: %, Фамилия: %, Имя: %, Отчество: %, Средний балл: %',
stud.student_id, stud.surname, stud.name, stud.patronymic, stud.avg_m;
end loop;
end_time := clock_timestamp();
duration := end_time - start_time;
raise notice 'Execution time: %', duration;
end
$$;
-- \timing off
select p.surname, p.name, p.patronymic, sum(zet) as sum_zet
from professor p
join professor_field pf on p.professor_id = pf.professor_id
join field f on f.field_id = pf.field_id
group by p.surname, p.name, p.patronymic
order by p.surname, p.name, p.patronymic
do
$$
declare
prof record;
begin
for prof in
select p.surname, p.name, p.patronymic, sum(zet) as sum_zet
from professor p
join professor_field pf on p.professor_id = pf.professor_id
join field f on f.field_id = pf.field_id
group by p.surname, p.name, p.patronymic
order by p.surname, p.name, p.patronymic
loop
raise notice 'Фамилия: %, Имя: %, Отчество: %, Сумма ЗЕТ: %',
prof.surname, prof.name, prof.patronymic, prof.sum_zet;
end loop;
end
$$;
select student_id, surname, name, patronymic,
random() * 180 - 90 as latitude, -- Генерация случайной широты
random() * 360 - 180 as longitude -- Генерация случайной долготы
from student
order by student_id
do
$$
declare
stud record;
begin
for stud in
select student_id, surname, name, patronymic,
random() * 180 - 90 as latitude, -- Генерация случайной широты
random() * 360 - 180 as longitude -- Генерация случайной долготы
from student
order by student_id
loop
raise notice 'Студенческий: %, Фамилия: %, Имя: %, Отчество: %, Широта: %, Долгота: %',
stud.student_id, stud.surname, stud.name, stud.patronymic, stud.latitude, stud.longitude;
end loop;
end
$$;
create or replace procedure update_enrolment_status(
new_status varchar,
group_number varchar
)
language plpgsql
as
$$
begin
-- Проверка допустимого значения
if new_status not in ('Очная', 'Заочная', 'Очно-заочная') then
raise exception 'Недопустимый статус обучения: %', new_status;
end if;
-- Обновление
update students_group
set enrolment_status = new_status
where students_group_number = group_number;
if not found then
raise notice 'Группа % не найдена.', group_number;
else
raise notice 'Статус обучения для группы % изменён на "%".', group_number, new_status;
end if;
end;
$$;
call update_enrolment_status('Очно-заочная', 'ИВТ-44');
drop function if exists avg_mark(integer);
drop type if exists student_avg_mark;
create type student_avg_mark as (
student_id integer,
surname varchar(30),
name varchar(30),
patronymic varchar(30),
avg_mark numeric
);
create function avg_mark(student_id_param integer) returns setof student_avg_mark
language sql
as
$$
select fc.student_id, s.surname, s.name, s.patronymic, avg(mark)
from field_comprehension fc
inner join student s on s.student_id = fc.student_id
where fc.student_id = student_id_param
group by fc.student_id, s.surname, s.name, s.patronymic
$$;
select * from avg_mark(813514);
drop function if exists avg_age();
drop type if exists student_avg_age;
create type student_avg_age as (
students_group_number varchar(7),
avg_age numeric
);
create function avg_age() returns setof student_avg_age
language sql
as
$$
select students_group_number, avg(extract(year from age(current_date, birthday))) as avg_age
from student
group by students_group_number
order by students_group_number
$$;
select * from avg_age();
create or replace function check_student_age() returns trigger
language plpgsql
as
$$
begin
if (current_date - NEW.birthday) / 365 > 23 then
raise exception 'Возраст студента % превышает 23 года', NEW.surname;
end if;
-- Если возраст нормальный, операция продолжается
return NEW;
end
$$;
create or replace trigger canceling_age_change
before insert or update on student
for each row execute function check_student_age();
create or replace function check_duplicate_professor() returns trigger
language plpgsql
as
$$
begin
if exists (
select 1
from professor p
where levenshtein(lower(p.surname), lower(NEW.surname)) <= 2
and levenshtein(lower(p.name), lower(NEW.name)) <= 2
and levenshtein(lower(p.patronymic), lower(NEW.patronymic)) <= 2
) then
raise exception 'Преподаватель с похожими фамилией, именем и отчеством уже существует.';
end if;
return NEW;
end
$$;
create or replace trigger prevent_duplicate_professor
before insert on professor
for each row execute function check_duplicate_professor();
create or replace function check_salary_limit() returns trigger
language plpgsql
as
$$
begin
if NEW.salary::numeric > 120000 then
raise exception 'Зарплата тренера не может превышать 120000.';
end if;
return NEW;
end
$$;
create or replace trigger prevent_high_salary
before insert or update on coach
for each row execute function check_salary_limit();
Контрольные вопросы
В чем отличие между процедурой и функцией?
Что такое курсоры?
При выполнении запроса SELECT внутри скрипта результирующее значение загружается с сервера, где хранится база данных и передается на клиент, где сохраняется внутри переменной stud типа record. Представьте себе, что результат запроса вернет огромное число строк. Передача подобного объема данных займет множество ресурсов. Также, сохранять их всех внутри данной переменной не существует возможности. Поэтому, создатели языка предусмотрели определенную переменную, называемую курсором. С его помощью возможно получать данные от запроса порциями, не переполняя при этом память. В языке PL/pgSQL такая переменная создается автоматически при использовании цикла «FOR цель IN запрос». Однако, во многих других процедурных надстройках SQL такой возможности нет, поэтому необходимо создавать курсор вручную.
Для каких целей используют триггеры?
Возможно ли написать триггерную функцию на языке SQL?
Да, но без использования сложной логики, циклов, условия if, переменные и т.п.
Просто для выполнения одной sql-команды, например, insert, update, delete.
Например, для логирования.
Для чего нужны представления?