Skip to content

Модель данных

Манцева Татьяна edited this page Nov 5, 2024 · 7 revisions

Далее используемые обозначения для расчета среднего объёма

  1. $N_{players} = 15$ - количество игроков в одной команде без организатора игры
  2. $L_{name} = 20$
  3. $L_{address} = 60$
  4. $L_{pass} = 20$
  5. $L_{email}= 40$
  6. $L_{wishlist} = 200$
  7. $L_{stoplist} = 100$
  8. $N_{games}$ - количество команд, переменная

Нереляционная модель

Графическое представление

Подробное описание и занимаемый объём данных

БД содержит 3 коллекции - "Game", "User", "Event":

  • Коллекция "Game"

    • id - идентификатор каждой команды/игры в Тайного Санту. 8 байт
    • lowest_price - нижняя граница стоимости подарка. 8 байт
    • highest_price - верхняя граница стоимости подарка. 8 байт
    • form_deadline - срок, к которому нужно заполнить анкету. 8 байт
    • purchase_deadline - срок, к которому нужно купить подарок. 8 байт
    • send_deadline - срок, к которому нужно отправить подарок. 8 байт
    • created_at - дата создания. 8 байт
    • updated_at - дата обновления. 8 байт
    • users - игроки данной команды, в том числе и организатор. $N_{players}*V_{user} + V_{host} + N_{players}*8 = 7480 +$
    • events - события, относящиеся к этой игре (заполнение анкеты, покупка подарка и т.д.). Верхняя граница(произошли все события для каждого игрока): $5N_{players}*V_{event}= 1300$ байт

    $V_{game} = 8844$ байт

  • Коллекция "User"

    • id - пользователь. 8 байт
    • password - пароль. $L_{pass} * 1 = 20$ байт
    • email - электронная почта. $L_{email} * 1 = 20$ байт
    • is_host - является ли организатором. 1 байт
    • name - имя. $L_{name} * 1 = 20$ байт
    • address - адрес. $L_{address} * 1 = 60$ байт
    • index - индекс. 4 байта
    • phone - телефон. 8 байт
    • status - статус игрока(ничего не сделал/ заполнил анкету/ купил подарок/ отправил его). 4 байта (none, form, paid, sent)
    • delivery_type - способ доставки подарка. 6 байт (mail, other, ticket, cert, none)
    • wishlist - желания игрока. $L_{wishlist} * 1 = 200$ байт
    • stoplist - "стоплист". $L_{stoplist} * 1 = 100$ байт
    • recipient - идентификатор получателя. 8 байт
    • santa - идентификатор того, кто будет "Тайным Сантой" данного игрока. 8 байт
    • got_gift - получил ли пользователь подарок. 1 байт
    • wrong_gift - купил ли игрок подарок, пересекающийся со стоплистом. 1 байт
    • created_at - дата создания. 8 байт
    • updated_at - дата обновления. 8 байт

    $V_{user} = 485$ байта

    Частный случай пользователя организатора - $V_{host} = V_{id} + V_{pass} + V_{email} + V_{name} + V_{ishost} + V_{createdat} + V_{updatedat} = 85$ байт

  • Коллекция "Event"

    • player_id - идентификатор игрока, совершившего определенноё действие 8 байт
    • type - тип события 4 байта
    • date - дата 8 байт

    $V_{event} = 20$ байт

$V(N_{games}) = 8879 *N_{games}$ - занимаемый объём.

Избыточность данных

Дублирующиеся поля - player_id в коллекции Event, recipient, santa в коллекции User - идентификаторы участвующих игроков.
Также дублируется поле player_id в коллекции Game
Объём без дублирования информации - 7964 Избыточность модели = $\frac{8844}{7964} = 1.11$

Направление роста модели при увеличении количества объектов каждой сущности

  • Новая запись об игре влечет за собой новую запись о пользователе-организаторе
  • Каждая новая запись об игроке - потенциальные новые записи в коллекции Event
  • Новые документы в таблице Event ни на что не влияют

Примеры данных

  1. Game
{'_id': ObjectId('6722d278ee5e086effffc8b0'),
 'id': 1,
 'lowest_price': 1000,
 'highest_price': 2000,
 'form_deadline': datetime.datetime(2024, 11, 18, 0, 0),
 'purchase_deadline': datetime.datetime(2024, 12, 11, 0, 0),
 'send_deadline': datetime.datetime(2024, 12, 21, 0, 0),
 'users': [1, 2],
 'events': [
            {
             'player_id': 2,
             'type': 2,
             'date': datetime.datetime(2024, 11, 21, 0, 0)
            }
 ],
 'created_at': datetime.datetime(2024, 11, 11, 0, 0),
 'updated_at': datetime.datetime(2024, 11, 12, 0, 0)
}
  1. User
   {
     '_id': ObjectId('6722d278ee5e086ddmeff8b0'),
     'id': 1, 
     'name': 'test_user1',
     'email': '[email protected]',
     'password': 'aafewfwefg',
     'is_host': True,
     'created_at': datetime.datetime(2024, 11, 11, 0, 0),
     'updated_at': datetime.datetime(2024, 11, 12, 0, 0)
   },
   {
     '_id': ObjectId('6722d278ee5e086effffuyuggb0'),
     'id': 2, 
     'name': 'test_user2',
     'email': '[email protected]',
     'password': 'hnhytresfsd',
     'is_host': False,
     'address': 'nevsky str. 17',
     'index': 111111,
     'phone': 89999999999,
     'status' : 1,
     'delivery_type': 1,
     'wishlist': 'flowers',
     'stoplist':  'candles',
     'recipient' : 2 ,
     'santa': 3,
     'got_gift': False,
     'created_at': datetime.datetime(2024, 11, 11, 0, 0),
     'updated_at': datetime.datetime(2024, 11, 12, 0, 0),
     'wrong_gift': False,
   }
  1. Event
{
 'player_id': 2,
 'type': 2,
 'date': datetime.datetime(2024, 11, 21, 0, 0)
}

