\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}