Files
databases/report.tex

2038 lines
111 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\documentclass[a4paper, final]{article}
%\usepackage{literat} % Нормальные шрифты
\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта
\usepackage{tabularx}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\usepackage{amsmath}
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
\usepackage{ragged2e} %для растягивания по ширине
\usepackage{setspace} %для межстрочного интервала
\usepackage{moreverb} %для работы с листингами
\usepackage{indentfirst} % для абзацного отступа
\usepackage{moreverb} %для печати в листинге исходного кода программ
\usepackage{pdfpages} %для вставки других pdf файлов
\usepackage{tikz}
\usepackage{graphicx}
\usepackage{afterpage}
\usepackage{longtable}
\usepackage{float}
% \usepackage[paper=A4,DIV=12]{typearea}
\usepackage{pdflscape}
% \usepackage{lscape}
\usepackage{array}
\usepackage{multirow}
\renewcommand\verbatimtabsize{4\relax}
\renewcommand\listingoffset{0.2em} %отступ от номеров строк в листинге
\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице
\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы
\usepackage{listings} %листинги
\usepackage{xcolor} % цвета
\usepackage{hyperref}% для гиперссылок
\usepackage{enumitem} %для перечислений
\newcommand{\specialcell}[2][l]{%
\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях
\hypersetup{colorlinks,
allcolors=[RGB]{010 090 200}} %красивые гиперссылки (не красные)
% подгружаемые языки — подробнее в документации listings (это всё для листингов)
\lstloadlanguages{ SQL}
% включаем кириллицу и добавляем кое−какие опции
\lstset{tabsize=2,
breaklines,
basicstyle=\footnotesize,
columns=fullflexible,
flexiblecolumns,
numbers=left,
numberstyle={\footnotesize},
keywordstyle=\color{blue},
inputencoding=cp1251,
extendedchars=true
}
\lstdefinelanguage{MyC}{
language=SQL,
% ndkeywordstyle=\color{darkgray}\bfseries,
% identifierstyle=\color{black},
% morecomment=[n]{/**}{*/},
% commentstyle=\color{blue}\ttfamily,
% stringstyle=\color{red}\ttfamily,
% morestring=[b]",
% showstringspaces=false,
% morecomment=[l][\color{gray}]{//},
keepspaces=true,
escapechar=\%,
texcl=true
}
\textheight=24cm % высота текста
\textwidth=16cm % ширина текста
\oddsidemargin=0pt % отступ от левого края
\topmargin=-1.5cm % отступ от верхнего края
\parindent=24pt % абзацный отступ
\parskip=5pt % интервал между абзацами
\tolerance=2000 % терпимость к "жидким" строкам
\flushbottom % выравнивание высоты страниц
% Настройка листингов
\lstset{
language=SQL,
extendedchars=\true,
inputencoding=utf8,
keepspaces=true,
% captionpos=b,
}
\begin{document} % начало документа
% НАЧАЛО ТИТУЛЬНОГО ЛИСТА
\begin{center}
\hfill \break
\hfill \break
\normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\
федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]}
\normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt]
\normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt]
\normalsize{Направление: 02.03.01 <<Математика и компьютерные науки>>}\\
\hfill \break
\hfill \break
\hfill \break
\hfill \break
\large{Отчет по лабораторным работам}\\
\large{<<Теоретические основы баз данных>>}\\
\hfill \break
% \hfill \break
\hfill \break
\end{center}
\small{
\begin{tabular}{lrrl}
\!\!\!Студент, & \hspace{2cm} & & \\
\!\!\!группы 5130201/20102 & \hspace{2cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\
\!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Попов С. Г. \\\\
&&\hspace{4cm}
\end{tabular}
\begin{flushright}
<<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2024г.
\end{flushright}
}
\hfill \break
% \hfill \break
\begin{center} \small{Санкт-Петербург, 2024} \end{center}
\thispagestyle{empty} % выключаем отображение номера для этой страницы
% КОНЕЦ ТИТУЛЬНОГО ЛИСТА
\newpage
\tableofcontents
\newpage
\section*{Введение}
\addcontentsline{toc}{section}{Введение}
В этом отчёте рассматривается разработка базы данных для информационной системы, предназначенной для организации соревнований по стрельбе из лука. Правила и порядок проведения соревнований в России регламентируются Всемирной федерацией стрельбы из лука (FITA) и Российской федерацией по стрельбе из лука (РФСЛ). Информационная система могла бы упростить организацию соревнований и сделать их более доступными для большего числа спортивных клубов, спортсменов и судей.
\newpage
\section {Аналитика предметной области}
\subsection{Вид спорта}
Стрельба из лука, олимпийский вид спорта с 1900 года. Правила и порядок проведения соревнований в России регламентируются Всемирной федерацией стрельбы из лука (FITA) и Российской федерацией по стрельбе из лука (РФСЛ). РФСЛ проводит несколько десятков соревнований по различным видам стрельбы из лука ежегодно. Спорт также приобретает популярность в любительской среде, например, только в Санкт-Петербурге функционируют около двух десятков спортивных стрелковых клубов и лучных клубов, где может тренировать любой желающий. Частные клубы также регулярно проводят спортивные соревнования.
В стрельбе из лука используются различные виды луков: классический или Олимпийский лук, с натяжением от 15 до 20 кг и скоростью стрелы около 240 км/ч, и блочный лук, с натяжением от 25 до 30 кг, что позволяет достигать скорости стрелы до 360 км/ч. Также применяются составные, периферийные, длинные и ассиметричные луки.
Также спортсмены соревнуются в различных видах стрельбы и на различных дистанциях, например, в помещении обычно стреляют с дистанций 18 м, 30 м и 50 м, на открытом воздухе на дистанциях до 90 м. Кроме традиционной стрельбы по мишеням, существуют специальные дисциплины, такие как 3Д стрельба из лука, арчери-кросс, арчери-биатлон и другие.
Именно индивидуальная стрельба по мишеням входит в олимпийские игры и является самым популярным видом стрельбы, поэтому в этой работе в первую очередь рассматриваются именно такие соревнования.
\subsection{Особенности соревнований по стрельбе из лука}
Когда организатор проводит соревнования, в первую очередь он должен определиться с местом и датами проведения. После этого он должен предоставить спортсменам возможность оставить заявку на соревнования, сейчас многие используют для этого Yandex или Google формы, в этой заявке спортсмены указывают персональные данные, а также спортивный разряд, тип лука, федерацию или клуб, от имени которого они участвуют в соревновании. По мимо спортсменов важно подобрать судей. Соответственно судьи также должны оставить заявку, в которой они указывают свои персональные данные, а также свою судейскую категорию, номер и дату приказа об её присвоении. Заявки от спортсменов и судей обычно начинают приниматься за две-три недели до начала соревнования.
\vspace{10pt}
Соревнования по стрельбе из лука можно разделить на следующие этапы:
\vspace{-5pt}
\begin{enumerate}
\item Регистрация прибывших спортсменов.
\item Регистрация прибывших судей.
\item Распределение спортсменов по дивизионам, например: "Мужчины блочный лук 50м", "Женщины классический лук 18м" и так далее.
\item Жеребьёвка, во время которой спортсмены случайным образом распределяются по щитам и мишеням. Обычно щиты нумеруются цифрами, а мишени внутри щита буквами. Пример щитов с мишенями изображён на Рис.~\ref{fig:shield}.
\begin{figure}[h]
\centering
\includegraphics[width=0.6\linewidth]{img/shield.jpg}
\caption{Пример щита с мишенями}
\label{fig:shield}
\end{figure}
\item Выдача каждому участнику карточки, в которой указывается, по какой мишени он будет стрелять. В эту же карточку судьи записывают результаты участника. Пример карточки участника представлен на Рис.~\ref{fig:card}.
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/card.jpg}
\caption{Пример карточки участника}
\label{fig:card}
\end{figure}
\item Определение списка судей и мишеней, у которых они будут записывать результаты. В квалификационном этапе могут участвовать судьи с любой судейской категорией.
\item Проведение двух этапов квалификации, где каждый спортсмен выполняет по 30 или 60 выстрелов. Результаты квалификации определяют содержание протоколов, которые затем печатаются и вывешиваются для всеобщего обозрения.
\item По результатам квалификации определяются лучшие спортсмены в каждом дивизионе, которые попадают в финал. Финалы проводятся по стандартной олимпийской системе.
\item Определение списка судей, фиксирующих результаты финальных стрельб. Обычно в финалах участвуют судьи, начиная с определённой судейской категории.
\item Подведение итогов соревнований и формирование итогового протокола, содержащего информацию о выступлениях спортсменов, имена победителей и так далее.
\end{enumerate}
% \newpage
% \phantom{.}
\newpage
\subsection {Цели информационной системы}
В информационной системе для организации соревнований по стрельбе из лука могут быть реализованы следующие функции:
\vspace{-5pt}
\begin{enumerate}
\item Объективная фиксация результата. Судья может фотографировать мишени после каждой серии выстрелов и загружать их в систему. Таким образом будет гораздо проще разрешить любую спорную ситуацию.
\item История выступлений спортсмена на всех соревнованиях может храниться в системе. Таким образом спортсмен сможет следить за тенденцией как своих результатов, так и результатов своих соперников.
\item Загруженные в систему результаты могут в реальном времени выводиться на интерактивное табло. Это не только упростит жизнь организаторам, так как им не нужно будет каждый раз печатать и вывешивать промежуточные результат, но и позволит спортсменам видеть результаты всех своих соперников в реальном времени.
\item Сократится время ожидания при формировании промежуточных результатов соревнований, потому что система позволит быстро сортировать участников по результатам, группировать по дивизионам, то есть по полу и типу лука, отбирать лучших спортсменов для участия в финалах.
\item Автоматическое вычисление разрядов. Система, используя сохранённые результаты, может автоматически выставлять выполненные спортивные разряды.
\item Организаторы смогут выбрать судей в системе и пригласить на свои соревнования. Организатор может, например, отфильтровать судей по региону, спортивной федерации, судейской категории.
\item Система может в автоматическом режиме переносить результаты спортсменов в промежуточные и итоговые протоколы, которые необходимо составлять организатору соревнований.
\end{enumerate}
\subsection{Роли}
В системе можно выделить несколько ролей:
\begin{enumerate}
\item \textbf{Организаторы}
\begin{itemize}
\item определяют место и даты проведения соревнований;
\item обеспечивают возможность регистрации для спортсменов и судей, часто с использованием онлайн-форм (Yandex или Google формы);
\item организуют жеребьёвку и распределение спортсменов по дивизионам;
\item обрабатывают результаты квалификаций и финалов, формируют и публикуют итоговые протоколы;
\item обеспечивают выполнение всех регламентирующих требований от Всемирной федерации стрельбы из лука (FITA) и Российской федерации по стрельбе из лука (РФСЛ).
\end{itemize}
\item \textbf{Спортсмены}
\begin{itemize}
\item участвуют в соревнованиях, зарегистрировавшись и предоставив необходимые данные (персональные данные, спортивный разряд, тип лука, федерация или клуб);
\item участвуют в квалификационных этапах и финалах, производя установленные количества выстрелов.
\end{itemize}
\item \textbf{Судьи}
\begin{itemize}
\item осуществляют судейство на соревнованиях, зарегистрировавшись и указав свою судейскую категорию, номер и дату приказа о её присвоении;
\item отвечают за фиксирование результатов серий выстрелов спортсменов.
\end{itemize}
\end{enumerate}
\subsection{Сущности}
В этом разделе представлены основные сущности системы и их атрибуты.
\begin{enumerate}
\item \textbf{Организатор} -- организует соревнования, одобряет заявки спортсменов и выбирает судей.
\begin{itemize}
\item Название организации или ФИО;
\item Логотип.
\end{itemize}
\item \textbf{Соревнование} -- ключевая сущность, мероприятие, в котором участвуют спортсмены и судьи. Заявки участников и судей, протоколы и результаты -- все эти сущности распределены по соревнованиям.
\begin{itemize}
\item Название;
\item Логотип;
\item Адрес проведения;
\item Дата начала;
\item Дата окончания.
\end{itemize}
\item \textbf{Спортсмен} -- участвует в соревнованиях, оставляя завки. Производит серии выстрелов в квалификационных этапах и финалах.
\begin{itemize}
\item ФИО;
\item Пол;
\item Спортивный разряд;
\item Тип лука;
\item Спортивная федерация;
\item Спортивный клуб;
\item Регион.
\end{itemize}
\item \textbf{Заявка участника} -- заявка оставляемая спортсменом, для участия в конкретном дивизионе конкретного соревнования. Одобряется организатором.
\begin{itemize}
\item Информация о спортсмене;
\item Отметка о регистрации.
\end{itemize}
\item \textbf{Судья} -- участвует в соревновании, оставляя заявку. Фиксирует результаты участников.
\begin{itemize}
\item ФИО;
\item Судейская категория;
\item Регион;
\item Спортивная федерация.
\end{itemize}
\item \textbf{Заявка судьи} -- заявка, оставляемая судьёй, для участия в конкретном соревновании. Одобряется организатором.
\begin{itemize}
\item Информация о судье;
\item Отметка о регистрации.
\end{itemize}
\item \textbf{Протокол (итоговый)} -- документ, содрежащий итоги соревнования, результаты всех этапов.
\begin{itemize}
\item Название;
\item Дата формирования;
\item Файл.
\end{itemize}
\item \textbf{Позиция для стрельбы} -- три мишени друг под другом, по которым участник ведёт стрельбу. Затем по этим же мишеням судья определяет количество очков, заработанных участником.
\begin{itemize}
\item Индекс мишени;
\item Номер щита.
\end{itemize}
\item \textbf{Серия выстрелов} -- серия из трёх выстрелов, которые производит спортсмен на своей позиции для стрельбы. Судья фиксирует результаты серии.
\begin{itemize}
\item Ссылка на фотографию.
\end{itemize}
\item \textbf{Очки} -- очки получаемые за серию выстрелов.
\begin{itemize}
\item Количество.
\end{itemize}
\item \textbf{Этап соревнования} -- бывают квалификационные и финальные этапы. Количество серий выстрелов может варьироваться в зависимости от этапа.
\begin{itemize}
\item Тип этапа;
\item Количество серий.
\end{itemize}
\item \textbf{Результат} -- сумма очков определённого спортсмена в определённом этапе и дивизионе соревнования.
\begin{itemize}
\item Сумма очков за этап.
\end{itemize}
\item \textbf{Дивизион} -- группа участников соревнования с одним полом, типом лука и дистанцией для стрельбы.
\begin{itemize}
\item МСМК;
\item МС;
\item КМС;
\item 1 разряд;
\item 2 разряд;
\item 3 разряд;
\item Название;
\item Тип лука;
\item Пол;
\item Дистанция.
\end{itemize}
\end{enumerate}
\subsection{Диаграммы}
На основании вышеизложенных разделов были составлены ER-диаграмма (Рис.~3) и диаграмма объектов (Рис.~4).
ER-диаграмму можно прочитать следующим образом:
\begin{enumerate}
\item Организатор проводит соревнование;
\item Организатор распределяет спортсменов по позициям для стрельбы;
\item Организатор утверждает заявки участников;
\item Организатор выбирает дивизионы для соревнования;
\item Спортсмен участвует в соревновании;
\item Спортсмен подаёт заявку участника;
\item Спортсмен производит серию выстрелов на позиции для стрельбы в определённом дивизионе и этапе соревнования;
\item Судья судит соревнование;
\item Судья подаёт заявку судьи на соревнование;
\item Судья назначает очки за серию выстрелов;
\item Судья суммирует очки за этап соревнования в результат;
\item Судья заносит результат в протокол.
\end{enumerate}
% ER-диаграмма
\includepdf[pages={1}, fitpaper, pagecommand={%
\thispagestyle{empty}%
\begin{tikzpicture}[remember picture, overlay]
\node at (current page.south) [anchor=north, yshift=35pt] {\large{Рис 3. ER-диаграмма}};
\end{tikzpicture}%
}]{pdf/er_diagram.pdf}
% Объектная диаграмма
\includepdf[pages={1}, fitpaper, pagecommand={%
\thispagestyle{empty}%
\begin{tikzpicture}[remember picture, overlay]
\node at (current page.south) [anchor=north, yshift=35pt] {\large{Рис 4. Объектная диаграмма}};
\end{tikzpicture}%
}]{pdf/obj_diagram.pdf}
\newpage
\section{Проектирование базы данных}
\subsection{Технология работы с СУБД}
В качестве системы управления базами данных была выбрана PostgreSQL. Она является одной из самых популярных бесплатных СУБД и поддерживает весь необходимый функционал.
PostgreSQL предоставляет пользователю как графический, так и консольный интерфейсы для работы с базами данных и выполнения SQL запросов. Для того чтобы выполнять команды в консоли PostgreSQL, необходимо сначала подключиться к нужной базе данных. Это можно сделать с помощью команды:
\begin{verbatim}
psql -U имя_пользователя -d имя_базы_данных
\end{verbatim}
Для создания таблицы используется команда \texttt{CREATE TABLE}. Эта команда позволяет определить структуру таблицы, включая имена столбцов и их типы данных. Пример создания таблицы \texttt{sportsman}, которая содержит информацию о спортсменах:
\begin{verbatim}
CREATE TABLE sportsman (
id_sportsman SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
gender VARCHAR(10) NOT NULL,
birthday DATE,
region VARCHAR(100),
club VARCHAR(100),
federation VARCHAR(100),
category VARCHAR(100)
);
\end{verbatim}
Для удаления таблицы и всех данных, которые в неё содержатся, используется команда \texttt{DROP TABLE}. Пример удаления таблицы \texttt{sportsman}:
\begin{verbatim}
DROP TABLE sportsman;
\end{verbatim}
Чтобы просмотреть структуру существующей таблицы, можно использовать команду \texttt{\textbackslash d}, которая отображает схему таблицы, включая информацию о столбцах и их типах данных. Пример использования команды для таблицы \texttt{sportsman}:
\begin{verbatim}
\d sportsman
\end{verbatim}
Результат выполнения команды представлен на Рис.~\ref{fig:slash_d}.
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/slash_d.png}
\caption{Пример работы команды \textbackslash d}
\label{fig:slash_d}
\end{figure}
Для вставки данных в таблицу используется команда \texttt{INSERT INTO}. Пример добавления новой записи в таблицу \texttt{sportsman}:
\begin{verbatim}
INSERT INTO
sportsman (
name,
gender,
birthday,
region,
club,
federation,
category
)
VALUES
(
'Иван Иванов',
'Мужчина',
'01.01.1990',
'Санкт-Петербург',
'ЛК Парадокс лучника',
'РФСЛ',
'КМС'
);
\end{verbatim}
Для удаления данных из таблицы используется команда \texttt{DELETE}. Пример удаления записи о спортсмене с \texttt{id\_sportsman = 1} из таблицы \texttt{sportsman}:
\begin{verbatim}
DELETE FROM sportsman
WHERE id_sportsman = 1;
\end{verbatim}
\subsection{Таблицы}
В этом разделе представлены таблицы метаданных, они дополняют схемы базы данных, приведённые на Рис.~6 и Рис.~7. В этих таблицах 1-12 все атрибуты таблиц базы данных и их типы данных.
Для большей части текстовых полей установлен тип \texttt{VARCHAR(100)}. Ста символов хватает для хранения большинства имён и названий. Самое длинное имя в имеющихся данных содержит 21 символ, самое длинное название клуба -- 47 символов, регион с самым длинным названием в России это <<Ханты-Мансийский автономный округ — Югра>> -- 40 символов. Для ссылок на логотипы и файлы используется \texttt{VARCHAR(1024)}.
Для хранения чисел, не считая идентификаторов, используется тип \texttt{SMALLINT}. В нём можно хранить значения до 32,767, этого более чем достаточно для данной задачи. Наибольшие числовые значения, которые требуется хранить в базе данных, это суммы очков -- числа от нуля до нескольких сотен. Для идентификаторов используется специфический для PostgreSQL тип \texttt{SERIAL}, который, по сути, является псевдонимом для типа \texttt{INTEGER} с автоинкрементом.
В нескольких местах используется тип \texttt{DATE} для хранения дат.
\begin{table}[h!]
\centering
\caption{Организатор}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & id\_organizer & id\_Организатор & INTEGER & NOT NULL \\
- & - & name & Название & VARCHAR(100) & NOT NULL \\
- & - & logo & Логотип & VARCHAR(1024) & NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Судья}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & id\_judge & id\_Судья & INTEGER & NOT NULL \\
- & - & name & Имя & VARCHAR(100) & NOT NULL \\
- & - & category & Категория & VARCHAR(100) & NOT NULL \\
- & - & region & Регион & VARCHAR(100) & NULL \\
- & - & federation & Федерация & VARCHAR(100) & NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Спортсмен}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & id\_sportsman & id\_Спортсмен & INTEGER & NOT NULL \\
- & - & name & Имя & VARCHAR(100) & NOT NULL \\
- & - & gender & Пол & VARCHAR(10) & NOT NULL \\
- & - & birthday & Дата\_рождения & DATE & NULL \\
- & - & region & Регион & VARCHAR(100) & NULL \\
- & - & club & Клуб & VARCHAR(100) & NULL \\
- & - & federation & Федерация & VARCHAR(100) & NULL \\
- & - & category & Категория & VARCHAR(100) & NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Дивизион}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & id\_division & id\_Дивизион & INTEGER & NOT NULL \\
- & - & title & Название & VARCHAR(100) & NOT NULL \\
- & - & gender & Пол & VARCHAR(10) & NOT NULL \\
- & - & bow\_type & Тип\_лука & VARCHAR(100) & NOT NULL \\
- & - & distance & Дистанция & SMALLINT & NOT NULL \\
- & - & msmk & МСМК & SMALLINT & NOT NULL \\
- & - & ms & МС & SMALLINT & NOT NULL \\
- & - & kms & КМС & SMALLINT & NOT NULL \\
- & - & category\_1 & Категория\_1 & SMALLINT & NOT NULL \\
- & - & category\_2 & Категория\_2 & SMALLINT & NOT NULL \\
- & - & category\_3 & Категория\_3 & SMALLINT & NOT NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Соревнование}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & id\_competition & id\_Соревнование & INTEGER & NOT NULL \\
- & - & title & Название & VARCHAR(100) & NOT NULL \\
- & - & address & Адрес & VARCHAR(100) & NOT NULL \\
- & - & logo & Логотип & VARCHAR(1024) & NULL \\
- & - & start\_date & Дата\_начала & DATE & NOT NULL \\
- & - & end\_date & Дата\_окончания & DATE & NOT NULL \\
FK & \specialcell{Организа-\\тор.id\_Ор-\\ганизатор} & id\_organizer & id\_Организатор & INTEGER & NOT NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Дивизион в соревновании}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & \specialcell{id\_division\_\\in\_competition} & \specialcell{id\_Дивизион\_\\в\_соревновании} & INTEGER & NOT NULL \\
FK & \specialcell{Дивизион.\\id\_Дивизион} & id\_division & id\_Дивизион & INTEGER & NOT NULL \\
FK & \specialcell{Соревнова-\\ние.id\_Со-\\ревнование} & id\_competition & id\_Соревнование & INTEGER & NOT NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Протокол}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & id\_protocol & id\_Протокол & INTEGER & NOT NULL \\
- & - & name & Название & VARCHAR(100) & NOT NULL \\
- & - & date & Дата & DATE & NOT NULL \\
- & - & file & Файл & VARCHAR(1024) & NOT NULL \\
FK & \specialcell{Соревнова-\\ние.id\_Со-\\ревнование} & id\_competition & id\_Соревнование & INTEGER & NOT NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Заявка участника}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & \specialcell{id\_participant\\\_request} & \specialcell{id\_Заявка\\\_участника} & INTEGER & NOT NULL \\
- & - & name & Имя & VARCHAR(100) & NOT NULL \\
- & - & gender & Пол & VARCHAR(10) & NOT NULL \\
- & - & birthday & Дата\_рождения & DATE & NOT NULL \\
- & - & region & Регион & VARCHAR(100) & NULL \\
- & - & club & Клуб & VARCHAR(100) & NULL \\
- & - & federation & Федерация & VARCHAR(100) & NULL \\
- & - & category & Категория & VARCHAR(100) & NOT NULL \\
- & - & is\_registered & Зарегистрирован & BOOLEAN & NOT NULL \\
FK & \specialcell{Соревнова-\\ние.id\_Со-\\ревнование} & id\_competition & id\_Соревнование & INTEGER & NOT NULL \\
FK & \specialcell{Спортсмен\\.id\_Спорт-\\смен} & id\_sportsman & id\_Спортсмен & INTEGER & NOT NULL \\
FK & \specialcell{Дивизион\\.id\_Диви-\\зион} & id\_division & id\_Дивизион & INTEGER & NOT NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Этап соревнования}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & \specialcell{id\_competition\\\_stage} & \specialcell{id\_Этап\\\_соревнования} & INTEGER & NOT NULL \\
- & - & title & Название & VARCHAR(100) & NOT NULL \\
- & - & count\_of\_series & \specialcell{Количество\\\_серий} & SMALLINT & NOT NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Результат в этапе}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & \specialcell{id\_result\\\_in\_stage} & \specialcell{id\_Результат\\\_в\_этапе} & INTEGER & NOT NULL \\
- & - & score & Очки & SMALLINT & NOT NULL \\
- & - & shield\_index & Индекс\_щита & SMALLINT & NOT NULL \\
- & - & target\_index & Индекс\_мишени & CHAR(1) & NOT NULL \\
FK & \specialcell{Заявка\_\\участника.\\id\_Заявка\_\\участника} & \specialcell{id\_participant\\\_request} & \specialcell{id\_Заявка\\\_участника} & INTEGER & NOT NULL \\
FK & \specialcell{Дивизион\\.id\_Диви-\\зион} & id\_division & id\_Дивизион & INTEGER & NOT NULL \\
FK & \specialcell{Этап\_соре-\\внования.\\id\_Этап\_со-\\ревнования} & \specialcell{id\_competition\\\_stage} & \specialcell{id\_Этап\\\_соревнования} & INTEGER & NOT NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Заявка судьи}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & \specialcell{id\_judge\\\_request} & \specialcell{id\_Заявка\\\_судьи} & INTEGER & NOT NULL \\
- & - & name & Имя & VARCHAR(100) & NOT NULL \\
- & - & category & Категория & VARCHAR(100) & NOT NULL \\
- & - & region & Регион & VARCHAR(100) & NULL \\
- & - & federation & Федерация & VARCHAR(100) & NULL \\
- & - & is\_registered & Зарегистрирован & BOOLEAN & NOT NULL \\
FK & \specialcell{Соревнова-\\ние.id\_Со-\\ревнование} & \specialcell{id\_competition} & \specialcell{id\_Соревнование} & INTEGER & NOT NULL \\
FK & \specialcell{Судья.\\id\_Судья} & \specialcell{id\_judge} & \specialcell{id\_Судья} & INTEGER & NOT NULL \\
\hline
\end{tabularx}
\end{table}
\begin{table}[h!]
\centering
\caption{Серия выстрелов}
\footnotesize
\begin{tabularx}{\textwidth}{|c|c|X|X|X|X|}
\hline
\textbf{KEY} & \textbf{Ссылка} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Тип атрибута} & \textbf{NULL/NOT NULL} \\
\hline
PK & - & \specialcell{id\_shot\\\_series} & \specialcell{id\_Серия\\\_выстрелов} & INTEGER & NOT NULL \\
- & - & score & Очки & SMALLINT & NOT NULL \\
- & - & photo & Фото & VARCHAR(1024) & NOT NULL \\
FK & \specialcell{Заявка\_\\участника.\\id\_Заявка\_\\участника} & \specialcell{id\_participant\\\_request} & \specialcell{id\_Заявка\\\_участника} & INTEGER & NOT NULL \\
FK & \specialcell{Результат\_\\в\_этапе.id\\\_Результат\\\_в\_этапе} & \specialcell{id\_result\\\_in\_stage} & \specialcell{id\_Результат\\\_в\_этапе} & INTEGER & NOT NULL \\
FK & \specialcell{Заявка\_\\судьи.\\id\_Заявка\_\\судьи} & \specialcell{id\_judge\\\_request} & \specialcell{id\_Заявка\\\_судьи} & INTEGER & NOT NULL \\
\hline
\end{tabularx}
\end{table}
\phantom{.}
\newpage
\phantom{.}
\newpage
\phantom{.}
\newpage
\phantom{.}
\newpage
\phantom{.}
\newpage
\newpage
% Схема на русском
\includepdf[pages={1}, fitpaper, pagecommand={%
\thispagestyle{empty}%
\begin{tikzpicture}[remember picture, overlay]
\node at (current page.south) [anchor=north, yshift=35pt] {\large{Рис 6. Схема базы данных на русском}};
\end{tikzpicture}%
}]{pdf/schema_ru.pdf}
% Схема на английском
\includepdf[pages={1}, fitpaper, pagecommand={%
\thispagestyle{empty}%
\begin{tikzpicture}[remember picture, overlay]
\node at (current page.south) [anchor=north, yshift=35pt] {\large{Рис 7. Схема базы данных на английском}};
\end{tikzpicture}%
}]{pdf/schema_en.pdf}
\subsection{Схемы}
На основе аналитики предметной области, целей программы, ER-диаграммы и объектной диаграммы, была спроектирована база данных. Схема базы данных на русском языке представлена на Рис.~6, на английском языке на Рис.~7.
\subsection{Программа для заполнения базы данных}
Программа для заполнения базы данных представлена в приложении Б. Для её написания был выбран язык Python, из-за его простоты и наличия библиотеки psycopg, которая значительно упрощает взаимодействие с API PostgreSQL.
Каждой таблице в базе данных можно присвоить порядковый номер заполнения. Таблицам, которые не содержат связей с другими таблицами, то есть не содержат foreign key полей, присваивается первый порядок. Таблицам, которые содержат связи только с таблицами первого порядка, присваивается второй порядок и так далее. Порядковые номера всех таблиц и количества строк в них изображены на Рис.~6 и в таблице~\ref{count_table}.
Таблицы первого порядка заполняются осмысленными данным из составленных вручную csv таблиц. Список участников взят из настоящего соревнования. Загрузка данных из csv таблиц в указанную таблицу базы данных производится с помощью функции \texttt{load\_csv\_to\_db}.
Содержание для таблиц следующих порядков генерируется на основе таблиц первого порядка, то есть дублируемые данные переносятся из таблицы в таблицу. Дополнительные текстовые атрибуты заданы константами и одинаковы для всех строк, а дополнительные числовые атрибуты генерируются случайным образом.
Всего программа создаёт около 283 тысяч строк за примерно 50 секунд.
\begin{table}[h!]
\centering
\caption{Количества записей в таблицах базы данных}
\footnotesize
\begin{tabularx}{\textwidth}{|c|X|X|c|X|}
\hline
\textbf{Порядок} & \textbf{Название EN} & \textbf{Название RU} & \textbf{Количество} & \textbf{Комментарий} \\
\hline
1 & competition\_stage & Этап соревнования & 6 & - \\
1 & division & Дивизион & 16 & - \\
1 & organizer & Организатор & 20 & - \\
1 & judge & Судья & 50 & - \\
1 & sportsman & Спортсмен & 100 & - \\
2 & competition & Соревнование & 200 & - \\
3 & protocol & Протокол & 200 & - \\
3 & judge\_request & Заявка судьи & 1200 & От 5 до 7 на соревнование. \\
3 & \specialcell{division\_in\\\_competition} & Дивизион в соревновании & 2400 & От 8 до 16 на соревнование. \\
3 & participant\_request & Заявка участника & 9000 & От 30 до 60 на соревнование. \\
4 & result\_in\_stage & Результат в этапе & 36000 & От 2 до 6 на заявку участника. \\
5 & shot\_series & Серия выстрелов & 234000 & От 3 до 10 на результат. \\
\hline
\end{tabularx}
\label{count_table}
\end{table}
\newpage
\phantom{.}
\newpage
\section {Запросы}
\subsection{Запрос 1}
Задача: найти спортсменов, которые участвовали в соревновании А, выстрелы которых судил судья B.
Текст запроса представлен в листинге~\ref{lst:sql_1}.
\begin{lstlisting}[numbers=none, caption={Текст первого запроса}, label=lst:sql_1]
select distinct
s.id_sportsman,
s.name,
s.gender,
pr.id_competition,
jr.id_judge
from
sportsman as s
join participant_request as pr on s.id_sportsman = pr.id_sportsman
join shot_series as ss on pr.id_participant_request = ss.id_participant_request
join judge_request as jr on ss.id_judge_request = jr.id_judge_request
where
pr.id_competition = 201
and jr.id_judge = 57
\end{lstlisting}
Запрос выполняется примерно за 1 миллисекунду. Первые пять строк результата представлены на Рис.~\ref{fig:sql_1}.
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/sql_1.png}
\caption{Результат выполнения первого запроса}
\label{fig:sql_1}
\end{figure}
План выполнения запроса, постренный СУБД, представлен в текстовом виде в листинге~\ref{lst:sql_1_plan} и в графическом виде на Рис.~\ref{fig:sql_1_visual}.
По плану запроса видно, что сначала в таблице judge\_request выбираются строки по заданному id\_judge. Затем в таблице participant\_request выбираются строки по заданному id\_competition, а потом эта таблица объединяется с shot\_series по id\_participant\_request. Далее результат объединяется с judge\_request по id\_judge\_request. После чего следует последнее объединение полученной таблицы с таблицой sportsman по id\_sportsman. В конце выполняется сортировка по id\_sportsman, name и gender, для того чтобы выбрать только уникальные значения, ведь в запросе используется ключевое слово distinct.
\begin{lstlisting}[caption={План выполнения первого запроса}, label=lst:sql_1_plan]
Unique (cost=398.70..398.92 rows=22 width=57)
-> Sort (cost=398.70..398.76 rows=22 width=57)
Sort Key: s.id_sportsman, s.name, s.gender
-> Hash Join (cost=51.56..398.21 rows=22 width=57)
Hash Cond: (pr.id_sportsman = s.id_sportsman)
-> Hash Join (cost=44.31..390.90 rows=22 width=12)
Hash Cond: (ss.id_judge_request = jr.id_judge_request)
-> Nested Loop (cost=0.58..344.56 rows=993 width=12)
-> Index Scan using idx_participant_request_id_competition on participant_request pr (cost=0.29..8.96 rows=38 width=12)
Index Cond: (id_competition = 201)
-> Index Scan using idx_shot_series_id_participant_request on shot_series ss (cost=0.29..8.56 rows=27 width=8)
Index Cond: (id_participant_request = pr.id_participant_request)
-> Hash (cost=43.40..43.40 rows=26 width=8)
-> Bitmap Heap Scan on judge_request jr (cost=4.48..43.40 rows=26 width=8)
Recheck Cond: (id_judge = 57)
-> Bitmap Index Scan on idx_judge_request_id_judge (cost=0.00..4.47 rows=26 width=0)
Index Cond: (id_judge = 57)
-> Hash (cost=6.00..6.00 rows=100 width=49)
-> Seq Scan on sportsman s (cost=0.00..6.00 rows=100 width=49)
\end{lstlisting}
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/sql_1_visual.png}
\caption{Графический план выполнения первого запроса}
\label{fig:sql_1_visual}
\end{figure}
\newpage
\subsection{Запрос 2}
Задача: для соревнования А посчитать число спортсменов участвующих в нём.
Текст запроса представлен в листинге~\ref{lst:sql_2}.
\begin{lstlisting}[numbers=none, caption={Текст второго запроса}, label=lst:sql_2]
select
count(distinct id_sportsman) as count_of_participants
from
participant_request
where
id_competition = 201
\end{lstlisting}
Запрос выполняется за 100 микросекунд. Результат представлен на Рис.~\ref{fig:sql_2}.
\begin{figure}[h]
\centering
\includegraphics[width=0.4\linewidth]{img/sql_2.png}
\caption{Результат выполнения второго запроса}
\label{fig:sql_2}
\end{figure}
План выполнения запроса, постренный СУБД, представлен в текстовом виде в листинге~\ref{lst:sql_2_plan} и в графическом виде на Рис.~\ref{fig:sql_2_visual}.
Сначала в таблице participant\_request выбираются строки с заданным \\ id\_competition, а затем производится сортировка по id\_sportsman, для того чтобы выбрать только уникальные значения.
\begin{lstlisting}[caption={План выполнения второго запроса}, label=lst:sql_2_plan]
Aggregate (cost=10.15..10.16 rows=1 width=8)
-> Sort (cost=9.96..10.05 rows=38 width=4)
Sort Key: id_sportsman
-> Index Scan using idx_participant_request_id_competition on participant_request (cost=0.29..8.96 rows=38 width=4)
Index Cond: (id_competition = 201)
\end{lstlisting}
\begin{figure}[h]
\centering
\includegraphics[width=0.7\linewidth]{img/sql_2_visual.png}
\caption{Графический план выполнения второго запроса}
\label{fig:sql_2_visual}
\end{figure}
\subsection{Запрос 3}
Задача: найти дивизион, в котором участвовало больше всего спортсменов.
Текст запроса представлен в листинге~\ref{lst:sql_3}.
\begin{lstlisting}[numbers=none, caption={Текст третьего запроса}, label=lst:sql_3]
select
d.id_division,
d.title,
count(pr.name) as participant_count
from
division as d
left join participant_request as pr on d.id_division = pr.id_division
group by
d.id_division,
d.title
having
count(pr.name) = (
select
count(pr.name) as participant_count
from
division as d
left join participant_request as pr on d.id_division = pr.id_division
group by
d.id_division
order by
participant_count desc
limit 1
)
\end{lstlisting}
Запрос выполняется примерно за 9 миллисекунд. Результат представлен на Рис.~\ref{fig:sql_3}.
\begin{figure}[h]
\centering
\includegraphics[width=0.8\linewidth]{img/sql_3.png}
\caption{Результат выполнения третьего запроса}
\label{fig:sql_3}
\end{figure}
План выполнения запроса, постренный СУБД, представлен в текстовом виде в листинге~\ref{lst:sql_3_plan} и в графическом виде на Рис.~\ref{fig:sql_3_visual}.
Рассмотрим план подробнее, начиная с подзапроса. В подзапросе прежде всего происходит объединение таблиц division и participant\_request по id\_division. Далее результат объединения группируется по id\_division и сортируется по count(name). В самом конце к подзапросу применяется команда limit, которая оставляет только первую строку подзапроса. В основном запросе сначала происходит объединение таблиц participant\_request и division по id\_division, затем результат объединения группируется по id\_division. Далее применяется фильтрация, остаются только те строки, в которых count(name) равно результату подзапроса.
\begin{lstlisting}[caption={План выполнения третьего запроса}, label=lst:sql_3_plan]
HashAggregate (cost=765.70..767.57 rows=1 width=230)
Group Key: d.id_division
Filter: (count(pr.name) = $0)
InitPlan 1 (returns $0)
-> Limit (cost=383.97..383.98 rows=1 width=12)
-> Sort (cost=383.97..384.35 rows=150 width=12)
Sort Key: (count(pr_1.name)) DESC
-> HashAggregate (cost=381.72..383.22 rows=150 width=12)
Group Key: d_1.id_division
-> Hash Right Join (cost=13.38..336.14 rows=9116 width=34)
Hash Cond: (pr_1.id_division = d_1.id_division)
-> Seq Scan on participant_request pr_1 (cost=0.00..298.16 rows=9116 width=34)
-> Hash (cost=11.50..11.50 rows=150 width=4)
-> Seq Scan on division d_1 (cost=0.00..11.50 rows=150 width=4)
-> Hash Right Join (cost=13.38..336.14 rows=9116 width=252)
Hash Cond: (pr.id_division = d.id_division)
-> Seq Scan on participant_request pr (cost=0.00..298.16 rows=9116 width=34)
-> Hash (cost=11.50..11.50 rows=150 width=222)
-> Seq Scan on division d (cost=0.00..11.50 rows=150 width=222)
\end{lstlisting}
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/sql_3_visual.png}
\caption{Графический план выполнения третьего запроса}
\label{fig:sql_3_visual}
\end{figure}
\subsection{Запрос 4}
Задача: для каждого организатора посчитать количество соревнований, которые он организовал.
Текст запроса представлен в листинге~\ref{lst:sql_4}.
\newpage
\begin{lstlisting}[numbers=none, caption={Текст четвёртого запроса}, label=lst:sql_4]
select
org.id_organizer,
org.name,
count(comp.id_competition) as count_of_competitions
from
organizer as org
left join competition as comp on org.id_organizer = comp.id_organizer
group by
org.id_organizer,
org.name
\end{lstlisting}
% \begin{figure}[H]
% \centering
% \includegraphics[width=1\linewidth]{img/diagrams/sql4.png}
% \caption{Гистограмма для результатов четвёртого запроса}
% \label{fig:diag-sql4}
% \end{figure}
Запрос выполняется примерно за 2 миллисекунды. Первые пять строк результата представлены на Рис.~\ref{fig:sql_4}. Результат запроса в виде гистограммы представлен на Рис.~\ref{fig:diag-sql5}.
\begin{figure}[h]
\centering
\includegraphics[width=0.8\linewidth]{img/sql_4.png}
\caption{Результат выполнения четвёртого запроса}
\label{fig:sql_4}
\end{figure}
План выполнения запроса, постренный СУБД, представлен в текстовом виде в листинге~\ref{lst:sql_4_plan} и в графическом виде на Рис.~\ref{fig:sql_4_visual}.
По плану видно, что прежде всего выполняется объединение таблиц organizer и competition по id\_organizer, а затем выполняется группировка по тому же id\_organizer.
\begin{lstlisting}[caption={План выполнения четвёртого запроса}, label=lst:sql_4_plan]
HashAggregate (cost=10.09..10.29 rows=20 width=55)
Group Key: org.id_organizer
-> Hash Right Join (cost=1.45..9.09 rows=200 width=51)
Hash Cond: (comp.id_organizer = org.id_organizer)
-> Seq Scan on competition comp (cost=0.00..7.00 rows=200 width=8)
-> Hash (cost=1.20..1.20 rows=20 width=47)
-> Seq Scan on organizer org (cost=0.00..1.20 rows=20 width=47)
\end{lstlisting}
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/sql_4_visual.png}
\caption{Графический план выполнения четвёртого запроса}
\label{fig:sql_4_visual}
\end{figure}
\newpage
\subsection{Запрос 5}
Задача: посчитать число участников для каждого организатора.
Текст запроса представлен в листинге~\ref{lst:sql_5}.
\begin{lstlisting}[numbers=none, caption={Текст пятого запроса}, label=lst:sql_5]
select
org.id_organizer,
org.name,
count(distinct pr.id_sportsman) as count_of_participants
from
organizer as org
left join competition as comp on org.id_organizer = comp.id_organizer
left join participant_request as pr on comp.id_competition = pr.id_competition
group by
org.id_organizer,
org.name
\end{lstlisting}
Запрос выполняется за 10 миллисекунд. Первые пять строк результата представлены на Рис.~\ref{fig:sql_5}. Результат запроса в виде гистограммы представлен на Рис.~\ref{fig:diag-sql5}.
\begin{figure}[h]
\centering
\includegraphics[width=0.8\linewidth]{img/sql_5.png}
\caption{Результат выполнения пятого запроса}
\label{fig:sql_5}
\end{figure}
План выполнения запроса, постренный СУБД, представлен в текстовом виде в листинге~\ref{lst:sql_5_plan} и в графическом виде на Рис.~\ref{fig:sql_5_visual}.
По данному плану выполнения запроса видно, что сначала происходит объединение таблиц competition и participant\_request по id\_competition. Затем результат объединяется с таблицей organizer по id\_organizer. После чего производится сортировка результата по id\_organizer и id\_sportsman, чтобы выбрать оставить только уникальные значения. Последней производится группировка по id\_organizer.
\begin{lstlisting}[caption={План выполнения пятого запроса}, label=lst:sql_5_plan]
GroupAggregate (cost=962.12..1030.69 rows=20 width=55)
Group Key: org.id_organizer
-> Sort (cost=962.12..984.91 rows=9116 width=51)
Sort Key: org.id_organizer, pr.id_sportsman
-> Hash Right Join (cost=10.95..362.55 rows=9116 width=51)
Hash Cond: (comp.id_organizer = org.id_organizer)
-> Hash Right Join (cost=9.50..332.10 rows=9116 width=8)
Hash Cond: (pr.id_competition = comp.id_competition)
-> Seq Scan on participant_request pr (cost=0.00..298.16 rows=9116 width=8)
-> Hash (cost=7.00..7.00 rows=200 width=8)
-> Seq Scan on competition comp (cost=0.00..7.00 rows=200 width=8)
-> Hash (cost=1.20..1.20 rows=20 width=47)
-> Seq Scan on organizer org (cost=0.00..1.20 rows=20 width=47)
\end{lstlisting}
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/sql_5_visual.png}
\caption{Графический план выполнения пятого запроса}
\label{fig:sql_5_visual}
\end{figure}
\newpage
\begin{figure}[H]
\centering
\includegraphics[width=1\linewidth]{img/diagrams/45_sep.png}
\caption{Гистограмма для четвёртого и пятого запросов.}
\label{fig:diag-sql5}
\end{figure}
\subsection{Запрос 6}
Задача: посчитать число спортсменов с заданным числом заявок.
Текст запроса представлен в листинге~\ref{lst:sql_6}.
\begin{lstlisting}[numbers=none, caption={Текст шестого запроса}, label=lst:sql_6]
select
count_of_requests,
count(id_sportsman) as count_of_duplicates
from
(
select
s.id_sportsman,
count(pr.id_participant_request) as count_of_requests
from
sportsman as s
left join participant_request as pr on s.id_sportsman = pr.id_sportsman
group by
s.id_sportsman
)
group by
count_of_requests
\end{lstlisting}
Запрос выполняется примерно за 5 миллисекунд. Первые пять строк результата представлены на Рис.~\ref{fig:sql_6}.
\begin{figure}[h]
\centering
\includegraphics[width=0.45\linewidth]{img/sql_6.png}
\caption{Результат выполнения шестого запроса}
\label{fig:sql_6}
\end{figure}
План выполнения запроса, постренный СУБД, представлен в текстовом виде в листинге~\ref{lst:sql_6_plan} и в графическом виде на Рис.~\ref{fig:sql_6_visual}.
Рассмотрим план выполнения запроса, начиная с подзапроса. В подзапросе сначала происходит объединение таблиц sportsman и participant\_request по id\_sportsman. Затем результат группируется по id\_sportsman, при этом к столбцу id\_participant\_request применяется функция агрегации count, а получившемуся столбцу даётся псевдоним count\_of\_requests. Затем таблица сортируется по count\_of\_requests. Сортировка происходит для упрощения последующей группировки таблицы по всё тому же столбцу count\_of\_requests.
\begin{lstlisting}[caption={План выполнения шестого запроса}, label=lst:sql_6_plan]
GroupAggregate (cost=381.25..383.00 rows=100 width=16)
Group Key: unnamed_subquery.count_of_requests
-> Sort (cost=381.25..381.50 rows=100 width=12)
Sort Key: unnamed_subquery.count_of_requests
-> Subquery Scan on unnamed_subquery (cost=375.93..377.93 rows=100 width=12)
-> HashAggregate (cost=375.93..376.93 rows=100 width=12)
Group Key: s.id_sportsman
-> Hash Right Join (cost=7.25..330.35 rows=9116 width=8)
Hash Cond: (pr.id_sportsman = s.id_sportsman)
-> Seq Scan on participant_request pr (cost=0.00..298.16 rows=9116 width=8)
-> Hash (cost=6.00..6.00 rows=100 width=4)
-> Seq Scan on sportsman s (cost=0.00..6.00 rows=100 width=4)
\end{lstlisting}
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/sql_6_visual.png}
\caption{Графический план выполнения шестого запроса}
\label{fig:sql_6_visual}
\end{figure}
\subsection{Запрос 7}
Задача: найти соревнования, в которых было больше судий чем в соревновании~A.
Текст запроса представлен в листинге~\ref{lst:sql_7}.
\begin{lstlisting}[numbers=none, caption={Текст седьмого запроса}, label=lst:sql_7]
select
comp.id_competition,
comp.title,
count(jr.id_judge) as count_of_judges
from
competition as comp
join judge_request as jr on comp.id_competition = jr.id_competition
group by
comp.id_competition,
comp.title
having
count(jr.id_judge) > (
select
count(*)
from
judge_request
where
id_competition = 250
)
\end{lstlisting}
Запрос выполняется примерно за 1 миллисекунду. Первые пять строк результата представлены на Рис.~\ref{fig:sql_7}.
\begin{figure}[h]
\centering
\includegraphics[width=0.7\linewidth]{img/sql_7.png}
\caption{Результат выполнения седьмого запроса}
\label{fig:sql_7}
\end{figure}
План выполнения запроса, постренный СУБД, представлен в текстовом виде в листинге~\ref{lst:sql_7_plan} и в графическом виде на Рис.~\ref{fig:sql_7_visual}.
Рассмотрим план подробнее, начиная с подзапроса. В подзапросе прежде всего из таблицы judge\_request выбираются строки с указанным id\_competition, а затем подсчитывается общее количество полученных строк. В основном запросе сначала происходит объединение таблиц competition и judge\_request по id\_competition, затем результат объединения группируется по id\_competition. Далее применяется фильтрация, остаются только те строки, в которых count(id\_judge) строго больше результата подзапроса.
\newpage
\begin{lstlisting}[caption={План выполнения седьмого запроса}, label=lst:sql_7_plan]
HashAggregate (cost=77.11..79.61 rows=67 width=30)
Group Key: comp.id_competition
Filter: (count(jr.id_judge) > $0)
InitPlan 1 (returns $0)
-> Aggregate (cost=4.40..4.41 rows=1 width=8)
-> Index Only Scan using idx_judge_request_id_competition on judge_request (cost=0.28..4.38 rows=6 width=0)
Index Cond: (id_competition = 250)
-> Hash Join (cost=9.50..66.70 rows=1199 width=26)
Hash Cond: (jr.id_competition = comp.id_competition)
-> Seq Scan on judge_request jr (cost=0.00..53.99 rows=1199 width=8)
-> Hash (cost=7.00..7.00 rows=200 width=22)
-> Seq Scan on competition comp (cost=0.00..7.00 rows=200 width=22)
\end{lstlisting}
\begin{figure}[H]
\centering
\includegraphics[width=0.8\linewidth]{img/sql_7_visual.png}
\caption{Графический план выполнения седьмого запроса}
\label{fig:sql_7_visual}
\end{figure}
\subsection{Запрос 8}
Задача: найти судей, которые никогда не судили ничего в дивизионе А.
Текст запроса представлен в листинге~\ref{lst:sql_8}.
\begin{lstlisting}[numbers=none, caption={Текст восьмого запроса}, label=lst:sql_8]
select
*
from
judge
where
id_judge not in (
select
j.id_judge
from
judge as j
left join judge_request as jr on j.id_judge = jr.id_judge
left join shot_series as ss on jr.id_judge_request = ss.id_judge_request
left join result_in_stage as ris on ss.id_result_in_stage = ris.id_result_in_stage
where
ris.id_division = 20
)
\end{lstlisting}
Запрос выполняется за 40 миллисекунд. Результат представлены на Рис.~\ref{fig:sql_8}.
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/sql_8.png}
\caption{Результат выполнения восьмого запроса}
\label{fig:sql_8}
\end{figure}
План выполнения запроса, постренный СУБД, представлен в текстовом виде в листинге~\ref{lst:sql_8_plan} и в графическом виде на Рис.~\ref{fig:sql_8_visual}.
Рассмотрим план подробнее, начиная с подзапроса. В подзапросе прежде всего из таблицы result\_in\_stage выбираются строки с указанным id\_division, затем отфильтрованный result\_in\_stage объединяется с таблицей shot\_series по id\_result\_in\_stage. Затем результат объединяется с таблицей judge\_request по id\_judge\_request. После чего идёт последнее объединение с таблицей judge по id\_judge. В основном запросе происходит фильтрация строк таблицы judge с условием not и результатом подзапроса. Это означает, что выбираются только те строки id\_judge, которых нет в сохранённом результате подзапроса.
\begin{lstlisting}[caption={План выполнения восьмого запроса}, label=lst:sql_8_plan]
Seq Scan on judge (cost=5448.30..5450.92 rows=25 width=101)
Filter: (NOT (hashed SubPlan 1))
SubPlan 1
-> Hash Join (cost=367.78..5421.81 rows=10596 width=4)
Hash Cond: (jr.id_judge = j.id_judge)
-> Hash Join (cost=364.65..5388.51 rows=10596 width=4)
Hash Cond: (ss.id_judge_request = jr.id_judge_request)
-> Hash Join (cost=295.67..5291.62 rows=10596 width=4)
Hash Cond: (ss.id_result_in_stage = ris.id_result_in_stage)
-> Seq Scan on shot_series ss (cost=0.00..4370.26 rows=238326 width=8)
-> Hash (cost=275.30..275.30 rows=1630 width=4)
-> Bitmap Heap Scan on result_in_stage ris (cost=20.92..275.30 rows=1630 width=4)
Recheck Cond: (id_division = 20)
-> Bitmap Index Scan on idx_result_in_stage_id_division (cost=0.00..20.52 rows=1630 width=0)
Index Cond: (id_division = 20)
-> Hash (cost=53.99..53.99 rows=1199 width=8)
-> Seq Scan on judge_request jr (cost=0.00..53.99 rows=1199 width=8)
-> Hash (cost=2.50..2.50 rows=50 width=4)
-> Seq Scan on judge j (cost=0.00..2.50 rows=50 width=4)
\end{lstlisting}
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/sql_8_visual.png}
\caption{Графический план выполнения восьмого запроса}
\label{fig:sql_8_visual}
\end{figure}
\subsection{Запрос 9}
Задача: для случайных 25 спортсменов и каждого организатора посчитать число участия в одном и том же соревновании.
Текст запроса представлен в листинге~\ref{lst:sql_9}.
\begin{lstlisting}[numbers=none, caption={Текст девятого запроса}, label=lst:sql_9]
select
o.id_organizer,
o.name,
s.id_sportsman,
s.name,
(
select
count(distinct comp.id_competition)
from
competition as comp
join participant_request as pr on comp.id_competition = pr.id_competition
where
comp.id_organizer = o.id_organizer
and pr.id_sportsman = s.id_sportsman
)
from
organizer as o
cross join (
select
*
from
sportsman
order by
random ()
limit
25
) as s
\end{lstlisting}
Запрос выполняется за 71 миллисекунд. Первые пять строк результата представлены на Рис.~\ref{fig:sql_9}. Результат в виде диаграммы представлен на Рис.~\ref{fig:sql_9_gist}.
\begin{figure}[h]
\centering
\includegraphics[width=0.8\linewidth]{img/sql_9.png}
\caption{Результат выполнения девятого запроса}
\label{fig:sql_9}
\end{figure}
План выполнения запроса, постренный СУБД, представлен в текстовом виде в листинге~\ref{lst:sql_9_plan} и в графическом виде на Рис.~\ref{fig:sql_9_visual}.
Рассмотрим план подробнее, начиная с подзапроса внутри select. В подзапросе прежде всего из таблицы competition выбираются строки с заданным id\_organizer. Затем результат объединяется с таблицей participant\_request по id\_competition, при этом в participant\_request выбираются строки с заданным id\_sportsman. Далее результат сортируется по id\_competition, для того чтобы оставить только уникальные значения, и агрегируются, чтобы посчитать количество строк. В запросе есть ещё один подзапрос внутри from, в нём просто считываются строки таблицы sportsman, затем сортируются случайным образом с помощью функции random(), а затем применяется команда limit, чтобы выбрать первые 25 строк. В основном происходит объединение таблиц с помощью Nested Loop, при этом первой подзапрос выполняется в цикле и формируется финальная таблица.
\begin{lstlisting}[caption={План выполнения девятого запроса}, label=lst:sql_9_plan]
Nested Loop (cost=9.07..63916.76 rows=500 width=89)
-> Limit (cost=9.07..9.13 rows=25 width=956)
-> Sort (cost=9.07..9.32 rows=100 width=956)
Sort Key: (random())
-> Seq Scan on sportsman (cost=0.00..6.25 rows=100 width=956)
-> Materialize (cost=0.00..1.30 rows=20 width=47)
-> Seq Scan on organizer o (cost=0.00..1.20 rows=20 width=47)
SubPlan 1
-> Aggregate (cost=127.79..127.80 rows=1 width=8)
-> Sort (cost=127.76..127.78 rows=5 width=4)
Sort Key: comp.id_competition
-> Nested Loop (cost=0.29..127.71 rows=5 width=4)
-> Seq Scan on competition comp (cost=0.00..7.50 rows=10 width=4)
Filter: (id_organizer = o.id_organizer)
-> Index Scan using idx_participant_request_id_competition on participant_request pr (cost=0.29..12.01 rows=1 width=4)
Index Cond: (id_competition = comp.id_competition)
Filter: (id_sportsman = sportsman.id_sportsman)
\end{lstlisting}
\begin{figure}[h]
\centering
\includegraphics[width=1\linewidth]{img/sql_9_visual.png}
\caption{Графический план выполнения девятого запроса}
\label{fig:sql_9_visual}
\end{figure}
\newpage
\begin{figure}[H]
\centering
\hspace*{-0.125\linewidth}
\includegraphics[width=1.25\linewidth]{img/diagrams/sql9.png}
\caption{Гистограмма построенная на основе результатов выполнения девятого запроса}
\label{fig:sql_9_gist}
\end{figure}
\newpage
\section* {Заключение}
\addcontentsline{toc}{section}{Заключение}
Для выполнения этой работы был проведён анализ выбранной предметной области -- стрельбы из лука. На его основе были построены ER-диаграмма, диаграмма объектов и схема база данных на русском и английском языках. Затем по полученным схемам и диаграммам были составлены таблицы метаданных.
Для реализации самой базы данных была выбрана система управления базами данных PostgreSQL. По таблицам метаданных на языке SQL были написаны и выполнены команды для создания необходимых таблиц и самой базы данных. Также был на языке Python был написан скрипт для автоматического заполнения базы данных, состоящий из примерно пятисот строк. По резлутатом его работы в базе данных было создано около 280,000 тысяч строк.
Далее были на русском языке были сформулированы девять различных запросов к базе данных. Они были переведены в запросы на языке SQL. Время выполнения и результаты их работы отражены в отчёте.
На выполнение первого этапа работ было потрачено около 15 часов, второго около 10 часов, третьего около 15 часов.
% \newpage
% \section*{Список литературы}
% \addcontentsline{toc}{section}{Список литературы}
% \vspace{-1.5cm}
% \begin{thebibliography}{0}
% \bibitem{1}
% Д. Кук, Г. Бейз, <<Компьютерная математика>>. — 1-е изд. — Москва: Наука, 1990. — 383 с.
% \bibitem{2}
% Новиков, Ф. А. <<Дискретная математика для программистов>>. — 3-е изд. — Санкт-Петербург: Питер, 2009. — 383 с.
% \bibitem{3}
% Полубенцева, М. И. <<Объектно-ориентированное программирование C++>>. — 1-е изд. — Санкт-Петербург: БХВ-Петербург, 2008. — 448 с.
% \end{thebibliography}
% \lstset{
% backgroundcolor=\color{white},
% frame=single
% }
\newpage
\addcontentsline{toc}{section}{Приложение А. Текст создания БД}
\section*{Приложение А. Текст создания БД}
\begin{lstlisting}[label=A]
CREATE DATABASE db_paradox;
\c db_paradox;
CREATE TABLE organizer (
id_organizer SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
logo VARCHAR(1024)
);
CREATE TABLE judge (
id_judge SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(100) NOT NULL,
region VARCHAR(100),
federation VARCHAR(100)
);
CREATE TABLE sportsman (
id_sportsman SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
gender VARCHAR(10) NOT NULL,
birthday DATE,
region VARCHAR(100),
club VARCHAR(100),
federation VARCHAR(100),
category VARCHAR(100)
);
CREATE TABLE division (
id_division SERIAL PRIMARY KEY,
title VARCHAR(100) NOT NULL,
gender VARCHAR(10) NOT NULL,
bow_type VARCHAR(100) NOT NULL,
distance SMALLINT NOT NULL,
msmk SMALLINT NOT NULL,
ms SMALLINT NOT NULL,
kms SMALLINT NOT NULL,
category_1 SMALLINT NOT NULL,
category_2 SMALLINT NOT NULL,
category_3 SMALLINT NOT NULL
);
CREATE TABLE competition (
id_competition SERIAL PRIMARY KEY,
title VARCHAR(100) NOT NULL,
address VARCHAR(100) NOT NULL,
logo VARCHAR(1024),
start_date DATE NOT NULL,
end_date DATE NOT NULL,
id_organizer INTEGER NOT NULL,
FOREIGN KEY (id_organizer) REFERENCES organizer(id_organizer)
);
CREATE INDEX idx_competition_id_organizer ON competition(id_organizer);
CREATE TABLE division_in_competition (
id_division_in_competition SERIAL PRIMARY KEY,
id_division INTEGER NOT NULL,
id_competition INTEGER NOT NULL,
FOREIGN KEY (id_division) REFERENCES division(id_division),
FOREIGN KEY (id_competition) REFERENCES competition(id_competition)
);
CREATE INDEX idx_division_in_competition_id_division ON division_in_competition(id_division);
CREATE INDEX idx_division_in_competition_id_competition ON division_in_competition(id_competition);
CREATE TABLE protocol (
id_protocol SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
date DATE NOT NULL,
file VARCHAR(1024) NOT NULL,
id_competition INTEGER NOT NULL,
FOREIGN KEY (id_competition) REFERENCES competition(id_competition)
);
CREATE INDEX idx_protocol_id_competition ON protocol(id_competition);
CREATE TABLE participant_request (
id_participant_request SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
gender VARCHAR(10) NOT NULL,
birthday DATE NOT NULL,
region VARCHAR(100),
club VARCHAR(100),
federation VARCHAR(100),
category VARCHAR(100) NOT NULL,
is_registered BOOLEAN NOT NULL,
id_competition INTEGER NOT NULL,
id_sportsman INTEGER NOT NULL,
id_division INTEGER NOT NULL,
FOREIGN KEY (id_competition) REFERENCES competition(id_competition),
FOREIGN KEY (id_sportsman) REFERENCES sportsman(id_sportsman),
FOREIGN KEY (id_division) REFERENCES division(id_division)
);
CREATE INDEX idx_participant_request_id_competition ON participant_request(id_competition);
CREATE INDEX idx_participant_request_id_sportsman ON participant_request(id_sportsman);
CREATE INDEX idx_participant_request_id_division ON participant_request(id_division);
CREATE TABLE competition_stage (
id_competition_stage SERIAL PRIMARY KEY,
title VARCHAR(100) NOT NULL,
count_of_series SMALLINT NOT NULL
);
CREATE TABLE result_in_stage (
id_result_in_stage SERIAL PRIMARY KEY,
score SMALLINT NOT NULL,
shield_index SMALLINT NOT NULL,
target_index CHAR(1) NOT NULL,
id_participant_request INTEGER NOT NULL,
id_division INTEGER NOT NULL,
id_competition_stage INTEGER NOT NULL,
FOREIGN KEY (id_participant_request) REFERENCES participant_request(id_participant_request),
FOREIGN KEY (id_division) REFERENCES division(id_division),
FOREIGN KEY (id_competition_stage) REFERENCES competition_stage(id_competition_stage)
);
CREATE INDEX idx_result_in_stage_id_participant_request ON result_in_stage(id_participant_request);
CREATE INDEX idx_result_in_stage_id_division ON result_in_stage(id_division);
CREATE INDEX idx_result_in_stage_id_competition_stage ON result_in_stage(id_competition_stage);
CREATE TABLE judge_request (
id_judge_request SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(100) NOT NULL,
region VARCHAR(100),
federation VARCHAR(100),
is_registered BOOLEAN NOT NULL,
id_competition INTEGER NOT NULL,
id_judge INTEGER NOT NULL,
FOREIGN KEY (id_competition) REFERENCES competition(id_competition),
FOREIGN KEY (id_judge) REFERENCES judge(id_judge)
);
CREATE INDEX idx_judge_request_id_competition ON judge_request(id_competition);
CREATE INDEX idx_judge_request_id_judge ON judge_request(id_judge);
CREATE TABLE shot_series (
id_shot_series SERIAL PRIMARY KEY,
score INTEGER NOT NULL,
photo VARCHAR(1024) NOT NULL,
id_participant_request INTEGER NOT NULL,
id_result_in_stage INTEGER NOT NULL,
id_judge_request INTEGER NOT NULL,
FOREIGN KEY (id_participant_request) REFERENCES participant_request(id_participant_request),
FOREIGN KEY (id_result_in_stage) REFERENCES result_in_stage(id_result_in_stage),
FOREIGN KEY (id_judge_request) REFERENCES judge_request(id_judge_request)
);
CREATE INDEX idx_shot_series_id_participant_request ON shot_series(id_participant_request);
CREATE INDEX idx_shot_series_id_result_in_stage ON shot_series(id_result_in_stage);
CREATE INDEX idx_shot_series_id_judge_request ON shot_series(id_judge_request);
\end{lstlisting}
\newpage
\addcontentsline{toc}{section}{Приложение Б. Текст программы для заполнения БД}
\section*{Приложение Б. Текст программы для заполнения БД}
\addcontentsline{toc}{subsection}{Приложение Б.1. Файл main.py}
\subsection*{Приложение Б.1. Файл main.py}
\begin{lstlisting}[label=B1]
import time
from utils import *
# Параметры подключения к базе данных
conn_params = {
"dbname": "db_univer",
"user": "postgres",
"password": "123",
"host": "localhost",
}
if __name__ == "__main__":
delete_all_rows_from_all_tables(conn_params)
start_time = time.perf_counter()
# Заполняем таблицы первого уровня
load_csv_to_db(conn_params, "division", "division.csv")
load_csv_to_db(conn_params, "organizer", "organizer.csv")
load_csv_to_db(conn_params, "competition_stage", "competition_stage.csv")
load_csv_to_db(conn_params, "sportsman", "sportsman.csv", date_columns=[2])
load_csv_to_db(conn_params, "judge", "judge.csv")
# Второй уровень
generate_competitions(conn_params, 200)
# Третий уровень
generate_protocols(conn_params)
generate_judge_requests(conn_params)
generate_participant_requests(conn_params)
generate_division_in_competitions(conn_params)
# Четвёртый уровень
generate_result_in_stage(conn_params)
# Пятый уровень
generate_shot_series(conn_params)
print_rows_count_summary(conn_params)
print(f"База данных заполнена за {time.perf_counter() - start_time:.3}с")
\end{lstlisting}
\newpage
\addcontentsline{toc}{subsection}{Приложение Б.2. Файл utils.py}
\subsection*{Приложение Б.2. Файл utils.py}
\begin{lstlisting}[label=B2]
import csv
import random
from datetime import datetime, timedelta
import psycopg2
def load_csv_to_db(
conn_params, table_name, csv_file_path, date_columns=None, delimiter=";"
):
"""Добавляет данные из csv таблицы в указанную таблицу в бд. Важно, чтобы названия
столбцов и типы данных в таблицах совпадали. Преобразует даты в указанных столбцах.
"""
# Соединение с базой данных
with psycopg2.connect(**conn_params) as conn:
with conn.cursor() as cur:
# Открытие CSV-файла
with open(csv_file_path, mode="r", encoding="windows-1251") as file:
reader = csv.reader(file, delimiter=delimiter)
# Считывание заголовков для формирования SQL запроса
headers = next(reader)
# Формирование строки запроса с плейсхолдерами для значений
placeholders = ", ".join(["%s"] * len(headers))
insert_query = f"INSERT INTO {table_name} ({', '.join(headers)}) VALUES ({placeholders});"
# Вставка данных в таблицу
for row in reader:
# Преобразование дат, если требуется
if date_columns:
for col_index in date_columns:
if row[col_index]: # Проверяем, не пустое ли значение
row[col_index] = datetime.strptime(
row[col_index], "%d.%m.%Y"
).strftime("%Y-%m-%d")
cur.execute(insert_query, row)
conn.commit()
def print_rows_count_summary(conn_params):
"""Выводит в консолько количество строк во всех таблицах в базе данных, а также
выводит суммарное количество всех строк в базе данных."""
total_rows = 0
with psycopg2.connect(**conn_params) as conn:
with conn.cursor() as cur:
# Получение списка всех таблиц в текущей базе данных
cur.execute(
"""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
"""
)
tables = cur.fetchall()
# Подсчет строк в каждой таблице
for (table_name,) in tables:
cur.execute(f"SELECT COUNT(*) FROM {table_name}")
count = cur.fetchone()[0]
total_rows += count
print(f"Таблица {table_name}: {count} строк")
print("Всего строк в базе данных: ", total_rows)
print()
def delete_all_rows_from_all_tables(conn_params):
"""Очищает базу данных. Удаляет все строки из всех таблиц, но оставляет
сами таблицы."""
with psycopg2.connect(**conn_params) as conn:
with conn.cursor() as cur:
# Отключение ограничений foreign key для избежания конфликтов при удалении
cur.execute("SET session_replication_role = 'replica';")
# Получение списка всех таблиц в текущей базе данных
cur.execute(
"""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
"""
)
tables = cur.fetchall()
# Удаление всех строк из каждой таблицы
for (table_name,) in tables:
cur.execute(f"DELETE FROM {table_name}")
print(f"Все строки удалены из таблицы {table_name}")
# Восстановление ограничений foreign key
cur.execute("SET session_replication_role = 'origin';")
# Подтверждение всех изменений
conn.commit()
print()
def generate_competitions(conn_params, num_records):
with psycopg2.connect(**conn_params) as conn:
with conn.cursor() as cur:
# Получение списка ID организаторов
cur.execute("SELECT id_organizer FROM organizer")
organizer_ids = [row[0] for row in cur.fetchall()]
for _ in range(num_records):
# Генерация случайного организатора
id_organizer = random.choice(organizer_ids)
# Генерация случайных дат
start_date = datetime.now() - timedelta(days=random.randint(1, 5 * 365))
end_date = start_date + timedelta(days=random.randint(1, 10))
# Форматирование дат для SQL
start_date = start_date.strftime("%Y-%m-%d")
end_date = end_date.strftime("%Y-%m-%d")
# Вставка данных в таблицу competition
cur.execute(
"""
INSERT INTO competition (title, address, logo, start_date, end_date, id_organizer)
VALUES (%s, %s, %s, %s, %s, %s);
""",
(
"Competition Title",
"Competition Address",
"Competition Logo",
start_date,
end_date,
id_organizer,
),
)
conn.commit()
def generate_protocols(conn_params):
with psycopg2.connect(**conn_params) as conn:
with conn.cursor() as cur:
# Получение ID соревнований, которые еще не имеют протоколов
cur.execute(
"""
SELECT id_competition FROM competition
WHERE id_competition NOT IN (SELECT id_competition FROM protocol)
"""
)
competition_ids = [row[0] for row in cur.fetchall()]
for comp_id in competition_ids:
# Генерация случайной даты и имени файла
date = datetime.now() - timedelta(days=random.randint(1, 5 * 365))
date = date.strftime("%Y-%m-%d")
file_name = f"protocol_{comp_id}.pdf"
# Вставка данных в таблицу protocol
cur.execute(
"""
INSERT INTO protocol (name, date, file, id_competition)
VALUES (%s, %s, %s, %s);
""",
(f"Protocol for Competition {comp_id}", date, file_name, comp_id),
)
conn.commit()
def generate_judge_requests(conn_params):
with psycopg2.connect(**conn_params) as conn:
with conn.cursor() as cur:
# Получение списка всех соревнований
cur.execute("SELECT id_competition FROM competition")
competitions = [row[0] for row in cur.fetchall()]
# Получение списка всех судей
cur.execute(
"SELECT id_judge, name, category, region, federation FROM judge"
)
judges = cur.fetchall()
for competition_id in competitions:
# Выбор случайного количества судей от 5 до 7 для каждого соревнования
selected_judges = random.sample(judges, random.randint(5, 7))
for judge in selected_judges:
id_judge, name, category, region, federation = judge
# Определение статуса регистрации
is_registered = random.choices([True, False], [0.9, 0.1])[0]
# Вставка данных в таблицу judge_request
cur.execute(
"""
INSERT INTO judge_request (name, category, region, federation, is_registered, id_competition, id_judge)
VALUES (%s, %s, %s, %s, %s, %s, %s);
""",
(
name,
category,
region,
federation,
is_registered,
competition_id,
id_judge,
),
)
conn.commit()
def generate_participant_requests(conn_params):
with psycopg2.connect(**conn_params) as conn:
with conn.cursor() as cur:
# Получение списка всех соревнований
cur.execute("SELECT id_competition FROM competition")
competitions = [row[0] for row in cur.fetchall()]
# Получение списка всех спортсменов
cur.execute(
"SELECT id_sportsman, name, gender, birthday, region, club, federation, category FROM sportsman"
)
sportsmen = cur.fetchall()
for competition_id in competitions:
# Выбор случайного количества участников
selected_sportsmen = random.sample(sportsmen, random.randint(30, 60))
for sportsman in selected_sportsmen:
(
id_sportsman,
name,
gender,
birthday,
region,
club,
federation,
category,
) = sportsman
# Определение статуса регистрации
is_registered = random.choices([True, False], [0.9, 0.1])[0]
# Выбор подходящих дивизионов с учетом гендера
cur.execute(
"""
SELECT id_division FROM division
WHERE gender = %s
""",
(gender,),
)
divisions = [row[0] for row in cur.fetchall()]
# Выбор случайного дивизиона из подходящих
id_division = random.choice(divisions)
# Вставка данных в таблицу participant_request
cur.execute(
"""
INSERT INTO participant_request (
name, gender, birthday, region, club, federation, category, is_registered, id_competition, id_sportsman, id_division
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
""",
(
name,
gender,
birthday,
region,
club,
federation,
category,
is_registered,
competition_id,
id_sportsman,
id_division,
),
)
conn.commit()
def generate_division_in_competitions(conn_params):
with psycopg2.connect(**conn_params) as conn:
with conn.cursor() as cur:
# Получение списка всех соревнований
cur.execute("SELECT id_competition FROM competition")
competitions = [row[0] for row in cur.fetchall()]
# Получение списка всех дивизионов
cur.execute("SELECT id_division FROM division")
divisions = [row[0] for row in cur.fetchall()]
for competition_id in competitions:
# Выбор случайного количества дивизионов
selected_divisions = random.sample(divisions, random.randint(8, 16))
for division_id in selected_divisions:
# Вставка данных в таблицу division_in_competition
cur.execute(
"""
INSERT INTO division_in_competition (id_division, id_competition)
VALUES (%s, %s);
""",
(division_id, competition_id),
)
conn.commit()
def generate_result_in_stage(conn_params):
with psycopg2.connect(**conn_params) as conn:
with conn.cursor() as cur:
# Получение списка всех заявок участников
cur.execute(
"""
SELECT id_participant_request, id_division
FROM participant_request
"""
)
participant_requests = cur.fetchall()
# Получение списка всех этапов соревнований
cur.execute("SELECT id_competition_stage FROM competition_stage")
competition_stages = [row[0] for row in cur.fetchall()]
for request in participant_requests:
id_participant_request, id_division = request
# Количество результатов для каждой заявки
num_results = random.randint(2, 6)
for _ in range(num_results):
score = random.randint(150, 300)
shield_index = random.randint(0, 20)
target_index = random.choice(["A", "B", "C", "D"])
id_competition_stage = random.choice(competition_stages)
# Вставка данных в таблицу result_in_stage
cur.execute(
"""
INSERT INTO result_in_stage (score, shield_index, target_index, id_participant_request, id_division, id_competition_stage)
VALUES (%s, %s, %s, %s, %s, %s);
""",
(
score,
shield_index,
target_index,
id_participant_request,
id_division,
id_competition_stage,
),
)
conn.commit()
def generate_shot_series(conn_params):
with psycopg2.connect(**conn_params) as conn:
with conn.cursor() as cur:
# Получение всех записей result_in_stage и связанных с ними данных
cur.execute(
"""
SELECT rs.id_result_in_stage, rs.id_participant_request, pr.id_competition
FROM result_in_stage rs
JOIN participant_request pr ON rs.id_participant_request = pr.id_participant_request
"""
)
results = cur.fetchall()
# Получение id судей с учетом id_competition
cur.execute(
"""
SELECT id_judge_request, id_competition FROM judge_request
"""
)
judges = cur.fetchall()
# Сопоставление судей с соревнованиями
judge_map = {}
for judge_id, comp_id in judges:
if comp_id in judge_map:
judge_map[comp_id].append(judge_id)
else:
judge_map[comp_id] = [judge_id]
for result in results:
id_result_in_stage, id_participant_request, id_competition = result
# Выбор случайного судьи для данного соревнования
if id_competition in judge_map:
id_judge_request = random.choice(judge_map[id_competition])
else:
# Если нет судьи для соревнования, пропускаем
raise KeyError(f"Нету судей для соревнования {id_competition}")
# Выбираем случайное количество серий выстрелов
num_shot_series = random.randint(3, 10)
for _ in range(num_shot_series):
score = random.randint(2, 10)
photo = "path/to/photo.jpg" # Пример пути к фото
# Вставка данных в таблицу shot_series
cur.execute(
"""
INSERT INTO shot_series (score, photo, id_participant_request, id_result_in_stage, id_judge_request)
VALUES (%s, %s, %s, %s, %s);
""",
(
score,
photo,
id_participant_request,
id_result_in_stage,
id_judge_request,
),
)
conn.commit()
\end{lstlisting}
\end{document}