Примеры запросов

  1. Запрос на отображение списка желаний пользователя по id
users.find_one({'id':'1'}).get('wishlist')

  1. Запрос на обновление анкеты
users.update_one({'id':1}, {
       '$set':
           {
           'wishlist':"flowers",
           'stoplist':"candles",
           'phone': 8999999999999,
           'index': 111111,
           'address':"nevsky str.1"
       }
   })

  1. Запрос на отображение участников конкретной игры
 for player_id in games.find_one({'id':1}).get('users'):
   print(users.find_one({'id': player_id }))

  1. Запрос на отображение участников, чью покупку админ посчитал пересекающейся со стоплистом
game_column = db["games"]
player_column = db["users"]
wrong = []
for game in game_column.find():
  for player in player_column.find({ "id": { "$in": game["players"]}}):
    if player["wrong_gift"]:
      wrong.append(player)
  1. Запрос на отображение всех игр, где есть участники, просрочившие дедлайн покупки подарка
game_column = db["games"]
player_column = db["users"]
expired = []
current_date = dateutil.utils.today()
enum = ["none", "form", "paid", "sent"]
for game in game_column.find():
  if game["form_deadline"] < current_date:
    for player in player_column.find({ "id": { "$in": game["players"]}}):
      if not player["is_host"] and enum.index(player["status"]) < 2:
        expired.append(game)
        break
  1. Запрос на отображение участников конкретной игры, ещё не купивших подарок
 game = db["games"].find_one({ "id": { "$in": [game_id]}})
 player_column = db["users"]
 late = []
 enum = ["none", "form", "paid", "sent"]
 for player in player_column.find({ "id": { "$in": game["players"]}}):
   if not player["is_host"] and enum.index(player["status"]) < 2:
     late.append(player)

Реляционная модель

Графическое представление

Подробное описание и занимаемый объём данных

БД содержит 4 отношения - "Game", "Host", "Player", "Event":

  • Отношение "Game"

    • id - идентификатор каждой команды/игры в Тайного Санту. Ключ 8 байт
    • lowest_price - нижняя граница стоимости подарка 8 байт
    • highest_price - верхняя граница стоимости подарка 8 байт
    • form_deadline - срок, к которому нужно заполнить анкету 8 байт
    • purchase_deadline - срок, к которому нужно купить подарок 8 байт
    • send_deadline - срок, к которому нужно отправить подарок 8 байт
    • created_at - дата создания. 8 байт
    • updated_at - дата обновления. 8 байт

    $V_{game} = 64$ байт

  • Отношение "Host"

    • id - идентификатор организатора. Ключ 8 байт
    • password - пароль $L_{pass} * 1 = 20$ байт
    • email - электронная почта $L_{email} * 1 = 40$ байт
    • game_id - идентификатор игры, в которой данный пользователь является организатором 8 байт
    • name - имя $L_{name} * 1 = 20$ байт
    • created_at - дата создания. 8 байт
    • updated_at - дата обновления. 8 байт

    $V_{host} = 112$ байт

  • Отношение "Player"

    • id - идентификатор игрока. Ключ 8 байт
    • password - пароль $L_{pass} * 1 = 20 $ байт
    • email - электронная почта $L_{email} * 1 = 40$ байт
    • game_id - идентификатор команды, в которой находится пользователь 8 байт
    • name - имя $L_{name} * 1 = 20$ байт
    • wishlist - желания игрока $L_{wishlist} * 1 = 200$ байт
    • stoplist - "стоплист" $L_{stoplist} * 1 = 100$ байт
    • address - адрес $L_{address} * 1 = 60$ байт
    • index - индекс 4 байта
    • phone - телефон 8 байт
    • got_gift - получил ли пользователь подарок 1 байт
    • status - статус игрока(ничего не сделал/ заполнил анкету/ купил подарок/ отправил его) 1 байт
    • delivery_type - способ доставки подарка 1 байт
    • recipient - идентификатор получателя 8 байт
    • santa - идентификатор игрока, который будет "Тайным Сантой"данного игрока 8 байт
    • wrong_gift - купил ли игрок подарок, пересекающийся со стоплистом 1 байт
    • created_at - дата создания. 8 байт
    • updated_at - дата обновления. 8 байт

