From 71f1563d007d4cee0b40e1f0c9f5b22cca656d05 Mon Sep 17 00:00:00 2001 From: Arity-T Date: Tue, 8 Oct 2024 14:53:52 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- report.tex | 1928 ++-------------------------------------------------- 1 file changed, 63 insertions(+), 1865 deletions(-) diff --git a/report.tex b/report.tex index bea0f71..2456747 100644 --- a/report.tex +++ b/report.tex @@ -141,21 +141,6 @@ \newpage \tableofcontents - - - - - - - - - - - - - - - \newpage @@ -163,1876 +148,89 @@ \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{Сущности} - В этом разделе представлены основные сущности системы и их атрибуты. + \section {Постановка задачи} + В ходе прохождения данного курса необходимо выполнить пять лабораторных работ. \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} - + \item Создать представление, инкапсулирующее запрос. Написать запрос, использующий в себе представление. + \item Написать триггеры, автоматизирующие сбор статистической информации о количестве соревнований, в которых участвовал каждый судья. + \item Создать двух пользователей. Первый должен иметь доступ только на просмотр представления из первого задания. Второй также должен уметь редактировать таблицы, участвующие в запросе представления. \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{Проектирование базы данных} + \section {Лабораторные работы} + \subsection{Работа 1: Создание представлений} + \textbf{Задача:} Разработать представление для хранения запроса внутри СУБД. - \subsection{Технология работы с СУБД} - В качестве системы управления базами данных была выбрана PostgreSQL. Она является одной из самых популярных бесплатных СУБД и поддерживает весь необходимый функционал. + \textbf{Формулировка задачи:} Посчитать для каждого спортсмена число заявок и число серий. Представление выдает только идентификатор спортсмена, число его заявок на соревнования и число серий выстрелов. Использовать представление в другом запросе. - 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}. + Сделано представление sportsman\_requests\_series\_count, которое хранит запрос, выполняющий поставленную задачу. В представлении участвуют таблицы \texttt{sportsman}, \texttt{participant\_request} и \texttt{shot\_series}. Код запроса для создания представления представлен в листинге~\ref{lst:view1}. \begin{figure}[h] \centering - \includegraphics[width=1\linewidth]{img/slash_d.png} - \caption{Пример работы команды \textbackslash d} - \label{fig:slash_d} + \includegraphics[width=0.4\linewidth]{img/view_result.png} + \caption{Первые десять записей в представлении \texttt{sportsman\_requests\_series\_count}.} + \label{fig:view} \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 + \begin{lstlisting}[mathescape=true, language=SQL, caption={Запрос для создания представления \texttt{sportsman\_requests\_series\_count}.}, label={lst:view1}] + create view + sportsman_requests_series_count as + select s.id_sportsman, - s.name, - s.gender, - pr.id_competition, - jr.id_judge + count(distinct pr.id_participant_request) as pr_count, + count(ss.id_shot_series) as ss_count 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 + left join participant_request as pr + on s.id_sportsman = pr.id_sportsman + left join shot_series as ss + on pr.id_participant_request = ss.id_participant_request + group by + s.id_sportsman; +\end{lstlisting} + + Получившееся представление представлено на Рис.~\ref{fig:view}. + + Также был составлен пример запроса, в котором используется представление \texttt{sportsman\_requests\_series\_count}, его код представлен в листинге~\ref{lst:view2}. В этом запросе выбираются 10 самых активных с точки зрения участия в соревнованиях спортсменов из клуба <<ЛК Парадокс Лучника>>. К исходному представлению также добавляются дополнительные столбцы с данными спортсмена из таблицы \texttt{sportsman}. Результат выполнения этого запроса представлен на Рис.~\ref{fig:view_query}. + + \begin{lstlisting}[mathescape=true, language=SQL, caption={Код примера запроса, в котором используется представление \texttt{sportsman\_requests\_series\_count}.}, label={lst:view2}] + select + srsc.id_sportsman, + s.name, + s.category, + srsc.pr_count, + srsc.ss_count + from + sportsman_requests_series_count as srsc + join sportsman as s on srsc.id_sportsman = s.id_sportsman 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); + s.gender = 'Мужчина' + and s.club = 'ЛК Парадокс Лучника' + order by + srsc.pr_count desc + limit 10; \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} + \begin{figure}[h] + \centering + \includegraphics[width=1\linewidth]{img/view_query.png} + \caption{Первые десять записей результата выполнения запроса с участием представления \texttt{sportsman\_requests\_series\_count}.} + \label{fig:view_query} + \end{figure} + + Созданное представление нельзя обновлять напрямую, а попытки его обновить приведут к ошибке, которая демонстрируется на Рис.~\ref{fig:insert_error}. Изменить данные представления можно только изменив исходные таблицы, которые в нём участвуют. + + \begin{figure}[h] + \centering + \includegraphics[width=1\linewidth]{img/insert_error.png} + \caption{Результат попытки изменения данных напрямую через представление \texttt{sportsman\_requests\_series\_count}.} + \label{fig:insert_error} + \end{figure} \end{document} \ No newline at end of file