mirror of
https://github.com/EDeev/mobiles_dataset.git
synced 2026-10-08 04:59:41 +03:00
Compare commits
No commits in common. "7277093a13dc024695811d5cfcb21cd18f8c0b99" and "fe5e71a2486dfe579a932bfb2d1d51961b1b4c9d" have entirely different histories.
7277093a13
...
fe5e71a248
20 changed files with 6917 additions and 4551 deletions
5
.gitattributes
vendored
5
.gitattributes
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
* text=auto eol=lf
|
|
||||||
*.png binary
|
|
||||||
*.pdf binary
|
|
||||||
*.docx binary
|
|
||||||
*.pgerd text
|
|
||||||
36
.github/workflows/ci.yml
vendored
36
.github/workflows/ci.yml
vendored
|
|
@ -1,36 +0,0 @@
|
||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
env:
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
ports: ["5432:5432"]
|
|
||||||
options: >-
|
|
||||||
--health-cmd "pg_isready -U postgres" --health-interval 5s --health-retries 10
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.11"
|
|
||||||
- run: pip install ruff==0.14.0 psycopg2-binary==2.9.9 pandas==2.1.4 numpy==1.26.2
|
|
||||||
- name: Ruff (синтаксис и ошибки)
|
|
||||||
run: ruff check --select E9,F .
|
|
||||||
- name: Схема и импорт датасета
|
|
||||||
env:
|
|
||||||
PGPASSWORD: postgres
|
|
||||||
DB_USER: postgres
|
|
||||||
DB_PASSWORD: postgres
|
|
||||||
run: |
|
|
||||||
psql -h localhost -U postgres -f sql/create_schema.sql
|
|
||||||
python scripts/import_data.py
|
|
||||||
test "$(psql -h localhost -U postgres -d mobile_devices_db -tAc 'select count(*) from models')" -gt 900
|
|
||||||
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
|
|
@ -1,23 +0,0 @@
|
||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags: ["v*"]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
exe:
|
|
||||||
runs-on: windows-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.11"
|
|
||||||
- run: pip install pyinstaller psycopg2-binary==2.9.9 PyQt6==6.6.1
|
|
||||||
- name: Сборка .exe из однофайловой версии
|
|
||||||
run: pyinstaller --onefile --windowed --name MobileDevices scripts/exe/mobile_devices.py
|
|
||||||
- name: Файл в релиз
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
run: gh release upload "${{ github.ref_name }}" dist/MobileDevices.exe --clobber
|
|
||||||
File diff suppressed because it is too large
Load diff
76
README.en.md
76
README.en.md
|
|
@ -1,76 +0,0 @@
|
||||||
# Mobile Devices Database
|
|
||||||
|
|
||||||
[Русский](README.md) · **English**
|
|
||||||
|
|
||||||
[](https://github.com/EDeev/mobiles_dataset/actions/workflows/ci.yml)
|
|
||||||
[](https://github.com/EDeev/mobiles_dataset/releases)
|
|
||||||
|
|
||||||
Database design coursework: the 2025 mobile devices dataset split into a normalized PostgreSQL database,
|
|
||||||
a PyQt6 desktop app to work with it, and a measurement of index gains with `EXPLAIN ANALYZE`.
|
|
||||||
|
|
||||||
**Status:** coursework ("Database Design and Administration", Moscow Polytechnic University, 2025), completed
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
**Stack:** Python 3.11 · PostgreSQL 15 · psycopg2 · PyQt6 · pandas · Docker
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- 3NF schema of five tables (companies, processors, models, regions, prices) with foreign keys and
|
|
||||||
cascading actions
|
|
||||||
- Import of the [Kaggle dataset](https://www.kaggle.com/datasets/abdulmalik1518/mobiles-dataset-2025)
|
|
||||||
(930 rows; 914 models and 4569 prices after cleaning) split into reference tables
|
|
||||||
- App: adding companies, models with create, edit and delete, prices in five regions (Pakistan, India,
|
|
||||||
China, UAE, USA) with currency symbols, search by name, company and RAM, a price statistics tab
|
|
||||||
- Index experiment: `EXPLAIN ANALYZE` queries before and after, plans saved in `sql/explain_results/`
|
|
||||||
|
|
||||||
| Metric | No indexes | With indexes | Difference |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Search | 0.234 ms | 0.089 ms | 62% faster |
|
|
||||||
| Four-table JOIN | 18.6 ms | 0.95 ms | 95% faster |
|
|
||||||
| Planner cost | 44.76 | 12.45 | 72% lower |
|
|
||||||
| Rows scanned | 914 | 18 | 98% fewer |
|
|
||||||
|
|
||||||
Measured on this dataset (hundreds of rows), so absolute times are fractions of a millisecond.
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/EDeev/mobiles_dataset.git && cd mobiles_dataset
|
|
||||||
docker compose up -d # PostgreSQL 15 with the schema from sql/create_schema.sql
|
|
||||||
pip install -r requirements.txt
|
|
||||||
python scripts/import_data.py # load the dataset
|
|
||||||
python main.py # the app
|
|
||||||
```
|
|
||||||
|
|
||||||
A ready-made Windows build (`.exe`) is in the [releases](https://github.com/EDeev/mobiles_dataset/releases).
|
|
||||||
|
|
||||||
## Without Docker
|
|
||||||
|
|
||||||
1. Run `sql/create_schema.sql` in psql or pgAdmin as `postgres` — the script creates the
|
|
||||||
`mobile_devices_db` database itself.
|
|
||||||
2. Set the connection with `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
|
|
||||||
(defaults: `localhost:5432`, `mobile_devices_db`, `admin` / `password`).
|
|
||||||
3. `python scripts/import_data.py`, then `python main.py`.
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
|
|
||||||
| Companies | Analytics |
|
|
||||||
|---|---|
|
|
||||||
|  |  |
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Coursework (Database Design and Administration, Moscow Polytechnic University, 2025). The code is open
|
|
||||||
for study; there is no separate license. The dataset comes from Kaggle under its author's terms.
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
**Egor Deev** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<sub>⭐ If you find this project useful, give it a star on GitHub!</sub>
|
|
||||||
<p><sub>Made with ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
|
|
||||||
</div>
|
|
||||||
331
README.md
331
README.md
|
|
@ -1,89 +1,290 @@
|
||||||
# Mobile Devices Database
|
# 📱 Mobile Devices Database Management System
|
||||||
|
|
||||||
**Русский** · [English](README.en.md)
|
**Комплексная система управления каталогом мобильных устройств с GUI-интерфейсом**
|
||||||
|
|
||||||
[](https://github.com/EDeev/mobiles_dataset/actions/workflows/ci.yml)
|
[](https://python.org)
|
||||||
[](https://github.com/EDeev/mobiles_dataset/releases)
|
[](https://postgresql.org)
|
||||||
|
[](https://pypi.org/project/PyQt6/)
|
||||||
|
[](#)
|
||||||
|
|
||||||
Курсовая по проектированию баз данных: датасет мобильных устройств 2025 года, разложенный в
|
## 🎯 Назначение проекта
|
||||||
нормализованную базу PostgreSQL, десктопное приложение на PyQt6 для работы с ним и измерение
|
|
||||||
выигрыша от индексов через `EXPLAIN ANALYZE`.
|
|
||||||
|
|
||||||
**Статус:** учебный проект (курсовая «Проектирование и администрирование баз данных», Московский
|
Курсовая работа по дисциплине "Проектирование и администрирование баз данных", представляющая собой полнофункциональную информационную систему для управления данными о мобильных устройствах. Проект демонстрирует практическое применение принципов реляционного моделирования, оптимизации производительности СУБД и создания современных desktop-приложений.
|
||||||
Политех, 2025), завершён
|
|
||||||
|
|
||||||

|
## 🏗️ Архитектура системы
|
||||||
|
|
||||||
**Стек:** Python 3.11 · PostgreSQL 15 · psycopg2 · PyQt6 · pandas · Docker
|
### Компонентная структура
|
||||||
|
```
|
||||||
## Возможности
|
📦 mobile-devices-db/
|
||||||
|
├── database.py # Слой доступа к данным (DAO)
|
||||||
- Схема в 3НФ из пяти таблиц (компании, процессоры, модели, регионы, цены) с внешними ключами и
|
├── main_window.py # GUI-интерфейс на PyQt6
|
||||||
каскадными операциями
|
├── main.py # Запускаемый файл
|
||||||
- Импорт [датасета с Kaggle](https://www.kaggle.com/datasets/abdulmalik1518/mobiles-dataset-2025)
|
├── Mobiles Dataset 2025.csv # Дата-сет данных
|
||||||
(930 строк, после очистки 914 моделей и 4569 цен) с разнесением по справочникам
|
├── 🗃️ sql/ # SQL-скрипты и схема БД
|
||||||
- Приложение: добавление компаний, модели с добавлением, правкой и удалением, цены по пяти регионам (Пакистан,
|
│ ├── create_schema.sql # Создание нормализованной структуры
|
||||||
Индия, Китай, ОАЭ, США) с символами валют, поиск по названию, компании и RAM, вкладка статистики цен
|
│ └── performance_analysis.sql # Тестовые запросы для анализа
|
||||||
- Эксперимент с индексами: запросы `EXPLAIN ANALYZE` до и после, планы сохранены в `sql/explain_results/`
|
├── 🐍 scripts/ # Python-модули системы
|
||||||
|
│ └── import_data.py # Система импорта и нормализации
|
||||||
| Метрика | Без индексов | С индексами | Разница |
|
├── 📊 docs/ # Техническая документация
|
||||||
|---|---|---|---|
|
└── 📋 requirements.txt # Зависимости проекта
|
||||||
| Поиск | 0,234 мс | 0,089 мс | быстрее на 62 % |
|
|
||||||
| JOIN четырёх таблиц | 18,6 мс | 0,95 мс | быстрее на 95 % |
|
|
||||||
| Стоимость по планировщику | 44,76 | 12,45 | ниже на 72 % |
|
|
||||||
| Просмотренные строки | 914 | 18 | меньше на 98 % |
|
|
||||||
|
|
||||||
Измерено на этом датасете (сотни строк), поэтому абсолютные времена — доли миллисекунды.
|
|
||||||
|
|
||||||
## Быстрый старт
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/EDeev/mobiles_dataset.git && cd mobiles_dataset
|
|
||||||
docker compose up -d # PostgreSQL 15 со схемой из sql/create_schema.sql
|
|
||||||
pip install -r requirements.txt
|
|
||||||
python scripts/import_data.py # загрузка датасета
|
|
||||||
python main.py # приложение
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Готовая сборка для Windows (`.exe`) — в [релизах](https://github.com/EDeev/mobiles_dataset/releases).
|
### Технологический стек
|
||||||
|
- **СУБД**: PostgreSQL 15.x с расширенными возможностями индексирования
|
||||||
|
- **Backend**: Python 3.11+ с паттерном Singleton для управления соединениями
|
||||||
|
- **Frontend**: PyQt6 с архитектурой Model-View-Controller
|
||||||
|
- **Интеграция**: psycopg2 для типобезопасного взаимодействия с БД
|
||||||
|
|
||||||
## Без Docker
|
## ⚡ Ключевые особенности
|
||||||
|
|
||||||
1. Выполните `sql/create_schema.sql` в psql или pgAdmin под пользователем `postgres` — скрипт сам
|
### 🔧 Техническая реализация
|
||||||
создаёт базу `mobile_devices_db`.
|
- **Нормализация до 3НФ**: Декомпозиция исходного CSV в 5 взаимосвязанных таблиц
|
||||||
2. Задайте подключение переменными `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
|
- **Референциальная целостность**: Система FK с каскадными операциями
|
||||||
(по умолчанию `localhost:5432`, `mobile_devices_db`, `admin` / `password`).
|
- **Оптимизация производительности**: Стратегическое индексирование с 62-95% улучшением
|
||||||
3. `python scripts/import_data.py`, затем `python main.py`.
|
- **CRUD-функционал**: Полный спектр операций через интуитивный GUI
|
||||||
|
|
||||||
## Как выглядит
|
### 📈 Результаты оптимизации
|
||||||
|
| Метрика | До оптимизации | После оптимизации | Улучшение |
|
||||||
|
|---------|----------------|-------------------|-----------|
|
||||||
|
| Время выполнения поиска | 0.234 мс | 0.089 мс | **62% быстрее** |
|
||||||
|
| JOIN-запросы (4 таблицы) | 18.6 мс | 0.95 мс | **95% быстрее** |
|
||||||
|
| Стоимость по планировщику | 44.76 ед. | 12.45 ед. | **72% снижение** |
|
||||||
|
| Обработанные строки | 914 (фильтрация) | 18 (прямой доступ) | **98% сокращение** |
|
||||||
|
|
||||||
| Компании | Аналитика |
|
### 🌍 Функциональные возможности
|
||||||
|---|---|
|
- **Интеллектуальный поиск** по множественным атрибутам устройств
|
||||||
|  |  |
|
- **Валютная локализация** ценовой информации (5 регионов)
|
||||||
|
- **Автоматический импорт** данных с нормализацией на лету
|
||||||
|
- **Аналитические отчеты** с региональной статистикой
|
||||||
|
|
||||||
## Структура
|
## 📊 Источники данных
|
||||||
|
|
||||||
```
|
Проект использует актуальный датасет мобильных устройств 2025 года:
|
||||||
database.py доступ к БД (синглтон, параметризованные запросы)
|
- **Источник**: [Kaggle - Mobiles Dataset 2025](https://www.kaggle.com/datasets/abdulmalik1518/mobiles-dataset-2025)
|
||||||
main_window.py, main.py приложение на PyQt6
|
- **Размер**: 1000+ записей устройств
|
||||||
scripts/import_data.py импорт и нормализация датасета
|
- **Охват**: Основные производители (Apple, Samsung, Xiaomi, OnePlus, etc.)
|
||||||
scripts/exe/ однофайловая версия приложения для сборки .exe
|
- **Регионы**: Пакистан, Индия, Китай, США, ОАЭ
|
||||||
sql/ схема, запросы для анализа производительности, ERD, результаты EXPLAIN
|
|
||||||
docs/report.* отчёт по курсовой (md, docx, pdf)
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
### Системные требования
|
||||||
|
- **Python 3.11+** с поддержкой venv
|
||||||
|
- **PostgreSQL 15.x** с административными правами
|
||||||
|
- **4GB RAM** (рекомендуется 8GB для больших наборов данных)
|
||||||
|
|
||||||
|
### Установка и настройка
|
||||||
|
|
||||||
|
1. **Клонирование репозитория**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/username/mobile-devices-db.git
|
||||||
|
cd mobile-devices-db
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Создание виртуального окружения**
|
||||||
|
```bash
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate # Linux/macOS
|
||||||
|
# или
|
||||||
|
venv\Scripts\activate # Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Установка зависимостей**
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Настройка PostgreSQL**
|
||||||
|
```sql
|
||||||
|
-- Создание пользователя и БД
|
||||||
|
CREATE USER admin WITH PASSWORD 'your_password';
|
||||||
|
CREATE DATABASE mobile_devices_db OWNER admin;
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE mobile_devices_db TO admin;
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Развертывание схемы данных**
|
||||||
|
```bash
|
||||||
|
psql -U admin -d mobile_devices_db -f sql/create_schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Импорт тестовых данных**
|
||||||
|
```bash
|
||||||
|
python scripts/import_data.py
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Запуск приложения**
|
||||||
|
```bash
|
||||||
|
python scripts/main_window.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Структура базы данных
|
||||||
|
|
||||||
|
### Реляционная модель (3НФ)
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
COMPANIES ||--o{ MODELS : manufactures
|
||||||
|
PROCESSORS ||--o{ MODELS : uses
|
||||||
|
MODELS ||--o{ PRICES : "has prices in"
|
||||||
|
REGIONS ||--o{ PRICES : "contains prices"
|
||||||
|
|
||||||
|
COMPANIES {
|
||||||
|
int company_id PK
|
||||||
|
varchar company_name UK
|
||||||
|
}
|
||||||
|
|
||||||
|
MODELS {
|
||||||
|
int model_id PK
|
||||||
|
varchar model_name
|
||||||
|
int company_id FK
|
||||||
|
int processor_id FK
|
||||||
|
varchar ram
|
||||||
|
varchar battery_capacity
|
||||||
|
int launched_year
|
||||||
|
}
|
||||||
|
|
||||||
|
PRICES {
|
||||||
|
int price_id PK
|
||||||
|
int model_id FK
|
||||||
|
int region_id FK
|
||||||
|
decimal price
|
||||||
|
varchar currency
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Лицензия
|
### Ключевые таблицы
|
||||||
|
- **Companies**: Справочник производителей (19 уникальных компаний)
|
||||||
|
- **Models**: Каталог устройств (914 моделей с техническими характеристиками)
|
||||||
|
- **Processors**: Справочник процессоров (217 уникальных чипсетов)
|
||||||
|
- **Regions**: Географические регионы ценообразования (5 регионов)
|
||||||
|
- **Prices**: Региональная ценовая информация (4,569 записей)
|
||||||
|
|
||||||
Учебный проект (Проектирование и администрирование баз данных, Московский Политех, 2025). Код открыт
|
## 🎨 Пользовательский интерфейс
|
||||||
для изучения, отдельной лицензии нет. Датасет — с Kaggle, на условиях его автора.
|
|
||||||
|
|
||||||
## Автор
|
### Основные компоненты
|
||||||
|
- **📋 Каталог компаний**: Обзор производителей с количеством моделей
|
||||||
|
- **📱 Управление моделями**: CRUD-операции с техническими характеристиками
|
||||||
|
- **💰 Ценовое управление**: Редактирование цен с валютной локализацией
|
||||||
|
- **📊 Аналитическая панель**: Региональная статистика и отчеты
|
||||||
|
|
||||||
**Деев Егор Викторович** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
|
### Функциональные возможности
|
||||||
|
```python
|
||||||
|
# Пример поискового запроса
|
||||||
|
def search_models(self, search_text: str):
|
||||||
|
"""Интеллектуальный поиск по множественным атрибутам"""
|
||||||
|
return self.db.search_models(search_text)
|
||||||
|
|
||||||
|
# Валютная локализация
|
||||||
|
CURRENCY_MAP = {
|
||||||
|
'Pakistan': ('PKR', '₨'),
|
||||||
|
'India': ('INR', '₹'),
|
||||||
|
'China': ('CNY', '¥'),
|
||||||
|
'USA': ('USD', '$'),
|
||||||
|
'Dubai': ('AED', 'د.إ')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Анализ производительности
|
||||||
|
|
||||||
|
### Методология тестирования
|
||||||
|
Комплексный анализ производительности выполнен с использованием PostgreSQL EXPLAIN ANALYZE:
|
||||||
|
|
||||||
|
#### Тестовые сценарии
|
||||||
|
1. **Простые селективные запросы** - поиск по названию компании
|
||||||
|
2. **Многотабличные JOIN** - получение полной информации об устройствах
|
||||||
|
3. **Агрегирующие операции** - статистические запросы с группировкой
|
||||||
|
4. **Поиск по характеристикам** - фильтрация по техническим параметрам
|
||||||
|
|
||||||
|
#### Стратегия оптимизации
|
||||||
|
```sql
|
||||||
|
-- Базовые B-tree индексы
|
||||||
|
CREATE INDEX idx_companies_name ON companies(company_name);
|
||||||
|
CREATE INDEX idx_models_company_id ON models(company_id);
|
||||||
|
|
||||||
|
-- Составные индексы для комплексных запросов
|
||||||
|
CREATE INDEX idx_models_ram_battery ON models(ram, battery_capacity);
|
||||||
|
|
||||||
|
-- Функциональные индексы для LIKE-операций
|
||||||
|
CREATE INDEX idx_companies_name_pattern ON companies(company_name varchar_pattern_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 Практические результаты
|
||||||
|
|
||||||
|
### Количественные показатели
|
||||||
|
- **Импорт данных**: 930 записей устройств с 100% целостностью
|
||||||
|
- **Нормализация**: 60% сокращение избыточности данных
|
||||||
|
- **Производительность**: 95% улучшение для сложных JOIN-запросов
|
||||||
|
- **Индексирование**: 72% снижение стоимости по планировщику
|
||||||
|
|
||||||
|
### Качественные достижения
|
||||||
|
- Полная автоматизация процесса нормализации данных
|
||||||
|
- Интуитивный GUI с поддержкой валютной локализации
|
||||||
|
- Масштабируемая архитектура для enterprise-развертывания
|
||||||
|
- Комплексная система мониторинга производительности
|
||||||
|
|
||||||
|
## 🔧 Документация
|
||||||
|
|
||||||
|
- **[Техническая документация](docs/README.md)** - Подробное описание архитектуры и API
|
||||||
|
- **[Отчет по курсовой работе](docs/report.md)** - Полный академический отчет
|
||||||
|
- **[SQL-скрипты](sql/)** - Схемы БД и запросы для анализа производительности
|
||||||
|
|
||||||
|
### Конфигурация базы данных
|
||||||
|
```python
|
||||||
|
# Параметры подключения
|
||||||
|
DATABASE_CONFIG = {
|
||||||
|
'host': 'localhost',
|
||||||
|
'port': 5432,
|
||||||
|
'database': 'mobile_devices_db',
|
||||||
|
'user': 'admin',
|
||||||
|
'password': 'your_password'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Основные зависимости
|
||||||
|
```
|
||||||
|
psycopg2-binary==2.9.9 # PostgreSQL adapter
|
||||||
|
PyQt6==6.6.1 # GUI framework
|
||||||
|
pandas==2.1.4 # Data processing
|
||||||
|
numpy==1.26.2 # Numerical computing
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Перспективы развития
|
||||||
|
|
||||||
|
### Краткосрочные цели
|
||||||
|
- **Веб-интерфейс**: Реализация REST API на FastAPI
|
||||||
|
- **Автоматизация**: Интеграция с внешними API для обновления каталога
|
||||||
|
- **Мониторинг**: Real-time дашборды производительности
|
||||||
|
|
||||||
|
### Долгосрочная стратегия
|
||||||
|
- **Machine Learning**: Прогнозирование ценовых тенденций
|
||||||
|
- **Микросервисы**: Архитектурная модернизация для cloud-deployment
|
||||||
|
- **Аналитика**: Интеграция с Apache Kafka для stream-processing
|
||||||
|
|
||||||
|
## 📚 Академическая ценность
|
||||||
|
|
||||||
|
Проект демонстрирует практическое применение ключевых концепций:
|
||||||
|
|
||||||
|
### Теоретические основы
|
||||||
|
- **Нормализация реляционных данных** до третьей нормальной формы
|
||||||
|
- **Оптимизация производительности СУБД** через стратегическое индексирование
|
||||||
|
- **Архитектурные паттерны** для desktop-приложений с базами данных
|
||||||
|
|
||||||
|
### Практические навыки
|
||||||
|
- Проектирование и реализация нормализованных схем данных
|
||||||
|
- Анализ и оптимизация производительности SQL-запросов
|
||||||
|
- Создание многокомпонентных приложений с GUI-интерфейсом
|
||||||
|
|
||||||
|
## 📄 Лицензия
|
||||||
|
|
||||||
|
Этот проект создан в учебных целях как курсовая работа по дисциплине "Проектирование и администрирование баз данных" в Московском Политехническом Университете, 2024-2025 учебный год.
|
||||||
|
|
||||||
|
## 👨💻 Автор
|
||||||
|
|
||||||
|
**Деев Егор Викторович** - Backend Developer
|
||||||
|
- GitHub: [@EDeev](https://github.com/EDeev)
|
||||||
|
- Email: egor@deev.space
|
||||||
|
- Telegram: [@Egor_Deev](https://t.me/Egor_Deev)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<sub>⭐ Если проект оказался полезным, поставьте звёздочку на GitHub!</sub>
|
<p><sub>Создано с ❤️ от вашего дорогого - deev.space ©</sub></p>
|
||||||
<p><sub>Сделано с ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
20
compose.yaml
20
compose.yaml
|
|
@ -1,20 +0,0 @@
|
||||||
# Локальный PostgreSQL 15 со схемой курсовой: docker compose up -d
|
|
||||||
services:
|
|
||||||
db:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
volumes:
|
|
||||||
- ./docker/00-roles.sql:/docker-entrypoint-initdb.d/00-roles.sql:ro
|
|
||||||
- ./sql/create_schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
|
|
||||||
- pgdata:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres -d mobile_devices_db"]
|
|
||||||
interval: 5s
|
|
||||||
retries: 20
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
pgdata:
|
|
||||||
10
database.py
10
database.py
|
|
@ -1,5 +1,4 @@
|
||||||
# db/database.py
|
# db/database.py
|
||||||
import os
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from psycopg2.extras import RealDictCursor
|
from psycopg2.extras import RealDictCursor
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
|
|
@ -21,13 +20,8 @@ class Database:
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self, host=None, port=None, database=None, user=None, password=None):
|
def __init__(self, host='localhost', port=5432, database='mobile_devices_db',
|
||||||
# Параметры подключения: аргументы, затем переменные окружения DB_*, затем значения для локальной разработки
|
user='admin', password='password'):
|
||||||
host = host or os.environ.get('DB_HOST', 'localhost')
|
|
||||||
port = port or int(os.environ.get('DB_PORT', '5432'))
|
|
||||||
database = database or os.environ.get('DB_NAME', 'mobile_devices_db')
|
|
||||||
user = user or os.environ.get('DB_USER', 'admin')
|
|
||||||
password = password or os.environ.get('DB_PASSWORD', 'password')
|
|
||||||
if not hasattr(self, 'initialized'):
|
if not hasattr(self, 'initialized'):
|
||||||
self.connection_params = {
|
self.connection_params = {
|
||||||
'host': host,
|
'host': host,
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- Роль приложения для локального стенда (значения по умолчанию из database.py)
|
|
||||||
CREATE ROLE admin LOGIN SUPERUSER PASSWORD 'password';
|
|
||||||
2374
docs/README.md
Normal file
2374
docs/README.md
Normal file
File diff suppressed because it is too large
Load diff
4746
docs/report.md
4746
docs/report.md
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 82 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 206 KiB |
1
main.py
1
main.py
|
|
@ -1,6 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
from PyQt6.QtWidgets import QApplication
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
|
||||||
from main_window import MainWindow
|
from main_window import MainWindow
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
# ui/main_window.py
|
# ui/main_window.py
|
||||||
|
import sys
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
QTableWidget, QTableWidgetItem, QPushButton, QTabWidget,
|
QTableWidget, QTableWidgetItem, QPushButton, QTabWidget,
|
||||||
QLabel, QLineEdit, QComboBox, QSpinBox, QMessageBox,
|
QLabel, QLineEdit, QComboBox, QSpinBox, QMessageBox,
|
||||||
QDialog, QFormLayout, QDialogButtonBox, QHeaderView,
|
QDialog, QFormLayout, QDialogButtonBox, QHeaderView,
|
||||||
QToolBar, QStatusBar, QGroupBox, QTextEdit, QInputDialog
|
QToolBar, QStatusBar, QGroupBox, QTextEdit, QInputDialog
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
||||||
from PyQt6.QtGui import QAction, QFont
|
from PyQt6.QtGui import QAction, QIcon, QFont
|
||||||
from typing import Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# Импортируем наш модуль БД
|
# Импортируем наш модуль БД
|
||||||
|
|
@ -189,9 +190,6 @@ class PriceDialog(QDialog):
|
||||||
def load_prices(self):
|
def load_prices(self):
|
||||||
"""Загрузка цен из БД"""
|
"""Загрузка цен из БД"""
|
||||||
prices = self.db.get_model_prices(self.model_id)
|
prices = self.db.get_model_prices(self.model_id)
|
||||||
# При включённой сортировке строки переставляются прямо во время заполнения,
|
|
||||||
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
|
|
||||||
self.prices_table.setSortingEnabled(False)
|
|
||||||
self.prices_table.setRowCount(len(prices))
|
self.prices_table.setRowCount(len(prices))
|
||||||
|
|
||||||
for row, price_data in enumerate(prices):
|
for row, price_data in enumerate(prices):
|
||||||
|
|
@ -216,9 +214,6 @@ class PriceDialog(QDialog):
|
||||||
delete_btn = QPushButton("Удалить")
|
delete_btn = QPushButton("Удалить")
|
||||||
delete_btn.clicked.connect(lambda checked, pid=price_data['price_id']: self.delete_price(pid))
|
delete_btn.clicked.connect(lambda checked, pid=price_data['price_id']: self.delete_price(pid))
|
||||||
self.prices_table.setCellWidget(row, 3, delete_btn)
|
self.prices_table.setCellWidget(row, 3, delete_btn)
|
||||||
# сортировку возвращаем только после заполнения (см. начало функции)
|
|
||||||
self.prices_table.setSortingEnabled(True)
|
|
||||||
|
|
||||||
|
|
||||||
def add_update_price(self):
|
def add_update_price(self):
|
||||||
"""Добавление или обновление цены"""
|
"""Добавление или обновление цены"""
|
||||||
|
|
@ -430,9 +425,6 @@ class MainWindow(QMainWindow):
|
||||||
def load_companies(self):
|
def load_companies(self):
|
||||||
"""Загрузка списка компаний"""
|
"""Загрузка списка компаний"""
|
||||||
companies = self.db.get_all_companies()
|
companies = self.db.get_all_companies()
|
||||||
# При включённой сортировке строки переставляются прямо во время заполнения,
|
|
||||||
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
|
|
||||||
self.companies_table.setSortingEnabled(False)
|
|
||||||
self.companies_table.setRowCount(len(companies))
|
self.companies_table.setRowCount(len(companies))
|
||||||
|
|
||||||
for row, company in enumerate(companies):
|
for row, company in enumerate(companies):
|
||||||
|
|
@ -450,9 +442,6 @@ class MainWindow(QMainWindow):
|
||||||
count_item = QTableWidgetItem(str(company['models_count']))
|
count_item = QTableWidgetItem(str(company['models_count']))
|
||||||
count_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
count_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
self.companies_table.setItem(row, 2, count_item)
|
self.companies_table.setItem(row, 2, count_item)
|
||||||
# сортировку возвращаем только после заполнения (см. начало функции)
|
|
||||||
self.companies_table.setSortingEnabled(True)
|
|
||||||
|
|
||||||
|
|
||||||
def load_models(self, search_text=""):
|
def load_models(self, search_text=""):
|
||||||
"""Загрузка списка моделей"""
|
"""Загрузка списка моделей"""
|
||||||
|
|
@ -461,9 +450,6 @@ class MainWindow(QMainWindow):
|
||||||
else:
|
else:
|
||||||
models = self.db.get_all_models()
|
models = self.db.get_all_models()
|
||||||
|
|
||||||
# При включённой сортировке строки переставляются прямо во время заполнения,
|
|
||||||
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
|
|
||||||
self.models_table.setSortingEnabled(False)
|
|
||||||
self.models_table.setRowCount(len(models))
|
self.models_table.setRowCount(len(models))
|
||||||
|
|
||||||
for row, model in enumerate(models):
|
for row, model in enumerate(models):
|
||||||
|
|
@ -522,9 +508,6 @@ class MainWindow(QMainWindow):
|
||||||
delete_btn = QPushButton("🗑️ Удалить")
|
delete_btn = QPushButton("🗑️ Удалить")
|
||||||
delete_btn.clicked.connect(lambda checked, mid=model['model_id']: self.delete_model(mid))
|
delete_btn.clicked.connect(lambda checked, mid=model['model_id']: self.delete_model(mid))
|
||||||
self.models_table.setCellWidget(row, 10, delete_btn)
|
self.models_table.setCellWidget(row, 10, delete_btn)
|
||||||
# сортировку возвращаем только после заполнения (см. начало функции)
|
|
||||||
self.models_table.setSortingEnabled(True)
|
|
||||||
|
|
||||||
|
|
||||||
def add_company(self):
|
def add_company(self):
|
||||||
"""Добавление новой компании"""
|
"""Добавление новой компании"""
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,9 +1,8 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
from psycopg2.extras import execute_values
|
||||||
import re
|
import re
|
||||||
from typing import Optional, Dict
|
from typing import Optional, Dict, Tuple
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
|
|
@ -246,13 +245,12 @@ class MobileDataImporter:
|
||||||
# Использование скрипта
|
# Использование скрипта
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Конфигурация подключения к БД
|
# Конфигурация подключения к БД
|
||||||
# Параметры подключения — из переменных окружения DB_* (по умолчанию — локальная разработка)
|
|
||||||
db_config = {
|
db_config = {
|
||||||
'host': os.environ.get('DB_HOST', 'localhost'),
|
'host': 'localhost',
|
||||||
'port': int(os.environ.get('DB_PORT', '5432')),
|
'port': 5432,
|
||||||
'database': os.environ.get('DB_NAME', 'mobile_devices_db'),
|
'database': 'mobile_devices_db',
|
||||||
'user': os.environ.get('DB_USER', 'admin'),
|
'user': 'admin',
|
||||||
'password': os.environ.get('DB_PASSWORD', 'password'),
|
'password': 'password' # Замените на ваш пароль
|
||||||
}
|
}
|
||||||
|
|
||||||
# Создаем импортер и выполняем импорт
|
# Создаем импортер и выполняем импорт
|
||||||
|
|
@ -260,9 +258,6 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
try:
|
try:
|
||||||
importer.connect()
|
importer.connect()
|
||||||
# Датасет лежит в корне репозитория; другой файл можно передать первым аргументом
|
importer.import_data('Mobiles Dataset 2025.csv') # Укажите путь к вашему файлу
|
||||||
csv_path = sys.argv[1] if len(sys.argv) > 1 else os.path.join(
|
|
||||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'Mobiles Dataset 2025.csv')
|
|
||||||
importer.import_data(csv_path)
|
|
||||||
finally:
|
finally:
|
||||||
importer.disconnect()
|
importer.disconnect()
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
"QUERY PLAN"
|
"QUERY PLAN"
|
||||||
"Sort (cost=44.76..44.84 rows=31 width=245) (actual time=0.216..0.217 rows=18 loops=1)"
|
"Sort (cost=44.76..44.84 rows=31 width=245) (actual time=0.216..0.217 rows=18 loops=1)"
|
||||||
" Sort Key: c.company_name, m.model_name"
|
" Sort Key: c.company_name, m.model_name"
|
||||||
" Sort Method: quicksort Memory: 25kB"
|
" Sort Method: quicksort Memory: 25kB"
|
||||||
" -> Hash Join (cost=17.20..43.99 rows=31 width=245) (actual time=0.055..0.180 rows=18 loops=1)"
|
" -> Hash Join (cost=17.20..43.99 rows=31 width=245) (actual time=0.055..0.180 rows=18 loops=1)"
|
||||||
" Hash Cond: (m.company_id = c.company_id)"
|
" Hash Cond: (m.company_id = c.company_id)"
|
||||||
" -> Seq Scan on models m (cost=0.00..26.71 rows=31 width=31) (actual time=0.038..0.159 rows=18 loops=1)"
|
" -> Seq Scan on models m (cost=0.00..26.71 rows=31 width=31) (actual time=0.038..0.159 rows=18 loops=1)"
|
||||||
" Filter: (((ram)::text ~~ '%8GB%'::text) AND ((battery_capacity)::text ~~ '%5000%'::text))"
|
" Filter: (((ram)::text ~~ '%8GB%'::text) AND ((battery_capacity)::text ~~ '%5000%'::text))"
|
||||||
" Rows Removed by Filter: 896"
|
" Rows Removed by Filter: 896"
|
||||||
" -> Hash (cost=13.20..13.20 rows=320 width=222) (actual time=0.010..0.010 rows=19 loops=1)"
|
" -> Hash (cost=13.20..13.20 rows=320 width=222) (actual time=0.010..0.010 rows=19 loops=1)"
|
||||||
" Buckets: 1024 Batches: 1 Memory Usage: 9kB"
|
" Buckets: 1024 Batches: 1 Memory Usage: 9kB"
|
||||||
" -> Seq Scan on companies c (cost=0.00..13.20 rows=320 width=222) (actual time=0.004..0.005 rows=19 loops=1)"
|
" -> Seq Scan on companies c (cost=0.00..13.20 rows=320 width=222) (actual time=0.004..0.005 rows=19 loops=1)"
|
||||||
"Planning Time: 0.155 ms"
|
"Planning Time: 0.155 ms"
|
||||||
"Execution Time: 0.234 ms"
|
"Execution Time: 0.234 ms"
|
||||||
|
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"schemaname","n_tup_ins","n_tup_upd","n_tup_del","seq_scan","seq_tup_read","idx_scan","idx_tup_fetch"
|
"schemaname","n_tup_ins","n_tup_upd","n_tup_del","seq_scan","seq_tup_read","idx_scan","idx_tup_fetch"
|
||||||
"public","19","0","0","33","589","934","915"
|
"public","19","0","0","33","589","934","915"
|
||||||
"public","914","0","0","32","27420","5520","4702"
|
"public","914","0","0","32","27420","5520","4702"
|
||||||
"public","4569","0","0","26","109656","4750","565"
|
"public","4569","0","0","26","109656","4750","565"
|
||||||
"public","5","0","0","9","30","4594","4594"
|
"public","5","0","0","9","30","4594","4594"
|
||||||
"public","217","0","0","8","1302","1141","924"
|
"public","217","0","0","8","1302","1141","924"
|
||||||
|
|
|
||||||
|
Loading…
Add table
Reference in a new issue