$V_{player} = 504$ байт

  • Отношение "Event"

    • game_id - идентификатор игры, к которой относится данное событие. 8 байт
    • player_id - идентификатор игрока, совершившего определенноё действие. 8 байт
    • type - тип события. game_id + player_id + type - ключ данной таблицы 4 байт
    • date - дата 8 байт

    $V_{event} = 28$ байт

$V(N_{games}) = 48 * N_{games} + 479 * N_{players} * N_{games} + 96 * N_{games} + * N_{players} * N_{games} * 5 = N_{games} * (64 + 504* 15 + 112 + 15 * 5 * 28) = N_{games} * 9556$ байт - занимаемый объём.

Избыточность данных

Дублирующиеся поля:

  1. player_id в отношении Event, recipient, santa в отношении Player - идентификаторы участвующих игроков.
  2. game_id в отношении Event, game_id в отношениях Player, Host - идентификаторы игры.
    Объём без дублирования информации - 8148 байт.
    Избыточность модели = $\frac{9556}{8148} = 1.17 $

Направление роста модели при увеличении количества объектов каждой сущности

  • При создании записи для команды будет создаваться запись для организатора игры
  • Аналогично ввод нового игрока приведёт к новым возможным событиям
  • Запись событий не приведет к росту других записей

Примеры данных

  1. Отношение Game
id lowest_price highest_price form_deadline purchase_deadline send_deadline created_at updated_at
1 1000 2000 18.11.2024 30.11.2024 12.12.2024 2024-11-08 23:59:59 2024-11-09 23:59:59
  1. Отношение Host
id password email game_id name created_at updated_at
1 fiuuhfie [email protected] 1 Ivan 2024-11-08 23:59:59 2024-11-09 23:59:59
  1. Отношение Player
id password email game_id name wishlist stoplist address index phone got_gift status delivery_type recipient santa wrong_gift created_at updated_at
1 fiuuhfe [email protected] 1 Ivan flowers candles Nevsky str. 17 1111 89999999999 false form none 2 3 false 2024-11-08 23:59:59 2024-11-09 23:59:59
2 uuhfe [email protected] 1 Katya books candies Professora Popova str. 16 1111 89129999999 false none none 3 1 false 2024-11-08 23:59:59 2024-11-09 23:59:59
3 udlfwefwefwhfe [email protected] 1 Mary perfume and sweets stationery Nevsky str. 16 1111 89999999999 true none none 1 3 false 2024-11-08 23:59:59 2024-11-09 23:59:59
  1. Отношение Event
game_id player_id type date
1 1 form 17.11.2024
1 3 form 10.11.2024
1 3 purchase 14.11.2024

G. Примеры запросов

  1. Запрос на отображение списка желаний пользователя по id
SELECT wishlist
FROM players
WHERE id=1

  1. Запрос на обновление анкеты
UPDATE users
SET wishlist = "flowers", stoplist = "sweets",phone=8999999999999, index=111111, address="nevsky str.1"
WHERE id = 1
  1. Запрос на отображение участников конкретной игры
SELECT *
FROM players
WHERE game_id=1 
  1. Запрос на отображение участников, чью покупку админ посчитал пересекающейся со стоплистом
SELECT player_id FROM player
WHERE wrong_gift = TRUE;
  1. Запрос на отображение всех игр, где есть участники, просрочившие дедлайн покупки подарка
SELECT game.game_id   
FROM game
INNER JOIN player
ON game.game_id = player.game_id 
WHERE status < 'bought' AND purchase_deadline < NOW()
GROUP BY game.game_id;
  1. Запрос на отображение участников конкретной игры, ещё не купивших подарок
FROM player
INNER JOIN game
ON game.game_id = player.game_id
WHERE game.game_id = 2 AND status < 'bought' AND purchase_deadline < NOW()
GROUP BY player.player_id;

Сравнение моделей

Занимаемый объём

Объём, зависящий от количества зарегистрированных команд в нереляционной БД немного меньше, чем в реляционной - $8844 * N_{games}$ и $9556 * N_{games}$

Избыточность

1.11 в нереляционной и 1.17 в реляционной

Количество запрсоов по юзкейсам

Юзкейс NoSQL SQL
1 Количество запросов:2, используемых коллекций:2 Количество запросов:3, используемых отношений:3
2 Количество запросов:1, используемых коллекций:1 Количество запросов:2, используемых отношений:2
3 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1
4 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1
6 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1
7 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1
8 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1
9 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1
11 Количество запросов:3, используемых коллекций:3 Количество запросов:4, используемых отношений:4
12 Количество запросов:3, используемых коллекций:3 Количество запросов:4, используемых отношений:4
14 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1
15 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1
16 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1
17 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1
18 Количество запросов:1, используемых коллекций:1 Количество запросов:1, используемых отношений:1

Вывод

Благодаря меньшему объёму, количеству коллекций, чуть меньшему количеству запросов и примерно той же избыточности, производительности, количеству запросов, в данном проекте немного предпочтительнее использовать нереляционную БД. Но, тем не менее, писать запросы со сложной фильтрацией намного легче именно в реляционной БД.