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