📦 Репозиторий занятия 84

Урок 84. MongoDB и Python. Модули

Как работать с репозиторием

Каждая карточка говорит, о чём файл, что он выводит и что в нём искать. Код виден прямо здесь: его можно скопировать одной кнопкой, скачать файл или открыть его целиком.

Маршрут изучения

  1. Прочитайте описание: по нему уже понятно, о чём файл и что он выведет.
  2. Предскажите вывод: сравните своё предположение со строкой «Что выводит».
  3. Запустите: скачайте файл или скопируйте код кнопкой и выполните его у себя.
  4. Измените: поменяйте одно условие или значение и объясните новый результат.

Файлы: рекомендуемый порядок

1
MarkdownРазбор концепции69 строк

SQL vs NoSQL: плюсы, минусы и когда что выбирать

14__MongoDB_introduction/theory_01__SQL_vs_NoSQL.md

Параллельное сравнение реляционных (RDBMS/SQL) и нереляционных (NoSQL) баз по четырём пунктам с каждой стороны — структурированность, масштабируемость, целостность данных, производительность. Завершается практической рекомендацией: RDBMS для финансовых/ERP-систем с чёткой структурой, NoSQL — для соцсетей, IoT и проектов с непредсказуемой структурой данных и большим объёмом.

  • RDBMS: плюсы — структурированность, сложные запросы, целостность, ACID-транзакции
  • RDBMS: минусы — вертикальное масштабирование, жёсткая схема, деградация на больших объёмах
  • NoSQL: плюсы — гибкая схема, горизонтальное масштабирование, разные модели данных (документы/ключ-значение/графы)
  • NoSQL: минусы — нет JOIN, только eventual consistency, нет единого языка запросов
  • Рекомендация выбора: RDBMS — финансы/ERP; NoSQL — соцсети, IoT, стартапы
Показать начало файла (69 строк всего)
Реляционные и нереляционные базы данных имеют свои особенности, плюсы и минусы, которые определяют их применение в различных проектах.

## Реляционные базы данных (RDBMS или SQL)

**RDBMS (Relational Database Management System)**  
**SQL (Structured Query Language)**

### Плюсы:

- Структурированность данных: 
  - Данные организованы в таблицы с четко определенными схемами. Это упрощает управление данными и обеспечивает целостность.
- Поддержка сложных запросов: 
  - Использование SQL позволяет выполнять сложные запросы и объединения данных, что делает их удобными для аналитики и сложных транзакций.
- Целостность данных: 
  - За счет использования связей между таблицами (например, первичных и внешних ключей) поддерживается целостность данных.
- Надежность транзакций (заложенная в самой структуре RDBMS): 
  - Обеспечивается атомарность, согласованность, изоляция и долговечность транзакций, что важно, например, для систем.

### Минусы:

- Масштабируемость: 
  - Горизонтальное масштабирование (добавление серверов для увеличения мощности) сложно реализовать. 
  - Реляционные БД обычно масштабируются вертикально (увеличение мощности одного сервера).
- Жесткость схемы: 
  - Требуется строгое определение структуры данных перед началом работы, что делает сложными изменения схемы при необходимости.
- Производительность при больших объемах данных: 
  - С увеличением объема данных и сложностью запросов производительность может снижаться.

## Нереляционные базы данных (NoSQL)

### Плюсы:

- Гибкость структуры данных:
  - Нет строгих схем, данные могут храниться в различных форматах (документы, ключ-значение, графы и т.д.), что упрощает разработку и внесение изменений.
- Горизонтальная масштабируемость: 
  - Легко масштабируются путем добавления новых узлов в кластер, что позволяет обрабатывать большие объемы данных.
- Высокая производительность: 
  - Оптимизированы для работы с большими объемами данных и могут быть более эффективны для определенных типов операций (например, быстрые записи и чтения).
- Поддержка разнообразных моделей данных: 
  - В зависимости от задачи можно выбрать:
    - документные базы (например, MongoDB), 
    - базы данных ключ-значение (например, Redis), 
    - графовые базы (например, Neo4j) 
    - и т.д.

### Минусы:

- Отсутствие поддержки сложных запросов: 
  - Многие NoSQL базы данных не поддерживают сложные объединения (JOIN) и операции, что может усложнять работу с данными.
- Ограниченная поддержка транзакций (ACID): 
  - Многие NoSQL базы обеспечивают лишь базовую согласованность (eventual consistency) вместо полной транзакционной целостности.
- Менее развитые стандарты: 
  - Нет единого языка запросов, как SQL, что может усложнять миграцию между разными NoSQL системами и требует обучения новым инструментам.
- Меньше инструментов для анализа: 
  - Инструменты для анализа и управления данными могут быть менее развиты по сравнению с реляционными базами.

## Выбор между RDBMS и NoSQL:

- RDBMS хороши там, есть чёткая структура и необходимы надёжные транзакции:
  - финансовые приложения, 
…
Проверьте себя: Почему для финансового приложения обычно выбирают RDBMS, даже понимая, что она хуже масштабируется горизонтально?
2
MarkdownРазбор концепции69 строк

BSON: бинарный формат хранения данных MongoDB

14__MongoDB_introduction/theory_02__BSON.md

Объясняет BSON как двоичную версию JSON с дополнительными типами (32/64-битные целые, даты, бинарные данные), которых нет в текстовом JSON, и показывает упрощённое побайтовое представление документа {name, age, languages}. Вторая часть — систематический разбор преимуществ бинарных форматов вообще: компактность памяти и передачи, скорость парсинга, расширенная типизация, меньше ошибок разбора, — и честно указанный недостаток: BSON не человекочитаем без специальных инструментов.

  • Определение BSON и его отличие от текстового JSON
  • Пример: JSON {name, age, languages} и его упрощённое BSON-представление в байтах
  • Дополнительные типы BSON: 32/64-бит числа, Date, Boolean, Null
  • 4 преимущества бинарных форматов: память, скорость, типизация, устойчивость к ошибкам разбора
  • Недостаток: machine-readable, а не human-readable без инструментов
Показать начало файла (69 строк всего)
Бинарное представление JSON, или BSON (Binary JSON), произносится как "би-эс-оу-эн" или "би-эс-он", — это формат, разработанный для хранения и передачи данных, который представляет собой двоичную, более компактную версию JSON. BSON сохраняет структуру данных JSON, но оптимизирован для быстрого чтения и записи, а также для поддержки дополнительных типов данных, которых нет в стандартном JSON.
Основные особенности BSON:

Компактность: хотя BSON обычно немного больше по размеру, чем простой текст JSON (из-за дополнительных метаданных), он всё же достаточно эффективен для передачи данных и хранения.
Быстрота обработки: поскольку данные закодированы в двоичном формате, их парсинг и обработка могут быть быстрее по сравнению с текстовым JSON.
Поддержка дополнительных типов данных: помимо стандартных типов JSON (строки, числа, массивы, объекты), BSON поддерживает дополнительные типы данных, такие как:
    32-битные и 64-битные целые числа.
    Даты.
    Булевы значения.
    Null и другие.

Преимущества BSON:

Эффективность: для операций, связанных с хранением и передачей данных, поскольку данные в двоичном формате проще и быстрее обрабатывать.
Расширяемость: позволяет хранить данные с более сложной структурой и типами, чем JSON.

Пример JSON и его представления в BSON:

JSON:
```
{
  "name": "Alice",
  "age": 30,
  "languages": ["English", "Spanish"]
}
```

BSON (в двоичном виде, представлено упрощённо):
```
\x16\x00\x00\x00           // Длина документа
\x02 name\x00\x06\x00\x00\x00Alice\x00  // Строка "name": "Alice"
\x10 age\x00\x1E\x00\x00\x00            // Целое число "age": 30
\x04 languages\x00\x19\x00\x00\x00      // Массив "languages"
\x02 0\x00\x08\x00\x00\x00English\x00   // Первый элемент массива
\x02 1\x00\x08\x00\x00\x00Spanish\x00   // Второй элемент массива
\x00                      // Завершение документа
```

Бинарное хранение данных имеет несколько преимуществ по сравнению с текстовым. Основные преимущества включают:
1. Эффективность использования памяти

- Компактность: Бинарные форматы, такие как BSON, позволяют сохранять данные более компактно за счет использования метаданных и структур данных с фиксированной длиной. Это снижает объем занимаемой памяти по сравнению с текстовыми форматами, такими как JSON или XML, где данные представлены в человекочитаемой строковой форме и требуют больше места.
- Меньший объем передачи данных: В связи с компактностью бинарных форматов уменьшается объем данных, передаваемых по сети, что улучшает производительность.

2. Быстродействие

- Быстрая обработка: Бинарные форматы обычно быстрее читаются и записываются, поскольку они уже находятся в формате, близком к тому, как данные хранятся в памяти. Это уменьшает необходимость преобразования данных в текст и обратно, что требуется при работе с текстовыми форматами.
- Парсинг и десериализация: Бинарные данные легче распарсить, так как они не требуют сложного разбора строк, как текстовые форматы. Это особенно важно для больших объемов данных.

3. Поддержка дополнительных типов данных

- Расширенная типизация: Бинарные форматы могут поддерживать больше типов данных, чем текстовые. Например, BSON, используемый в MongoDB, поддерживает типы данных, такие как даты, двоичные данные, целые числа разной разрядности, булевы значения и даже вложенные документы.
- Удобство хранения: Например, в JSON все числа представлены в виде строки, и нужно вручную контролировать целые и вещественные числа. Бинарные форматы хранят их более точно и оптимально.

4. Безопасность

- Mеньше шансов на ошибки при разборе: Текстовые форматы могут быть подвержены ошибкам, связанным с неверной кодировкой, неправильными символами или синтаксическими ошибками, которые могут повредить данные или нарушить их целостность. Бинарные форматы, напротив, жестко структурированы и имеют четкий набор правил.

Примеры использования:

…
Проверьте себя: Почему BSON-документ может занимать больше места, чем эквивалентный JSON, хотя в целом бинарные форматы называют более компактными?
3
MarkdownРазбор концепции161 строк

Восемь типов NoSQL баз данных с примерами СУБД

14__MongoDB_introduction/theory_03__NoSQL_dbs.md

Каталог типов NoSQL: документо-ориентированные (MongoDB), ключ-значение (Redis), графовые (Neo4j), сетевые (устаревшие), колонно-ориентированные (Cassandra, ClickHouse), временных рядов (InfluxDB), объектно-ориентированные и гибридные (ArangoDB) — для каждого типа описание, сценарии использования, примеры СУБД, преимущества и короткий пример записи данных. Завершается кратким гидом «как выбрать тип по задаче».

  • Документо-ориентированные (MongoDB, Couchbase) — гибкая структура
  • Ключ-значение (Redis, DynamoDB) — кэш и сессии
  • Графовые (Neo4j, ArangoDB) — соцсети и рекомендательные системы
  • Колонно-ориентированные (Cassandra, ClickHouse) — аналитика
  • Time-Series (InfluxDB, TimescaleDB) — IoT и мониторинг
  • Объектно-ориентированные и гибридные базы
  • Итоговый гид выбора типа NoSQL по задаче
Показать начало файла (161 строк всего)
Нереляционные базы данных (NoSQL) включают несколько типов, каждый из которых предназначен для специфических задач и сценариев. Вот основные типы и их особенности:

## 1. Документо-ориентированные базы данных

- Описание: 
    - Данные хранятся в виде документов (например, JSON, BSON, XML). Каждый документ представляет собой запись с гибкой структурой.
- Примеры использования:
    - Сайты с динамическим контентом.
    - Хранилище данных для приложений, где структура может меняться.
- Примеры СУБД:
    - MongoDB
    - Couchbase
    - Amazon DocumentDB
- Преимущества:
    - Гибкость структуры данных.
    - Хорошо подходят для сложных вложенных данных.
    - Простое масштабирование.

*Пример записи:*
```
    {
      "name": "John Doe",
      "age": 30,
      "skills": ["JavaScript", "Python"]
    }
```

## 2. Ключ-значение (Key-Value)

- Описание:
    - Простые хранилища, где данные представляют собой пары "ключ-значение". Используются для быстрого доступа по ключу.
- Примеры использования:
    - Кэширование данных.
    - Хранилище сессий.
    - Простые конфигурационные данные.
- Примеры СУБД:
    - Redis
    - Amazon DynamoDB
- Преимущества:
    - Высокая скорость.
    - Простота структуры.
    - Масштабируемость.

*Пример записи:*
```
    "user:1001": { "name": "Alice", "age": 25 }
```

## 3. Графовые базы данных

- Описание: 
    - Ориентированы на работу с узлами (объектами) и рёбрами (связями). Используются для анализа сложных взаимосвязей.
- Примеры использования:
    - Социальные сети.
    - Рекомендательные системы.
    - Анализ сетей и связей.
- Примеры СУБД:
    - Neo4j
    - ArangoDB
    - Amazon Neptune
…
Проверьте себя: Почему для IoT-датчиков рекомендуют базу временных рядов (Time-Series), а не обычную документо-ориентированную MongoDB?
4
MarkdownРазбор концепции89 строк

Простые запросы в MongoDB Compass: аналоги WHERE, SELECT, ORDER BY

14__MongoDB_introduction/theory_04__basic_queries.md

Проводит явные параллели между MongoDB и SQL: фильтрация документов через JSON-условие в find() — аналог WHERE; стадия $project — аналог SELECT, с разбором шести сценариев (все поля, выбор полей, скрытие _id, переименование ключа через алиас, вывод вложенных JSON в отдельные строки через dot-notation, приведение _id к строке через $toString); $sort — аналог ORDER BY с направлением 1/-1.

  • Пустой { } в find() — аналог SELECT * / отсутствия WHERE
  • { age: { $gte: 18 } } — фильтрация, аналог WHERE
  • $project: { age: 1, name: 1 } — выбор полей, _id остаётся по умолчанию
  • $project: { ..., _id: 0 } — явное скрытие _id
  • $project: { Возраст: "$age" } — переименование поля (алиас)
  • $project: { Имя: "$person.name" } — dot-notation для вложенных полей в кавычках с $
  • $project: { _id: { $toString: '$_id' } } — приведение _id к строке
  • $sort: { age: -1, name: 1 } — аналог ORDER BY с направлением
Показать начало файла (89 строк всего)
# Простые запросы в MongoDB Compass

## Аналог WHERE (фильтрация документов)

В случе пустого JSON `{ }` будут выведены все документы коллекции.

Для фильтрации необходимо в JSON-объекте `{ }` указать параметры фильтрации.  

Например, `{ age: { $gte: 18 } }` оставить только те документы, где возраст >= 18.

(смотрите [theory_07__logical_operations.md](theory_07__logical_operations.md))

##  Аналог SELECT

Поле `project` (или стадия `$project` в агрегации) позволяет выбирать для вывода нужные ключи коллекции.



### 1. Вывести все ключи (поля)

Пустой JSON `{ }` выводит документы полностью и является аналогом `SELECT *` в SQL
```
$project: { }
```

### 2. Указать поля (ключи) для вывода
```
$project: { age: 1, name: 1 }
```
В каждом документе останется только `age`, `name` и  `_id`.
(`_id` ВСЕГДА выводится по умолчанию, если ЯВНО не указано иное)
 

### 3. Запрет вывода `_id`

В этом случае надо ЯВНО указать `_id: 0`
```
$project: { age: 1, name: 1, _id: 0 }
```
В каждом документе останется только `age` и `name`

### 4. Переименование ключа (поля) (Аналог alias)
```
$project: { Возраст: "$age" }
```
Теперь значения ключа (поля) `age` будут выведены под именем `Возраст`

### 5. Вывод JSON вложений в одну строку

JSON объекты, указанные в `$project` сохраняют свой формат при выводе.
Например, 
```
person: {
    age: 25,
    name: "John"
}
```
будет выведен как `person` и ссылка, нажав на которую, откроются все данные (`age` и `name`).
Чтобы вывести возраст и имя в разных строках, необходимо 
 - указать полный путь к нужному полю через dot-notation;
…
Проверьте себя: Почему для вывода вложенного поля person.age в отдельную строку нужно указывать путь в кавычках со знаком $ ("$person.age"), а имя поля слева — без dot-notation?
5
MarkdownРазбор концепции117 строк

Агрегация в MongoDB: pipeline, $group, $sortByCount

14__MongoDB_introduction/theory_05__aggregation.md

Вводит ключевую идею агрегации — конвейер (pipeline) стадий, где выход одной стадии становится входом следующей, и показывает, что любой обычный find()-запрос можно переписать через aggregate() (пример с find+sort+skip+limit против эквивалентного aggregate с $match/$project/$sort/$skip/$limit). Разбирает стадию $group по синтаксису (_id как поле группировки или null для всей коллекции, аккумуляторы $sum/$avg/$min/$max/$first/$last/$push/$addToSet) и её сокращённый вариант $sortByCount, который одновременно группирует, считает и сортирует, но не годится для min/max.

  • Pipeline — конвейер стадий, выход одной = вход следующей
  • find().sort().skip().limit() — то же самое через aggregate([$match, $project, $sort, $skip, $limit])
  • $group: _id (поле группировки или null) + аккумуляторы
  • Таблица аккумуляторов: $sum, $avg, $min, $max, $first, $last, $push, $addToSet, $count
  • Группировка по department_id и по всей коллекции (_id: null)
  • $sortByCount — группировка + подсчёт + сортировка в одной стадии, не для min/max
Показать начало файла (117 строк всего)
# Агрегация в MongoDB 

## Как запустить агрегацию?
В MongoDB Compass - это вкладка `aggregation`;
В mongo shell или mongosh — используется метод `aggregation()`.

## Отличия от SQL

Агрегация в MongoDB несколько отличается от агрегации в SQL.

Главная причина - наличие pipeline (конвейера), который позволяет последовательно указать несколько операций,  
где результаты (выходные документы) предыдущей операции будут входными документами для последующей.

Таким образом, все БЕЗ ИСКЛЮЧЕНИЯ запросы MongoDB можно выразить через также через агрегацию.

**Задание для примера**:  
Выбрать документы, где возраст сотрудника больше 30 лет. 
Оставить только имя и зарплату, отсортировать по з/п по убыванию.
Пропустить первые 10 документов, и вывести следующие 10.

- Обычный запрос
```
db.getCollection('employees').find(
    { age: { $gt: 30 } }, 
    { name: 1, salary: 1, _id: 0 }
  )
  .sort({ salary: -1 })
  .skip(10)
  .limit(10);
```

Запрос через агрегацию:
```
db.employees.aggregate(
    [
        { "$match": { "age": { "$gt": 30 } } },
        { "$project": { "_id": 0, "name": 1, "salary": 1 } },
        { "$sort": { "salary": -1 } },
        { "$skip": 10 },
        { "$limit": 10 }
    ]
);
```

## Непосредственно группировка

Бывает двух видов:
- `$group` - похожа на `GROUP BY`
- `$sortByCount` - одновременно: 
  - группирует, 
  - сортирует по убыванию 
  - и подсчитывает кол-во документов в каждой группе
  - (те есть фактически это две стадии: `$group` и `$sort`)

### Стадия `$group` 
                    
#### Синтаксис:
```
{
  _id: expression,
…
Проверьте себя: Почему $sortByCount не подходит для вычисления, например, максимальной зарплаты по группам, хотя он тоже группирует данные?
6
PythonИсполняемый пример58 строк

Каркас для решения задач по MongoDB через pymongo (Задача 1 решена, Задача 2 — заготовка)

14__MongoDB_introduction/theory_06__run_query_to_mongodb_by_python.py

Учебный шаблон для практики: подключается к живому серверу MongoDB через MongoClient и учётные данные из local_settings (MONGODB_URL_READ), затем накапливает пары «условие задачи → результат агрегации» в параллельных списках task_statement и data, чтобы в конце вывести все решения одним циклом с ограничением TIMES=5 документов на задачу. В файле решена только первая задача (найти всех 40-летних из коллекции ich.US_Adult_Income через $match), вторая — пустая заготовка (result = '') для самостоятельного дозаполнения.

  • MongoClient(MONGODB_URL_READ) as client — подключение через контекстный менеджер
  • task_statement и data — параллельные списки: условие задачи и результат-курсор
  • Задача 1: client['ich']['US_Adult_Income'].aggregate([{'$match': {'age': 40}}])
  • Задача 2: result = '' — незаполненная заготовка для самостоятельного решения
  • Проверка len(data) != len(task_statement) — защита от рассинхронизации списков
  • Цикл вывода: docs = list(result); pprint(doc) для первых TIMES документов + Total: N docs

Что выводит: Не запускался: скрипту требуется живое подключение к серверу MongoDB (адрес в MONGODB_URL_READ из local_settings, которого нет в репозитории) — без него MongoClient(...) не подключится к реальным данным. Ожидаемый эффект при рабочем подключении: печать заголовка задачи 1, до 5 документов 40-летних из коллекции US_Adult_Income через pprint и строка "Total: N docs"; для задачи 2 result = '' — list('') даст пустой список документов, так как условие ещё не реализовано.

Начало файла
from pprint import pprint
from pymongo import MongoClient  # pip install pymongo

from local_settings import MONGODB_URL_READ

with MongoClient(MONGODB_URL_READ) as client:
    TIMES = 5  # число документов на печать по умолчанию
    task_statement = []  # список условий задач
    data = []  # список документов по каждому решению задачи

    """ ********************** Блок задач **************************** """

    # === Задача 1 (пример решения) ===
    task_statement.append(
Показать файл целиком (58 строк)
from pprint import pprint
from pymongo import MongoClient  # pip install pymongo

from local_settings import MONGODB_URL_READ

with MongoClient(MONGODB_URL_READ) as client:
    TIMES = 5  # число документов на печать по умолчанию
    task_statement = []  # список условий задач
    data = []  # список документов по каждому решению задачи

    """ ********************** Блок задач **************************** """

    # === Задача 1 (пример решения) ===
    task_statement.append(
        'Задача 1: Найти документы всех 40-летних'
    )

    # ----- в result подставляем решение из mongodb -----
    result = client['ich']['US_Adult_Income'].aggregate([
        {
            '$match': {
                'age': 40
            }
        }
    ])

    data.append(result)

    # ===== Задача 2 =====
    task_statement.append(
        'Условие задачи 2'
    )

    # ----- в result подставляем решение из mongodb -----
    result = ''

    data.append(result)



    """ *************** Блок вывода всех результатов на печать *************** """

    if len(data) != len(task_statement):
        raise IndexError("Ошибка!!! Кол-во заданий НЕ РАВНО кол-ву решений!!!")

    # Цикл по задачам
    for task_num, result in enumerate(data):
        print(50 * '=')
        print(task_statement[task_num])
        print()

        # Цикл по выводу документов решения
        docs = list(result)
        for idx, doc in enumerate(docs[:TIMES]):
            # print(idx, 50 * '-')
            pprint(doc)

        print(f"Total: {len(docs)} docs")
Проверьте себя: Что произойдёт при попытке вывести результаты задачи 2, если оставить result = '' как есть — сработает ли list(result) и что покажет цикл вывода?
Открыть файл →
7
MarkdownРазбор концепции180 строк

Логические и сравнительные операторы MongoDB: $and, $or, $not, $nor, $gt/$lt и другие

14__MongoDB_introduction/theory_07__logical_operations.md

Систематический разбор 13 операторов запроса: логические $and/$or/$not/$nor для комбинирования условий, операторы сравнения $eq/$ne/$gt/$lt/$gte/$lte, проверка вхождения $in/$nin и существования поля $exists. Каждый оператор сопровождён отдельным примером на условной коллекции с полями age/city/status, в конце — пример комбинированного запроса, объединяющего $and с $ne и $in одновременно.

  • $and — все условия одновременно, $or — хотя бы одно
  • $not — инверсия условия, $nor — не соответствует ни одному из списка
  • $eq, $ne, $gt, $lt, $gte, $lte — операторы сравнения
  • $in, $nin — вхождение/невхождение в список значений
  • $exists — проверка наличия поля в документе
  • Комбинированный запрос: $and + $ne + $in вместе
Показать начало файла (180 строк всего)
# Логические операции в MongoDB

В MongoDB для выполнения логических операций используются операторы, такие как `$and`, `$or`, `$not`, и `$nor`. Эти операторы позволяют комбинировать условия фильтрации для выполнения сложных запросов. Рассмотрим каждый из них с примерами.
## 1. $and — логическое И

Оператор `$and` позволяет объединить несколько условий, и документ должен удовлетворять всем этим условиям одновременно.

*Пример: Найдем все документы, где age больше 25 и city равен "New York".*
```
db.collection.find({
  $and: [
    { age: { $gt: 25 } },        // возраст больше 25
    { city: "New York" }          // город равен "New York"
  ]
});
```
## 2. $or — логическое ИЛИ

Оператор `$or` позволяет выполнить запрос, если хотя бы одно из условий выполняется.

*Пример: Найдем все документы, где age меньше 18 или city равен "Los Angeles".*

```
db.collection.find({
  $or: [
    { age: { $lt: 18 } },         // возраст меньше 18
    { city: "Los Angeles" }       // город равен "Los Angeles"
  ]
});
```

## 3. $not — логическое НЕ

Оператор `$not` инвертирует условие, то есть возвращает документы, которые не соответствуют указанному условию.

* Пример: Найдем все документы, где age не меньше 30.*

```
db.collection.find({
  age: { $not: { $lt: 30 } }     // возраст не меньше 30
});
```
## 4. $nor — логическое НЕ ИЛИ

Оператор `$nor` комбинирует несколько условий и возвращает документы, которые не соответствуют любому из условий. Это эквивалентно логическому отрицанию оператора $or.

Пример: Найдем все документы, где age не меньше 18 и city не равен "Los Angeles".*

```
db.collection.find({
  $nor: [
    { age: { $lt: 18 } },         // возраст не меньше 18
    { city: "Los Angeles" }       // город не равен "Los Angeles"
  ]
});
```

## 5. $eq — равно

Оператор `$eq` проверяет, равен ли элемент заданному значению.
…
Проверьте себя: Чем find({ age: { $not: { $lt: 30 } } }) отличается по смыслу от эквивалентного find({ age: { $gte: 30 } } ), если результат должен быть одинаковым?
8
MarkdownРазбор концепции219 строк

Арифметические операторы MongoDB для агрегации: $add, $multiply, $round и другие

14__MongoDB_introduction/theory_08__math_operations.md

Каталог из 12 арифметических операторов агрегации ($add, $subtract, $multiply, $divide, $mod, $exp, $ln, $sqrt, $abs, $ceil, $floor, $round), каждый с примером внутри $project. Отдельно отмечена практическая ловушка: $subtract над датами (stop_time - start_time) в MongoDB даёт результат в миллисекундах, как в JavaScript. Завершается примером комбинирования — вычисление итоговой цены с налогом через вложенные $add и $multiply.

  • $add, $subtract, $multiply, $divide — базовая арифметика
  • Предупреждение: $subtract над датами даёт разницу в миллисекундах
  • $mod — остаток от деления
  • $exp, $ln, $sqrt, $abs — математические функции
  • $ceil, $floor, $round — округление (round — до заданного числа знаков)
  • Комбинирование: total_price = $add: [price, {$multiply: [price, 0.1]}]
Показать начало файла (219 строк всего)
# Математические операции в MongoDB

В MongoDB для выполнения арифметических операций в запросах используется несколько операторов. Эти операторы применяются в агрегации, обычно в этапах типа `$project`, `$group`, `$addFields` и т. д. Вот основные арифметические операторы:

## 1. $add — сложение

Оператор `$add` выполняет сложение двух или более значений.

*Пример: Сложение значений полей price и tax*

```
db.collection.aggregate([
  {
    $project: {
      total: { $add: ["$price", "$tax"] }  // 
    }
  }
]);
```

## 2. $subtract — вычитание

Оператор `$subtract` выполняет вычитание одного значения из другого.

*Пример: Находим разницу между временем окончания и временем начала* 
*ВНИМАНИЕ: В JavaScript эта разница будет в миллисекундах!*

```
db.collection.aggregate([
  {
    $project: {
      difference: { $subtract: ["$stop_time", "$start_time"] } 
    }
  }
]);
```

## 3. $multiply — умножение

Оператор `$multiply` выполняет умножение двух или более значений.

*Пример: Умножение полей price и quantity*

```
db.collection.aggregate([
  {
    $project: {
      total_amount: { $multiply: ["$price", "$quantity"] }  
    }
  }
]);
```

## 4. $divide — деление

Оператор `$divide` выполняет деление одного значения на другое.


*Пример: Деление полей total_amount на quantity*

…
Проверьте себя: Почему результат $subtract над двумя полями типа Date нужно дополнительно делить на 1000 (и на 60, 60, 24...), чтобы получить разницу в секундах, минутах или днях?
9
MarkdownРазбор концепции332 строк

Строковые операторы MongoDB: $concat, $substr, $split, $regex и преобразования типов

14__MongoDB_introduction/theory_09__string_operations.md

Развёрнутый каталог из 17 строковых операторов агрегации: базовые $concat/$substr/$toUpper/$toLower/$trim/$split, поиск и замена ($indexOfBytes, $replaceOne, $replaceAll), длина строки в байтах vs в кодовых точках Unicode ($strLenBytes vs $strLenCP), $regex для поиска по шаблону и группа операторов преобразования типов ($toString, $toDouble, $toInt, $toDecimal) с явным предупреждением проверять формат строки регуляркой перед числовым преобразованием. Завершается примером комбинирования $toUpper внутри $concat.

  • $concat, $substr, $toUpper, $toLower, $trim — базовые операции над строкой
  • $split — строка в массив по разделителю, с примером результата
  • $indexOfBytes, $replaceOne, $replaceAll — поиск и замена подстроки
  • $strLenBytes vs $strLenCP — длина в байтах против кодовых точек Unicode
  • $regex — поиск по регулярному выражению внутри $match
  • $toString, $toDouble, $toInt, $toDecimal — преобразования строка ↔ число
  • Комбинирование: $concat с вложенным $toUpper для города
Показать начало файла (332 строк всего)
# Операции со строками в MongoDB


В MongoDB для работы со строками используются различные операторы в рамках агрегации и запросов. Эти операторы позволяют манипулировать строковыми данными, извлекать подстроки, преобразовывать регистры и т. д. Вот основные операторы и методы для работы с строками в MongoDB.

## 1. $concat — объединение строк

Оператор `$concat` используется для объединения двух или более строк в одну строку.

*Пример: Объединим имя и фамилию в одно поле full_name:*

```
db.collection.aggregate([
  {
    $project: {
      full_name: { $concat: ["$first_name", " ", "$last_name"] }  
    }
  }
]);
```

## 2. $substr — извлечение подстроки

Оператор `$substr` используется для извлечения части строки. Он принимает три аргумента: строку, начальную позицию и длину подстроки.

*Пример: Извлечем первые 5 символов из строки name:*

```
db.collection.aggregate([
  {
    $project: {
      short_name: { $substr: ["$name", 0, 5] }  // Извлечение первых 5 символов из name
    }
  }
]);
```

## 3. $toUpper — преобразование в верхний регистр

Оператор `$toUpper` преобразует строку в верхний регистр.

*Пример: Преобразуем строку name в верхний регистр:*

```
db.collection.aggregate([
  {
    $project: {
      upper_name: { $toUpper: "$name" }  // Преобразование name в верхний регистр
    }
  }
]);
```

## 4. $toLower — преобразование в нижний регистр

Оператор `$toLower` преобразует строку в нижний регистр.

*Пример: Преобразуем строку name в нижний регистр:*

```
…
Проверьте себя: Для строки с кириллицей вроде «Привет» — дадут ли $strLenBytes и $strLenCP одинаковый результат, и если нет, то почему?
10
MarkdownРазбор концепции43 строк

Первая нормальная форма (1NF): атомарность значений

15__MongoDB_arrays/SQL_normalization_laws/theory_01__1NF.md

Требование 1NF — каждая ячейка содержит одно неделимое значение, строки уникальны. Антипример со списком курсов через запятую в одной ячейке (Courses: 'Math, Science') нарушает 1NF; показано пошаговое приведение к 1NF — разложение многозначной ячейки на отдельные строки (по одному курсу на StudentID).

  • Определение 1NF: атомарные значения, уникальные строки
  • Пример таблицы, уже соответствующей 1NF
  • Антипример: Courses = 'Math, Science' — неатомарное значение
  • Приведение к 1NF: разложение на отдельные строки по курсу
Показать начало файла (43 строк всего)
Первая нормальная форма (1NF) — это базовый уровень нормализации базы данных, который требует, чтобы каждый атрибут (столбец) содержал только атомарные (неделимые) значения, и чтобы в таблице не было повторяющихся строк. Это означает, что каждая ячейка таблицы должна содержать только одно значение, и каждая запись (строка) должна быть уникальной.

Пример таблицы в 1NF:

| StudentID | Name       | Course   |
|-----------|------------|----------|
| 1         | John Doe   | Math     |
| 2         | Jane Smith | Science  |
| 3         | Bob Brown  | History  |

Объяснение:

    Таблица соответствует 1NF, так как каждый столбец содержит атомарные значения, и все строки уникальны.
    Course содержит только одно значение в каждой ячейке.

Антипример таблицы, не соответствующей 1NF:

| StudentID | Name       | Courses                  |
|-----------|------------|--------------------------|
| 1         | John Doe   | Math, Science            |
| 2         | Jane Smith | Science, History, Math   |
| 3         | Bob Brown  | History                  |

Объяснение:

    Таблица не соответствует 1NF, потому что в столбце Courses хранятся неатомарные значения (список курсов через запятую).
    Это нарушает требование 1NF, согласно которому каждый столбец должен содержать только одно значение в каждой ячейке.

Преобразование антипримера в 1NF:

Чтобы привести таблицу к 1NF, нужно разложить многозначные значения в отдельные строки:

| StudentID | Name       | Course     |
|-----------|------------|------------|
| 1         | John Doe   | Math       |
| 1         | John Doe   | Science    |
| 2         | Jane Smith | Science    |
| 2         | Jane Smith | History    |
| 2         | Jane Smith | Math       |
| 3         | Bob Brown  | History    |


Теперь таблица соответствует 1NF, так как все значения атомарны, и каждый столбец содержит только одно значение в каждой ячейке.
Проверьте себя: Почему хранение списка курсов через запятую в одной ячейке нарушает именно 1NF, а не более высокие нормальные формы?
11
MarkdownРазбор концепции51 строк

Вторая нормальная форма (2NF): устранение частичных зависимостей

15__MongoDB_arrays/SQL_normalization_laws/theory_02__2NF.md

Требование 2NF — 1NF плюс отсутствие частичных зависимостей от составного ключа. Антипример: таблица с ключом (StudentID, CourseID), где StudentName зависит только от StudentID, а CourseName — только от CourseID (частичные зависимости). Решение — разбиение на три таблицы: студенты, курсы, оценки (связующая таблица).

  • Определение 2NF: 1NF + нет частичных зависимостей от составного ключа
  • Антипример: StudentName и CourseName зависят каждый от своей части ключа
  • Разбиение на три таблицы: студенты, курсы, оценки
  • Итог: все неключевые атрибуты зависят от всего ключа
Показать начало файла (51 строк всего)
Вторая нормальная форма (2NF) требует выполнения условий 1NF, а также того, чтобы все неключевые атрибуты зависели от всего первичного ключа, а не от его части. 
Это означает, что таблица не должна иметь частичных зависимостей, где неключевой атрибут зависит только от части составного первичного ключа.

Антипример таблицы, не соответствующей 2NF:

| StudentID | CourseID | StudentName | CourseName  | Grade |
|-----------|----------|-------------|-------------|-------|
| 1         | 101      | John Doe    | Math        | A     |
| 1         | 102      | John Doe    | Science     | B     |
| 2         | 101      | Jane Smith  | Math        | A     |
| 2         | 103      | Jane Smith  | History     | C     |

Объяснение:

    В этой таблице первичный ключ — это комбинация StudentID и CourseID.
    Атрибуты StudentName и CourseName зависят только от StudentID и CourseID соответственно, а не от полной комбинации. Это нарушение 2NF.

Преобразование таблицы в 2NF:

Разделим таблицу на две отдельные таблицы, чтобы устранить частичные зависимости.
Таблица студентов:

| StudentID | StudentName |
|-----------|-------------|
| 1         | John Doe    |
| 2         | Jane Smith  |

Таблица курсов:

| CourseID | CourseName |
|----------|------------|
| 101      | Math       |
| 102      | Science    |
| 103      | History    |

Таблица оценок:

| StudentID | CourseID | Grade |
|-----------|----------|-------|
| 1         | 101      | A     |
| 1         | 102      | B     |
| 2         | 101      | A     |
| 2         | 103      | C     |

Объяснение:

    Таблица студентов содержит информацию о студентах и их идентификаторах.
    Таблица курсов содержит информацию о курсах и их идентификаторах.
    Таблица оценок связывает студентов с курсами и содержит их оценки. 
    Теперь все неключевые атрибуты зависят от всего первичного ключа, и таблицы соответствуют 2NF.
Проверьте себя: Почему частичная зависимость возможна только при составном первичном ключе — может ли она возникнуть в таблице с одним столбцом в ключе?
12
MarkdownРазбор концепции58 строк

Третья нормальная форма (3NF): устранение транзитивных зависимостей

15__MongoDB_arrays/SQL_normalization_laws/theory_03__3NF.md

Требование 3NF — 2NF плюс отсутствие транзитивных зависимостей (неключевой атрибут не должен зависеть от другого неключевого атрибута). Антипример: InstructorName зависит от CourseID, а не напрямую от первичного ключа (StudentID, CourseID) — транзитивная зависимость через CourseName. Решение — выделение отдельной таблицы преподавателей по CourseID.

  • Определение 3NF: 2NF + нет транзитивных зависимостей
  • Антипример: InstructorName зависит от CourseID, а не от полного ключа
  • Разбиение на четыре таблицы: студенты, курсы, преподаватели, оценки
  • Итог: каждый неключевой атрибут зависит только от первичного ключа своей таблицы
Показать начало файла (58 строк всего)
Третья нормальная форма (3NF) требует выполнения всех условий 2NF, а также того, чтобы не было транзитивных зависимостей, то есть каждый неключевой атрибут должен напрямую зависеть от первичного ключа, а не через другой неключевой атрибут.

Антипример таблицы, не соответствующей 3NF:

| StudentID | StudentName | CourseID | CourseName  | InstructorName |
|-----------|-------------|----------|-------------|----------------|
| 1         | John Doe    | 101      | Math        | Dr. Smith      |
| 1         | John Doe    | 102      | Science     | Dr. Johnson    |
| 2         | Jane Smith  | 101      | Math        | Dr. Smith      |
| 2         | Jane Smith  | 103      | History     | Dr. Brown      |

Объяснение:

    В этой таблице атрибут InstructorName зависит от CourseID, а не от StudentID, что приводит к транзитивной зависимости. 
    То есть, InstructorName зависит от CourseID, который, в свою очередь, зависит от первичного ключа (StudentID и CourseID).

Преобразование таблицы в 3NF:

Чтобы привести таблицу к 3NF, необходимо устранить транзитивные зависимости. Это можно сделать, разделив таблицу на три части:
Таблица студентов:

| StudentID | StudentName |
|-----------|-------------|
| 1         | John Doe    |
| 2         | Jane Smith  |

Таблица курсов:

| CourseID | CourseName |
|----------|------------|
| 101      | Math       |
| 102      | Science    |
| 103      | History    |

Таблица преподавателей:

| CourseID | InstructorName |
|----------|----------------|
| 101      | Dr. Smith      |
| 102      | Dr. Johnson    |
| 103      | Dr. Brown      |

Таблица оценок:

| StudentID | CourseID | Grade |
|-----------|----------|-------|
| 1         | 101      | A     |
| 1         | 102      | B     |
| 2         | 101      | A     |
| 2         | 103      | C     |

Объяснение:

    В таблице студентов содержатся только идентификаторы студентов и их имена.
    В таблице курсов содержатся идентификаторы курсов и их названия.
    В таблице преподавателей содержится информация о том, какой преподаватель ведет каждый курс.
    В таблице оценок хранятся оценки студентов по курсам.
    Теперь нет транзитивных зависимостей, так как атрибуты зависят только от первичного ключа каждой таблицы, что соответствует 3NF.
Проверьте себя: Чем транзитивная зависимость (нарушение 3NF) отличается от частичной зависимости (нарушение 2NF) — в чём разница между «зависит от части ключа» и «зависит через другой неключевой атрибут»?
13
MarkdownРазбор концепции60 строк

Четвёртая нормальная форма (4NF): устранение многозначных зависимостей

15__MongoDB_arrays/SQL_normalization_laws/theory_04__4NF.md

Требование 4NF — 3NF плюс отсутствие многозначных зависимостей, когда в одной таблице смешаны два независимых друг от друга набора данных (Hobby и Language), оба зависящие от StudentID, но не связанные между собой. Решение — разнесение хобби и языков по отдельным таблицам, каждая со своей связью к StudentID.

  • Определение многозначной зависимости: два независимых набора данных в одной таблице
  • Антипример: Hobby и Language в одной строке, независимые друг от друга
  • Разбиение на таблицу студентов + отдельную таблицу хобби + отдельную таблицу языков
  • Итог: каждая таблица хранит только один независимый набор данных
Показать начало файла (60 строк всего)
Четвертая нормальная форма (4NF) требует выполнения всех условий 3NF и того, чтобы в таблице не было многозначных зависимостей, т.е. таблица не должна содержать атрибуты, которые одновременно зависят от нескольких независимых многозначных факторов.
Многозначная зависимость

Многозначная зависимость возникает, когда один атрибут зависит от нескольких независимых факторов. Это может происходить, когда в одной строке таблицы хранятся несколько независимых наборов данных.
Антипример таблицы, не соответствующей 4NF:

| StudentID | StudentName | Hobby        | Language   |
|-----------|-------------|--------------|------------|
| 1         | John Doe    | Reading      | English    |
| 1         | John Doe    | Painting     | French     |
| 2         | Jane Smith  | Swimming     | Spanish    |
| 2         | Jane Smith  | Hiking       | German     |

Объяснение:

    В таблице имеются две независимые группы данных:
        Хобби студента (Hobby).
        Язык, который студент изучает (Language).
    Эти данные должны храниться отдельно, потому что каждый из этих атрибутов зависит от StudentID, но они независимы друг от друга. Это нарушение 4NF.

Преобразование в 4NF (с учётом нормализации):

Чтобы соблюсти 4NF, нам нужно создать две новые таблицы: одну для хобби, другую для языков. Это избавит от многозначных зависимостей.
Таблица студентов:

| StudentID | StudentName |
|-----------|-------------|
| 1         | John Doe    |
| 2         | Jane Smith  |

Таблица хобби:

| StudentID | Hobby    |
|-----------|----------|
| 1         | Reading  |
| 1         | Painting |
| 2         | Swimming |
| 2         | Hiking   |

Таблица языков:

| StudentID | Language |
|-----------|----------|
| 1         | English  |
| 1         | French   |
| 2         | Spanish  |
| 2         | German   |

Почему это решение правильное?

    Каждая таблица теперь хранит только один набор данных (хобби или языки) в зависимости от StudentID. Это устраняет многозначные зависимости.
    В таблице студентов больше нет избыточных данных. 
    Каждая строка в таблице студентов хранит только уникальную информацию о студенте (его идентификатор и имя).
    Хобби и языки теперь хранятся отдельно в своих таблицах и могут быть многократными для каждого студента, но без нарушения 4NF, 
    поскольку хобби и языки являются независимыми многозначными аттрибутами.

Заключение:

Таким образом, в предыдущем ответе имело место ошибочное объединение хобби и языков с таблицей студентов, что нарушало принципы нормализации (2NF). В 4NF мы должны создать отдельные таблицы для хобби и языков, чтобы предотвратить многозначные зависимости и правильно нормализовать базу данных.
Проверьте себя: Если у студента 2 хобби и 3 языка, сколько строк потребовалось бы в исходной ненормализованной таблице, чтобы перечислить все комбинации, и почему это и есть проблема, которую решает 4NF?
14
MarkdownРазбор концепции64 строк

Пятая нормальная форма (5NF / PJ-NF): устранение несогласованных соединений

15__MongoDB_arrays/SQL_normalization_laws/theory_05__5NF.md

5NF (проекция-объединение нормальная форма) требует, чтобы любая зависимость восстанавливалась через проекции без избыточности. Антипример — таблица (StudentID, CourseID, InstructorID, InstructorName), где данные о преподавателе избыточно привязаны к курсу через студента. Решение — три отдельные таблицы (студенты-курсы, преподаватели, курсы-преподаватели), которые полностью восстанавливаются через JOIN без потери и без дублирования.

  • Определение 5NF: 4NF + отсутствие несогласованных соединений (join dependencies)
  • Антипример: InstructorName избыточно привязан через курс к каждому студенту
  • Разбиение на три таблицы: студенты-курсы, преподаватели, курсы-преподаватели
  • Итог: все связи восстановимы через стандартный JOIN без избыточности
Показать начало файла (64 строк всего)
**Пятая нормальная форма (5NF)**, также известная как ***проекция-объединение нормальная форма (PJ/NF)***, требует, чтобы таблица была разделена так, чтобы каждая зависимость в таблице могла быть восстановлена с использованием только проекций (подмножеств) данных, а не через избыточные данные.
Правила 5NF:

    В 5NF таблица должна быть в 4NF и не содержать несогласованных соединений (join dependencies).
    Таблица не должна содержать избыточных данных, которые можно восстановить через соединение нескольких таблиц.
    Для этого каждый атрибут должен зависеть от всей проекции таблицы, 
    и любые соединения между таблицами должны быть восстановимы через стандартные операции объединения.

Пример нарушения 5NF:

Предположим, у нас есть следующая таблица:

| StudentID | CourseID | InstructorID | InstructorName |
|-----------|----------|--------------|----------------|
| 1         | 101      | 1            | Dr. Smith      |
| 1         | 102      | 2            | Dr. Johnson    |
| 2         | 101      | 1            | Dr. Smith      |
| 2         | 103      | 3            | Dr. Brown      |

Объяснение проблемы:

    В этой таблице три атрибута — StudentID, CourseID, и InstructorID — используются вместе, чтобы определить уникальность каждой строки.
    Проблема заключается в том, что информация о преподавателях (InstructorID и InstructorName) привязана к конкретному курсу, а не непосредственно к студенту. 
    Это создает избыточность, потому что данные о преподавателях могут быть восстановлены из других атрибутов таблицы.

Преобразование в 5NF:

Чтобы привести таблицу к 5NF, мы должны разделить её так, чтобы каждое соединение можно было восстановить без избыточности. Для этого создадим несколько таблиц:
Таблица студентов и курсов:

| StudentID | CourseID |
|-----------|----------|
| 1         | 101      |
| 1         | 102      |
| 2         | 101      |
| 2         | 103      |

Таблица преподавателей:

| InstructorID | InstructorName |
|--------------|----------------|
| 1            | Dr. Smith      |
| 2            | Dr. Johnson    |
| 3            | Dr. Brown      |

Таблица курсов и преподавателей:

| CourseID | InstructorID |
|----------|--------------|
| 101      | 1            |
| 102      | 2            |
| 103      | 1            |

Почему это решение правильно:

    Теперь каждая таблица хранит уникальную информацию и никаких избыточных данных.

Все связи между таблицами можно восстановить через обычные операции объединения (JOIN), и никакие данные не повторяются.
Заключение:

…
Проверьте себя: Чем избыточность, которую устраняет 5NF, отличается от многозначной зависимости, которую устраняет 4NF?
15
MarkdownРазбор концепции71 строк

Шестая нормальная форма (6NF): атомарность для временных данных

15__MongoDB_arrays/SQL_normalization_laws/theory_06__6NF.md

6NF — самая строгая форма, применяется к данным, меняющимся во времени (цены, статусы), требует, чтобы каждая таблица хранила ровно одну атомарную зависимость. На примере истории цен товара (ProductID, Price, StartDate, EndDate) показано разбиение на таблицу продуктов и таблицу цен с временными метками, а затем — предельный вариант с одной атомарной точкой времени на запись. Указана область применения: финансовые системы, склад, отслеживание изменений с точностью до момента времени.

  • Определение 6NF: одна атомарная зависимость на таблицу, для временных данных
  • Пример: история изменения цены товара с StartDate/EndDate
  • Разбиение на таблицу продуктов и таблицу цен с временными интервалами
  • Предельная форма: одна атомарная временная точка на запись
  • Область применения: финансы, склад, история изменений с точностью до момента
Показать начало файла (71 строк всего)
**Шестая нормальная форма (6NF)** — это наиболее строгая форма нормализации, которая применяется, когда мы работаем с временными данными (например, данными, которые изменяются со временем) и требуется минимизация избыточности в таких ситуациях. Она была разработана с целью устранения всех зависимостей, включая временные, и обеспечения сохранности данных без потери информации о изменениях.
Важные моменты о 6NF:

    6NF актуальна для систем, работающих с временными данными или когда изменения данных происходят часто и должны быть отслежены с точностью.
    Она обеспечивает полное разделение данных и позволяет иметь только одну атомарную зависимость в каждой таблице.
    В таблицах, находящихся в 6NF, каждая таблица хранит одну фактическую информацию (одну зависимость), что минимизирует избыточность и возможные аномалии.

Пример 6NF:

Предположим, у нас есть система, которая отслеживает изменение цен на товары с течением времени. Без использования 6NF, таблица может выглядеть так:

| ProductID | ProductName | Price  | StartDate  | EndDate    |
|-----------|-------------|--------|------------|------------|
| 1         | Apple       | 1.00   | 2023-01-01 | 2023-02-01 |
| 1         | Apple       | 1.10   | 2023-02-02 | 2023-03-01 |
| 2         | Banana      | 0.50   | 2023-01-01 | 2023-02-01 |
| 2         | Banana      | 0.60   | 2023-02-02 | 2023-03-01 |

Здесь таблица сохраняет информацию о цене товара в определенный промежуток времени. Мы видим, что одно и то же название продукта повторяется несколько раз, а цена изменяется в разные моменты времени.
Как привести к 6NF?

Чтобы привести такую таблицу к 6NF, мы должны разделить данные на несколько таблиц, где каждая таблица будет содержать атомарные (независимые) значения. Так мы устраним избыточность и будем точно отслеживать изменения.
1. Таблица продуктов:

| ProductID | ProductName |
|-----------|-------------|
| 1         | Apple       |
| 2         | Banana      |

2. Таблица цен (с временными метками):

| ProductID | Price  | StartDate  | EndDate    |
|-----------|--------|------------|------------|
| 1         | 1.00   | 2023-01-01 | 2023-02-01 |
| 1         | 1.10   | 2023-02-02 | 2023-03-01 |
| 2         | 0.50   | 2023-01-01 | 2023-02-01 |
| 2         | 0.60   | 2023-02-02 | 2023-03-01 |

В этой таблице хранится только информация о цене товара с точными временными интервалами, без повторения данных о продукте.
3. Таблица времени:

Для обеспечения 6NF и минимизации зависимости от времени, можно создать таблицу с конкретными временными интервалами. Эта таблица будет записывать изменения, но с атомарным временем для каждой записи:

| ProductID | Price  | Timepoint  |
|-----------|--------|------------|
| 1         | 1.00   | 2023-01-01 |
| 1         | 1.10   | 2023-02-02 |
| 2         | 0.50   | 2023-01-01 |
| 2         | 0.60   | 2023-02-02 |

Здесь таблица хранит только изменение цены для одного момента времени (атомарные временные точки). Система в 6NF помогает точно отслеживать каждый момент изменения, например, когда произошла цена в один конкретный день, независимо от других атрибутов.
Почему это важно?

    6NF позволяет минимизировать избыточность в данных и сохранить точную историю изменений для каждого атрибута.
    Если нужно будет отслеживать изменения с точностью до времени или даты, 
    или если один атрибут может изменяться несколько раз в день (например, цены или статус заказа), 
    то 6NF поможет хранить такую информацию без избыточности.
    6NF также важна, когда данные о времени (например, временные метки или статус) изменяются независимо от других данных.

Применение 6NF:
…
Проверьте себя: Почему 6NF актуальна именно для систем с частыми изменениями данных во времени, а не для статичных справочников вроде списка стран?
16
MarkdownРазбор концепции84 строк

Нормальная форма Бойса-Кодда (BCNF): детерминант должен быть ключом

15__MongoDB_arrays/SQL_normalization_laws/theory_07__BCNF.md

BCNF строже 3NF: для любой функциональной зависимости X → Y атрибут X обязан быть потенциальным ключом. Показывает таблицу, которая формально проходит 1NF/2NF/3NF, но нарушает BCNF из-за зависимости CourseID → InstructorName, где CourseID сам по себе не уникален (курс ведётся у нескольких студентов). Решение — вынести эту зависимость в отдельную таблицу, где CourseID становится настоящим ключом.

  • Определение BCNF: для X → Y атрибут X должен быть потенциальным ключом
  • Проверка таблицы по 1NF, 2NF, 3NF — формально всё выполняется
  • Разбор нарушения: CourseID → InstructorName, но CourseID не ключ всей таблицы
  • Решение: таблица курсы-преподаватели (CourseID как ключ) + таблица студенты-курсы-оценки
Показать начало файла (84 строк всего)
## Нормальная форма Бойса — Кодда (BCNF)

**Нормальная форма Бойса — Кодда (BCNF)** — это более строгая версия третьей нормальной формы (3NF), которая устраняет все виды функциональных зависимостей, выходящих за пределы потенциальных ключей. В BCNF, если существует функциональная зависимость X→Y, то атрибуты X должны составлять потенциальный ключ.

Давайте рассмотрим пример, который удовлетворяет 1NF, 2NF, и 3NF, но противоречит BCNF.

Для этого мы создадим таблицу, где функциональная зависимость нарушает правило BCNF, хотя таблица еще будет соответствовать первым трем нормальным формам.
Пример

| StudentID | CourseID | InstructorName | Grade |
|-----------|----------|----------------|-------|
| 1         | 101      | Dr. Smith      | A     |
| 1         | 102      | Dr. Johnson    | B     |
| 2         | 101      | Dr. Smith      | A     |
| 2         | 103      | Dr. Brown      | C     |

Проверим соответствие первым трем нормальным формам:
1NF (Первая нормальная форма):

Таблица удовлетворяет 1NF, так как все данные атомарны (каждая ячейка содержит только одно значение).
2NF (Вторая нормальная форма):

Таблица удовлетворяет 2NF, так как:

    Все неключевые атрибуты (InstructorName, Grade) полностью зависят от всего первичного ключа (StudentID, CourseID).
    Нет частичных зависимостей, так как каждый неключевой атрибут зависит от всей комбинации ключа.

3NF (Третья нормальная форма):

Таблица удовлетворяет 3NF, так как:

    Все неключевые атрибуты (InstructorName, Grade) зависят только от ключа (StudentID, CourseID).
    Нет транзитивных зависимостей (InstructorName зависит только от CourseID, и это не транзитивная зависимость).

Анализ по BCNF:

Разбор функциональной зависимости

Функциональная зависимость:
    CourseID → InstructorName.
    Это означает, что для каждого CourseID (идентификатора курса) однозначно определяется преподаватель (InstructorName). Например, курс с CourseID = 101 всегда преподает Dr. Smith, курс с CourseID = 102 — Dr. Johnson, и так далее.

Почему это нарушает BCNF?

    В BCNF говорится, что для каждой функциональной зависимости X → Y в таблице, X должно быть потенциальным ключом (или суперклавишем). 
    Это означает, что X должно быть уникальным идентификатором для каждой строки таблицы.

    В нашем примере:
        StudentID и CourseID вместе составляют первичный ключ таблицы, потому что комбинация этих двух атрибутов уникально идентифицирует каждую строку.
        Однако, CourseID → InstructorName предполагает, что для каждого CourseID однозначно определяется InstructorName.
        но CourseID сам по себе не является уникальным идентификатором.
        Один и тот же курс может преподаваться нескольким студентам (например, курс Math может преподаваться нескольким студентам).

    Согласно BCNF, CourseID должен быть потенциальным ключом, но это не так. 
    CourseID не уникален в контексте всей таблицы, потому что для одного и того же курса могут быть несколько студентов, 
    получающих разные оценки.

Решение для приведения таблицы в BCNF:

    Чтобы устранить нарушение BCNF, нам нужно разделить таблицу таким образом, чтобы функциональная зависимость CourseID → InstructorName находилась в отдельной таблице, где CourseID станет ключом.
…
Проверьте себя: Почему таблица из примера удовлетворяет 3NF, но не BCNF — в чём именно разница между требованиями этих двух форм?
17
MarkdownРазбор концепции68 строк

Доменно-ключевая нормальная форма (DKNF): самая строгая форма нормализации

15__MongoDB_arrays/SQL_normalization_laws/theory_08__DKNF.md

DKNF требует, чтобы все ограничения данных выражались исключительно через домены значений и ключи, без каких-либо дополнительных функциональных зависимостей. На той же таблице студентов/курсов/преподавателей показано, что зависимость CourseID → InstructorName нарушает DKNF по той же причине, что и BCNF (CourseID не ключ), и решается тем же разбиением на две таблицы. Отдельно указана практическая сложность: для большинства реальных задач достаточно 3NF/BCNF, DKNF — скорее теоретический ориентир.

  • Определение DKNF: все ограничения — только через домены и ключи
  • Условия: функциональные зависимости только на суперключи, отсутствие сложных зависимостей
  • Тот же антипример CourseID → InstructorName, что и в BCNF
  • Решение — то же разбиение на таблицы курсы-преподаватели и студенты-курсы
  • Практическое замечание: DKNF сложна в поддержке, обычно хватает 3NF/BCNF
Показать начало файла (68 строк всего)
## Доменно-ключевая нормальная форма (DKNF)

DKNF — это самая строгая форма нормализации базы данных. Она гарантирует, что все ограничения на данные в таблице могут быть выражены исключительно через ограничения на домены (домен — это множество допустимых значений для атрибута) и ключи.

Простой способ объяснить DKNF — это таблица, в которой не осталось ни одной аномалии данных (ни одной функциональной, ни транзитивной зависимости), и все ограничения на данные контролируются исключительно с помощью доменных и ключевых ограничений, без необходимости вводить дополнительные функциональные зависимости.
Характеристика DKNF:

    Каждая таблица в DKNF должна быть такой, что все ограничения на данные могут быть выражены в виде ограничений на домены значений атрибутов и ключей.
    Все функциональные зависимости должны быть "сущностными" (относящимися к ограничениям значений), а не "произвольными" (определяемыми избыточными зависимостями).
    В отличие от BCNF или 3NF, в DKNF не существует транзитивных зависимостей, и любые данные можно выразить с помощью атрибутов или их значений.

Условия для удовлетворения DKNF:

    Все функциональные зависимости являются зависимостями на ключи: То есть, для каждой функциональной зависимости X→Y, X должен быть суперключом.
    Отсутствие сложных зависимостей: Каждая зависимость должна быть явной и строго ограниченной доменом значений.

Пример таблицы, нарушающей DKNF:

Возьмем пример с таблицей студентов и курсов:

| StudentID | CourseID | InstructorName | Grade |
|-----------|----------|----------------|-------|
| 1         | 101      | Dr. Smith      | A     |
| 1         | 102      | Dr. Johnson    | B     |
| 2         | 101      | Dr. Smith      | A     |
| 2         | 103      | Dr. Brown      | C     |

Нарушение DKNF:

    В этой таблице существует зависимость CourseID → InstructorName. То есть, для каждого CourseID однозначно определяется InstructorName.
    Эта зависимость нарушает DKNF, потому что CourseID не является потенциальным ключом. Например, CourseID не может быть уникальным идентификатором для каждой строки таблицы, поскольку курс может преподаваться нескольким студентам. Таким образом, зависимость CourseID → InstructorName не может быть выражена через ограничения на домены и ключи, что нарушает принцип DKNF.

Приведение таблицы в DKNF:

Чтобы привести таблицу к DKNF, нам нужно исключить все такие функциональные зависимости, которые не могут быть выражены через доменные ограничения. Разделим таблицу на две:
Таблица 1: Курсы и преподаватели

| CourseID | InstructorName |
|----------|----------------|
| 101      | Dr. Smith      |
| 102      | Dr. Johnson    |
| 103      | Dr. Brown      |

Здесь CourseID является ключом, и зависимость CourseID → InstructorName теперь не нарушает DKNF, так как она выражена через ключ (и это единственная зависимость, которую можно выразить на уровне домена).
Таблица 2: Студенты и курсы с оценками

| StudentID | CourseID | Grade |
|-----------|----------|-------|
| 1         | 101      | A     |
| 1         | 102      | B     |
| 2         | 101      | A     |
| 2         | 103      | C     |

Здесь StudentID и CourseID вместе составляют ключ, и эта таблица также соответствует DKNF, так как нет других зависимостей между атрибутами, кроме как через ключи.
Почему теперь таблицы удовлетворяют DKNF?

    Первое ограничение: Все зависимости теперь выражены через ключи.
    Второе ограничение: Все значения в таблице ограничены доменом (например, Grade ограничено набором возможных оценок, InstructorName ограничено возможными именами преподавателей).
    Нет лишних зависимостей: Например, зависимость CourseID → InstructorName теперь находится в отдельной таблице, где CourseID является ключом, и эта зависимость контролируется доменом.

…
Проверьте себя: Если таблица уже удовлетворяет BCNF, значит ли это автоматически, что она удовлетворяет и DKNF? Судя по примерам в файле — да, но какая формальная разница остаётся между этими двумя формами?
18
MarkdownРазбор концепции158 строк

Связи в MySQL через FOREIGN KEY: one-to-one и one-to-many на практике

15__MongoDB_arrays/SQL_normalization_laws/theory_10__FK.md

Показывает, что в MySQL реализуемы фактически только две связи — one-to-one и one-to-many, и разница между ними чисто в ограничениях на поле внешнего ключа (UNIQUE делает связь 1:1, его отсутствие — 1:many). Три полных SQL-скрипта подряд: (1) one-to-zero-or-one — user_id UNIQUE, но допускает NULL; (2) строгая one-to-one — user_id UNIQUE NOT NULL; (3) заявлена как one-to-many, но код скопирован из строгой one-to-one без реального снятия UNIQUE — расхождение между текстом и кодом стоит проверить перед использованием.

  • В MySQL по факту 2 вида связи: one-to-one и one-to-many, различие — в ограничении на FK
  • Пример 1: one_to_zero_or_one — user_id UNIQUE (NULL допустим)
  • INSERT с user_id = NULL — проходит; повторный INSERT с user_id = 1 — ошибка UNIQUE
  • Пример 2: strictly_one_to_one — user_id UNIQUE NOT NULL
  • Пример 3 (заявлен как one-to-many): код по факту не отличается от примера 2 — UNIQUE NOT NULL не снят

Что выводит: DDL/DML для живого сервера MySQL, не выполнялся. Ожидаемый эффект первого скрипта: 4 пользователя, 4 паспорта (включая один с user_id = NULL, что допустимо), затем неудачная попытка вставить второй паспорт с user_id = 1 — ошибка нарушения UNIQUE. Во втором скрипте (NOT NULL) вставка с NULL тоже завершится ошибкой.

Показать начало файла (158 строк всего)
Строго говоря, в MySQL есть только 2 связи: one-to-one и one-to-many. 

## Когда используется связь one-to-one (один к одному)?

Следует помнить, что основная идея SQL реализация системы Сущность-Связь (Entity-Relationship).
Поэтому разнесение данных Person-Passport по разным таблицам, соединённым связью *one-to-one*, -
это, в первую очередь, следование правилам нормализации (каждой сущности - своя отдельная таблица).

Разница между one-to-many и one-to-one только в дополнительном ограничении на
уникальность поля FK.

Пример one-to-one 
(точнее one_to_zero_or_one: поскольку допустимы значения NULL у FK, 
то связь по FK в этом примере не обязательна) 

```
DROP DATABASE IF EXISTS one_to_zero_or_one;
CREATE DATABASE one_to_zero_or_one;
USE one_to_zero_or_one;


CREATE TABLE users (
    user_id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL
);


CREATE TABLE passports (
    profile_id INT AUTO_INCREMENT PRIMARY KEY,
    bio TEXT,
    user_id INT UNIQUE,         -- Ограничение на уникальность внешнего ключа
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

-- Добавление данных в таблицу users
INSERT INTO `users` (`username`) VALUES
('Alice'),
('Bob'),
('Mike'),
('Ann');

-- Добавление данных в таблицу passports
INSERT INTO `passports` (`bio`, `user_id`) VALUES
('Bio for Alice', 1),
('Bio for Bob', 2),
('Bio for Ann', 4);

-- Попытка вставить данные, чтобы проверить связь 1:0
-- Добавление записи без указания ключа не таблицу users
INSERT INTO `passports` (`bio`, `user_id`) VALUES
('Bio for NULL', NULL);

-- Попытка вставить данные, чтобы проверить связь 1:1
-- Следующий запрос вызвает ошибку из-за ограничения уникальности внешнего ключа
INSERT INTO `passports` (`bio`, `user_id`) VALUES
('Duplicate Bio for Alice', 1);


```
Немного изменяем (ужесточаем) требования к полю user_id в таблице passport:
…
Проверьте себя: В третьем скрипте файл заявляет связь one-to-many, но код таблицы passports по-прежнему объявляет user_id как UNIQUE NOT NULL — какое реальное ограничение нужно убрать в CREATE TABLE, чтобы получить настоящую one-to-many связь?
19
MarkdownРазбор концепции259 строк

Фильтрация документов по содержимому массива: $all, $size, $in, $nin и объединение массива в строку

15__MongoDB_arrays/theory_01__arrays_document_filtering.md

Разбирает операторы для условий по массивам: $all (все указанные элементы присутствуют независимо от порядка), $size (точное число элементов, с обходным путём через $expr + $gt для сравнения размера с порогом), $in/$nin (хотя бы один элемент есть/нет). Вторая часть — практический приём $reduce для склейки всех элементов массива в одну строку через разделитель, с универсальным вариантом через $toString для массивов смешанных типов, и $arrayElemAt для получения первого/последнего элемента с защитой $ifNull на случай пустого массива.

  • $all: ['electronics', 'portable'] — все элементы присутствуют, порядок неважен
  • $size: 3 — точное количество элементов в массиве
  • $expr + $gt + $size — сравнение размера массива с порогом (>, <)
  • $in / $nin — хотя бы один / ни один из списка элементов
  • $reduce с $cond и $concat — склейка строкового массива в одну строку через разделитель
  • Универсальный $reduce с $toString — склейка массива смешанных типов
  • $arrayElemAt для первого/последнего элемента + $ifNull для защиты от пустого массива
Показать начало файла (259 строк всего)
# Операторы для фильтрации документов в массиве

## 1. $all — поиск документов по указанному набору элементов

Оператор `$all` в MongoDB применяется только к массивам. Он используется для проверки,   
содержит ли массив все указанные значения, независимо от порядка элементов в массиве.

Пример:

В коллекции products, значения поля tags является массивом.
```
db.products.insertMany([
    { name: "Laptop", tags: ["electronics", "computer", "portable"] },
    { name: "Smartphone", tags: ["electronics", "mobile", "portable"] },
    { name: "Desk", tags: ["furniture", "wood", "office"] },
    { name: "Chair", tags: ["furniture", "wood"] } 
]);
```
Необходимо найти продукты, у которых в массиве tags одновременно присутствуют "electronics" и "portable".
```
db.products.find({
    tags: { $all: ["electronics", "portable"] }
});
```

Ожидаемый результат:
```
[{
  _id: ObjectId('6746172fc9bca7bc6f2363bc'),
  name: 'Laptop',
  tags: [ 'electronics', 'computer', 'portable' ]
},
{
  _id: ObjectId('6746172fc9bca7bc6f2363bd'),
  name: 'Smartphone',
  tags: [ 'electronics', 'mobile', 'portable' ]
}]
```

## 2. $size — поиск по указанному числу элементов в массиве

Оператор `$size` проверяет количество элементов в массиве.

*Пример: выбрать документы, где массив в tag содержит элемента*
```
db.collection.find({ 
    tags: { $size: 3 } 
});
```

Ожидаемый результат:
```
[{
  _id: ObjectId("67461aa23f054cc437def0fd"),
  name: "Laptop",
  tags: [ "electronics", "computer", "portable" ]
},
{
  _id: ObjectId("67461aa23f054cc437def0fe"),
  name: "Smartphone",
…
Проверьте себя: Почему для сравнения размера массива с порогом ($size > 3) нельзя написать { tags: { $size: { $gt: 3 } } } напрямую, а нужен $expr?
20
MarkdownРазбор концепции126 строк

$limit, $skip и $slice: пагинация документов против обрезки массива внутри документа

15__MongoDB_arrays/theory_02__$limit_$skip_$slice.md

Разграничивает три похожих на вид, но разных по применению оператора: $limit/$skip управляют количеством документов, возвращаемых запросом (пагинация коллекции), а $slice работает только внутри project и обрезает массив внутри каждого документа. Показаны пять вариантов $slice — первые N, последние N (отрицательное число), диапазон [offset, count], и альтернативный порядок аргументов {$slice: ['$массив', N]} при переименовании поля.

  • find().limit(2) — первые N документов из выборки
  • find().skip(2) — пропустить первые N документов
  • tags: { $slice: 2 } — первые 2 элемента массива внутри документа
  • tags: { $slice: -2 } — последние 2 элемента массива
  • tags: { $slice: [2, 1] } — 1 элемент начиная со 2-го индекса
  • new_tag: { $slice: ['$tags', -1] } — другой порядок аргументов при переименовании поля
  • Сводная таблица: $limit/$skip — для документов, $slice — для массивов
Показать начало файла (126 строк всего)
# Операторы $limit, $skip, и $slice

Эти операторы используются для управления выборкой данных, но применяются в разных контекстах

Для иллюстрации используем ту же коллекцию products:

```
db.products.insertMany([
    { name: "Laptop", tags: ["electronics", "computer", "portable"] },
    { name: "Smartphone", tags: ["electronics", "mobile", "portable"] },
    { name: "Desk", tags: ["furniture", "wood", "office"] },
    { name: "Chair", tags: ["furniture", "wood"] }
]);
```

## 1. `$limit` - Ограничивает количество документов, возвращаемых запросом.

*Пример: Вывести только два первые документа из коллекции.*

```
db.products.find().limit(2);
```

Ожидаемый результат:

```
[
  { "_id": ObjectId("..."), "name": "Laptop", "tags": ["electronics", "computer", "portable"] },
  { "_id": ObjectId("..."), "name": "Smartphone", "tags": ["electronics", "mobile", "portable"] }
]
```

## 2. `$skip` - пропускает указанное количество документов перед началом возврата.


*Пример: пропустить первые два документа и вывести оставшиеся*
```
db.products.find().skip(2);
```
Ожидаемый результат:
```
[
  { "_id": ObjectId("..."), "name": "Desk", "tags": ["furniture", "wood", "office"] },
  { "_id": ObjectId("..."), "name": "Chair", "tags": ["furniture", "wood"] }
]
```


## 3. `$slice` - "Вырезает" из указанной позиции массива указанное число элементов

Применяется для форматирования вывода массивов (т.е. ТОЛЬКО в project!) внутри документа

*Пример: получить только первые два тега для каждого документа*
```
db.products.find(
  {}, { name: 1, tags: { $slice: 2 } }
);
```

Ожидаемый результат:
…
Проверьте себя: Почему в примере с переименованием поля синтаксис $slice: ['$tags', -1] отличается от tags: { $slice: -2 } — что меняется в записи при явном указании имени массива внутри выражения?
21
MarkdownРазбор концепции96 строк

Пользовательские функции ($function) в MongoDB на JavaScript

15__MongoDB_arrays/theory_03__user_defined_functions.md

Показывает оператор $function для написания собственной логики на JS прямо внутри стадии агрегации (только внутри агрегации, доступен только базовый JS без внешних библиотек). Два примера — сложение двух полей и склейка массива в строку через arr.join(', '), с уточнением, что для массивов не-строк нужно сначала .map(String). Честно перечислены и минусы: запрещено в Atlas, может работать медленнее стандартных операторов, требует знания JS.

  • Синтаксис $function: { body, args, lang: 'js' }
  • Плюсы: лаконичность; минусы: запрещено в Atlas, медленнее, нужен JS
  • Пример: mySum = function(a, b) { return a + b; }
  • Пример: arrayAsString через arr.join(', '), с .map(String) для нестроковых массивов
  • Особенности: функция чистая, без побочных эффектов, работает только внутри агрегации
Показать начало файла (96 строк всего)
# Пользовательcкие функции в MongoDB

**Плюсы**:
- Могут существенно упростить стандартные варианты запросов
- Более лаконичны и читабельны

**Минусы**:
- Возможность их использования может быть ограничена на сервере
- Запрещены в Atlas
- Могут работать медленнее, чем стандартные средства
- Требуется знание JS

## Синтаксис
```
{
  $function: {
    body: <function_code_as_string_or_JS_function>,
    args: [ <expression1>, <expression2>, ... ],
    lang: "js"
  }
}
```  
, где:
- `body` - тело функции
- `args` - аргументы функции (массив полей, используемых как аргументы)
- `lang` - пока ТОЛЬКО js

##  Пример
Создать новое поле `mySum`, как сумму значений двух других полей
```
{
  $project: {
    mySum: {
      $function: {
        body: function(a, b) { return a + b; },
        args: ["$field1", "$field2"],
        lang: "js"
      }
    }
  }
}
```

##  Пример
Создать новое поле `arrayAsString`, в котором собираются все элементы массива `$yourArray`
через разделитель запятая с пробелом `", "`


Если все элементы массива строки - просто джойним каждый элемент массива через `", "`:
```javascript
arrayAsString: {
  $function: {
    body: function(arr) { return arr.join(", ") },
    args: ["$yourArray"],
    lang: "js"
  }
}
```

Если нет - надо добавить `.map(String)` перед  `.join(", ")`:
…
Проверьте себя: Почему $function запрещён в MongoDB Atlas — какое ограничение окружения это, скорее всего, отражает (по контрасту с self-hosted сервером)?
22
MarkdownРазбор концепции58 строк

Подключение к MongoDB в терминале Ubuntu через mongosh

15__MongoDB_arrays/theory_04__monosh_as_terminal_command.md

Пошаговая организационная инструкция: проверка и запуск службы mongod через systemctl (status/start/enable), подключение mongosh к локальному и удалённому серверу (с указанием строки подключения mongodb://user:pass@host:27017), подключение сразу к конкретной базе через путь в URI, и базовая проверка после входа (db, show dbs).

  • sudo systemctl status/start/enable mongod — проверка и запуск службы
  • mongosh — подключение к локальному серверу
  • mongosh "mongodb://user:pass@host:27017" — подключение к удалённому серверу
  • mongosh "mongodb://localhost:27017/mydatabase" — подключение сразу к базе
  • db и show dbs — проверка подключения после входа
Показать начало файла (58 строк всего)
# Как подключиться к MongoDB в терминале на **Ubuntu**.

## 1. Убедитесь, что MongoDB установлена и запущена

Проверка статуса службы:

```bash
sudo systemctl status mongod
```

Запустить (если не запущена):

```bash
sudo systemctl start mongod
```

Включить автозапуск:

```bash
sudo systemctl enable mongod
```

---

## 2. Подключение к MongoDB

Если установлен `mongosh`, подключение к локальному серверу:

```bash
mongosh
```

К удалённому серверу:

```bash
mongosh "mongodb://username:********@host:27017"
```


---

## 3. Подключение к конкретной базе

```bash
mongosh "mongodb://localhost:27017/mydatabase"
```

---

## 4. Проверка подключения

После входа можно выполнить:

```js
db
show dbs
```
Проверьте себя: Зачем перед подключением рекомендуется проверить systemctl status mongod, а не сразу пытаться подключиться через mongosh?
23
MarkdownРазбор концепции48 строк

Создание коллекции: автоматически, явно и с TTL/capped-настройками

16__MongoDB_write/theory_00_create_collection.md

Три способа завести коллекцию: неявно при первой вставке документа (самый частый случай), явно через db.createCollection() с параметрами, и с особыми режимами — capped (фиксированный размер, старые документы вытесняются по FIFO при переполнении) и TTL (документы автоматически удаляются по истечении времени через индекс на поле даты). Отдельно подчёркнуто требование к TTL: поле, на которое ставится индекс, обязано иметь тип Date, иначе автоочистка не сработает.

  • insertOne() без createCollection — коллекция создаётся неявно
  • db.createCollection(имя, параметры) — явное создание
  • capped collection: фиксированный размер, вытеснение старых документов по FIFO
  • TTL: createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
  • Требование: поле для TTL-индекса обязано быть типа Date
Показать начало файла (48 строк всего)
## 1. Автоматическое создание при вставке

Самый простой способ — просто вставить документ, и MongoDB сама создаст коллекцию.
```
db.новаяКоллекция.insertOne({ имя: "Тест" })
```
Используется чаще всего. Подходит для обычных коллекций без специальных настроек.

## 2. Явное создание через db.createCollection()

```
db.createCollection(имя, параметры)
```
Примеры:

### Обычная коллекция:

```
db.createCollection("users")
```

### Ограниченная (capped):

db.createCollection("logs", { capped: true, size: 1024 * 1024 })

**Что такое `capped` collection?**
- Это фиксированная по размеру коллекция (1024 Кб * 1024 = 1 Мб)
- Когда размер достигает лимита, старые документы удаляются автоматически, чтобы освободить место для новых.
- Документы удаляются в порядке вставки (FIFO — First In, First Out).
- Невозможно удалить или обновить размер документа, если он превышает размер вставленного ранее.

### С автоматической очисткой по времени (TTL):
```
db.createCollection("sessions")
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
```

- Создаётся коллекция `sessions`
- Создаётся TTL-индекс (Time-To-Live) — MongoDB будет автоматически удалять документы, когда они "устареют" (в нашем случае через час).

ВАЖНО: при создании документа поле `createdAt` должно быть датой!

```
db.sessions.insertOne({
  userId: 123,
  createdAt: new Date()  // правильно: тип `Date`
})
```
Проверьте себя: Что произойдёт с TTL-индексом, если поле createdAt сохранить не как Date, а как строку с датой?
24
MarkdownРазбор концепции29 строк

insertOne и insertMany: добавление документов через MongoDB Shell

16__MongoDB_write/theory_01__add_or_create_documents.md

Короткий файл про два метода вставки — insertOne для одного документа и insertMany для нескольких сразу одним вызовом — с примерами на коллекции people. В конце — ссылка вперёд на менее очевидный способ добавления документов: через методы update с опцией upsert (подробности вынесены в отдельный файл theory_02__update.md).

  • db.people.insertOne({...}) — один документ
  • db.people.insertMany([{...}, {...}]) — несколько документов за один вызов
  • Ссылка вперёд: документы можно создавать и через update с upsert
Показать начало файла (29 строк всего)
# Методы добавление документов

Помимо удобного и наглядного добавление документов через графический интерфейс Compass, можно также добавлять новые документы через MongoDB Shell.

## 1. Метод insertOne

Этот метод добавляет один документ в коллекцию.

```
db.people.insertOne(
  { name: "Alice", age: 30, city: "New York" }
);
```
## 2. Метод insertMany

Используется для добавления нескольких документов за один вызов.

```
db.people.insertMany([
  { name: "Bob", age: 25, city: "Los Angeles" },
  { name: "Carol", age: 27, city: "Chicago" }
]);
```

## 3. Методы update

Перечисленные выше способы позволяют явно создать (добавить) новые документы в коллекцию.
Помимо этого, новый документ может быть добавлен в коллекцию и с помощью методов update (подробнее в [theory_02__update.md](./theory_02__update.md))
 
Проверьте себя: Чем insertMany отличается от нескольких вызовов insertOne подряд с точки зрения количества обращений к серверу?
25
MarkdownРазбор концепции250 строк

updateOne, updateMany, replaceOne и полный набор операторов обновления

16__MongoDB_write/theory_02__update.md

Сравнивает три метода изменения документов по трём осям — сколько документов затрагивает, меняет поля или заменяет документ целиком, поддерживает ли upsert (создание нового документа, если фильтр ничего не нашёл). Дальше — исчерпывающий каталог операторов: для полей ($set, $unset, $rename, $inc, $mul), для массивов ($push, $addToSet, $pop, $pull, $pullAll, а также модификаторы $each/$slice/$position внутри $push) и для условной замены значений ($min, $max, $currentDate).

  • updateOne / updateMany / replaceOne — сравнительная таблица (кол-во документов, замена/изменение, upsert)
  • upsert: true — создание нового документа, если фильтр не нашёл совпадений
  • $set, $unset, $rename, $inc, $mul — операторы для полей
  • $push, $addToSet, $pop, $pull, $pullAll — операторы для массивов
  • $each, $slice, $position внутри $push — массовое добавление с ограничением и позицией
  • $min, $max, $currentDate — условное обновление и текущая дата/время
Показать начало файла (250 строк всего)
# Обновление существующих документов с помощью методов update

## 1. Метод updateOne

Этот метод обновляет только первый документ, соответствующий фильтру.

Что делает:
- Если документ найден:
  - Меняет указанные поля с помощью операторов ($set, $inc и т.д.). 
  - Если указанных полей нет, они добавляются.
- Если документ не найден и включён upsert: true, создаётся новый документ, который:
  - Содержит данные из фильтра.
  - Содержит данные, указанные в $set (или других операторах).
- Если документ не найден и `upsert` выключен (`{ upsert: false }`) или вовсе отсутствует, то апдейт будет попросту проигнорирован.

Пример:
```
db.collection.updateOne(
  { name: "Alice" }, // Фильтр
  { $set: { age: 30 } }, // Обновление
  { upsert: true } // Опция upsert
);
```

Результат:
- Если документ `{ name: "Alice" }` найден:
```
{ "name": "Alice", "age": 30 }
```
- Если документа нет:
```
{ "name": "Alice", "age": 30 }
```

## 2. Метод updateMany

Этот метод обновляет все документы, которые соответствуют фильтру.

- Что делает:
  - Если документы найдены:
  - Меняет указанные поля с помощью операторов.
  - Если указанных полей нет, они добавляются.
- Если ни одного документа не найдено и включён upsert: true, создаётся один новый документ:
  - С данными из фильтра.
  - С данными из $set (или других операторов).

Пример:
```
db.collection.updateMany(
  { city: "New York" }, // Фильтр
  { $set: { visited: true } }, // Обновление
  { upsert: true } // Опция upsert
);
```

Результат:
- Если есть несколько документов с city: "New York":
```
{ "city": "New York", "visited": true }
```
…
Проверьте себя: Чем поведение updateOne с upsert: true отличается от replaceOne с upsert: true, если в обоих случаях документ по фильтру не найден?
26
MarkdownРазбор концепции106 строк

deleteOne, deleteMany и findOneAndDelete: удаление документов

16__MongoDB_write/theory_03__delete.md

Сравнивает три способа удаления — deleteOne (первый подходящий), deleteMany (все подходящие) и findOneAndDelete (удаляет и возвращает содержимое удалённого документа, с возможностью указать sort для выбора конкретного, например самого старого). Отдельным блоком — предупреждения: пустой фильтр {} удаляет всю коллекцию, методы delete* не возвращают удалённые данные (для этого нужен findOneAndDelete или предварительный find), deleteMany быстрее при массовом удалении.

  • deleteOne({filter}) — удаляет первый подходящий документ
  • deleteMany({filter}) — удаляет все подходящие, пустой фильтр = вся коллекция
  • findOneAndDelete({filter}, {sort}) — удаляет и возвращает содержимое документа
  • Сравнительная таблица трёх методов
  • Предупреждения: пустой фильтр опасен, delete* не возвращают данные, deleteMany быстрее для массового удаления
Показать начало файла (106 строк всего)
# Удаление документов

В MongoDB есть несколько методов для удаления документов из коллекции. Они отличаются по количеству удаляемых документов и способу работы. Вот подробное описание:

## 1. deleteOne

Удаляет первый найденный документ, который соответствует заданному фильтру.

Синтаксис:
```
db.collection.deleteOne(
  { <фильтр> } // Условие для удаления
)
```

Особенности:
  - Удаляет только один документ, даже если фильтру соответствует несколько документов.
  - Если фильтр пустой, удаляет первый документ в коллекции (не рекомендуется использовать без фильтра).

Пример:
```
db.collection.deleteOne({ name: "Alice" });
```
(Удалит первый документ, где name равно "Alice")

## 2. deleteMany

Удаляет все документы, которые соответствуют заданному фильтру.

Синтаксис:
```
db.collection.deleteMany(
  { <фильтр> } // Условие для удаления
)
```

Особенности:
- Удаляет все документы, соответствующие фильтру.
- Если фильтр пустой, удаляет все документы в коллекции (будьте осторожны).

Пример:
```
db.collection.deleteMany({ status: "inactive" });
```
(Удалит все документы, где status равно "inactive")


## 3. findOneAndDelete

Находит и удаляет один документ, возвращая его содержимое.

Синтаксис:

```
db.collection.findOneAndDelete(
  { <фильтр> },          // Условие для удаления
  { sort: { <поле>: 1 } } // (необязательно) Сортировка, чтобы определить, какой документ удалить
)
```

…
Проверьте себя: Если нужно посмотреть, что именно удаляется, перед тем как это удалить, — какой из трёх методов для этого предназначен и почему deleteOne/deleteMany для этого не подходят?
27
MarkdownРазбор концепции545 строк

Полный справочник стадий агрегации MongoDB (30 операторов от $addFields до $unwind)

16__MongoDB_write/theory_04__aggregation_all_stages.md

Самый объёмный файл лекции — исчерпывающий каталог из 30 стадий конвейера агрегации с примером на каждую: от простых $match/$project/$sort/$limit/$skip/$count до сложных $bucket/$bucketAuto (группировка по диапазонам), $facet (несколько параллельных подсчётов за один проход), $geoNear (геопоиск, требует индекс 2dsphere), $graphLookup (рекурсивный поиск связей), $lookup (JOIN с другой коллекцией), $unwind (разворачивание массива в отдельные документы, с наглядным «было/стало»), $replaceRoot/$replaceWith (подъём вложенного документа на верхний уровень), $merge/$out (сохранение результата в коллекцию) и $setWindowFields (оконные функции без группировки). Завершается примером полного pipeline из трёх стадий подряд.

  • $addFields / $set — добавление и изменение полей (эквивалентны)
  • $bucket / $bucketAuto — группировка по диапазонам, вручную и автоматически
  • $facet — несколько независимых подсчётов в одном проходе
  • $geoNear — геопространственный поиск, требует индекс 2dsphere
  • $graphLookup — рекурсивный поиск связей (графовый запрос)
  • $group, $sortByCount — группировка и подсчёт с сортировкой
  • $lookup — JOIN с другой коллекцией
  • $match, $redact — фильтрация документов и вложенных уровней с $$KEEP/$$PRUNE/$$DESCEND
  • $project, $unset — выбор/исключение полей
  • $replaceRoot, $replaceWith — замена документа его вложенным полем
  • $sample — случайная выборка документов
  • $setWindowFields — оконные функции (сумма/ранг/среднее без группировки)
  • $unionWith — объединение с другой коллекцией
  • $unwind — разворачивание массива в отдельные документы (с примером было/стало)
  • Полный пример pipeline: $match + $group + $sort
Показать начало файла (545 строк всего)
# Операции агрегации в MongoDB Compass 

Операции агрегации обрабатывают несколько документов и возвращают вычисленные результаты. Вы можете использовать операции агрегации для:

- Группировки значений из нескольких документов.
- Выполнения операций над сгруппированными данными для получения единого результата.
- Анализа изменений данных со временем.

Для выполнения операций агрегации можно использовать:

- Конвейеры агрегации (Aggregation pipelines) — это предпочтительный метод выполнения агрегаций.
- Методы агрегации для одной цели (Single-purpose aggregation methods) — они просты в использовании, но не обладают такими возможностями, как конвейеры агрегации.




## 01. $addFields - Добавляет или изменяет поля.

*Пример: Добавить поле hours_per_day, разделив hours_per_week на 7.*
```
{
  "$addFields": {
    "hours_per_day": { "$divide": ["$hours_per_week", 7] }
  }
}
```

## 02. $bucket - Разбивает документы на группы (корзины) на основе диапазонов.

*Пример: Разделить документы по возрастным группам (до 20, от 20 до 40, от 40).*
```
{
  "$bucket": {
    "groupBy": "$age",
    "boundaries": [0, 20, 40, 60],
    "default": "other",
    "output": {
      "count": { "$sum": 1 },
      "average_income": { "$avg": "$total" }
    }
  }
}
```

## 03. $bucketAuto

Аналогично $bucket, но автоматически определяет границы.

*Пример: Создать 3 возрастные группы автоматически.*
```
{
  "$bucketAuto": {
    "groupBy": "$age",
    "buckets": 3
  }
}
```

## 04. $collStats

…
Проверьте себя: В примере $unwind документ Charlie с пустым массивом skills: [] и David с skills: null полностью исчезают из результата после разворачивания — почему так происходит и как это учитывать при построении отчётов?
28
MarkdownРазбор концепции55 строк

Аккумуляторы стадии $group: $sum, $avg, $stdDevPop и другие

16__MongoDB_write/theory_05__some_aggregation_functions.md

Табличный справочник агрегирующих функций, применяемых именно внутри $group ($sum, $avg, $min, $max, $first, $last, $push, $addToSet, $count через $sum: 1, $mergeObjects, $stdDevPop, $stdDevSamp — статистическое отклонение по всей популяции против выборочного). Завершается сквозным примером: группировка товаров по category с одновременным вычислением суммы, среднего, минимума, максимума и количества в одном запросе.

  • Таблица аккумуляторов: $sum, $avg, $min, $max, $first, $last
  • $push (все значения в массив) и $addToSet (только уникальные)
  • $count через $sum: 1, $mergeObjects — слияние документов в один объект
  • $stdDevPop vs $stdDevSamp — популяционное и выборочное стандартное отклонение
  • Комбинированный пример $group: totalPrice + avgPrice + minPrice + maxPrice + count за один проход
Показать начало файла (55 строк всего)
# Некоторые функции агрегации, используемые при группировке ($group)

В операторе $group используются агрегирующие функции, которые позволяют выполнять вычисления на основе данных в группе. 

| **Функция**        | **Описание**                                                  | **Пример использования**                         |
|---------------------|--------------------------------------------------------------|-------------------------------------------------|
| `$sum`             | Суммирует значения в группе.                                 | `{ $sum: "$price" }`                            |
| `$avg`             | Вычисляет среднее арифметическое значений в группе.          | `{ $avg: "$score" }`                            |
| `$min`             | Находит минимальное значение в группе.                      | `{ $min: "$age" }`                              |
| `$max`             | Находит максимальное значение в группе.                     | `{ $max: "$age" }`                              |
| `$first`           | Возвращает первый документ в группе.                        | `{ $first: "$name" }`                           |
| `$last`            | Возвращает последний документ в группе.                     | `{ $last: "$name" }`                            |
| `$push`            | Создаёт массив из всех значений в группе.                   | `{ $push: "$tags" }`                            |
| `$addToSet`        | Создаёт массив уникальных значений (без дублирования).       | `{ $addToSet: "$category" }`                    |
| `$count`           | Считает количество документов в группе (суммирует 1).       | `{ $sum: 1 }`                                   |
| `$mergeObjects`    | Объединяет несколько документов в один объект.              | `{ $mergeObjects: "$details" }`                 |
| `$stdDevPop`       | Вычисляет стандартное отклонение для всех значений (популяционное). | `{ $stdDevPop: "$values" }`           |
| `$stdDevSamp`      | Вычисляет стандартное отклонение (выборочное).              | `{ $stdDevSamp: "$values" }`                    |



*Пример: Группировка с вычислениями*
*Исходные данные*
```
[
  { "_id": 1, "category": "A", "price": 100 },
  { "_id": 2, "category": "A", "price": 200 },
  { "_id": 3, "category": "B", "price": 300 },
  { "_id": 4, "category": "B", "price": 400 }
]
```

*Запрос с `$group`*
```
db.collection.aggregate([
  {
    $group: {
      _id: "$category",             // Группировка по полю "category"
      totalPrice: { $sum: "$price" }, // Сумма цен в каждой группе
      avgPrice: { $avg: "$price" },  // Средняя цена в группе
      minPrice: { $min: "$price" },  // Минимальная цена
      maxPrice: { $max: "$price" },  // Максимальная цена
      count: { $sum: 1 }             // Количество документов
    }
  }
])
```

*Результат:*
```
[
  { "_id": "A", "totalPrice": 300, "avgPrice": 150, "minPrice": 100, "maxPrice": 200, "count": 2 },
  { "_id": "B", "totalPrice": 700, "avgPrice": 350, "minPrice": 300, "maxPrice": 400, "count": 2 }
]
```
Проверьте себя: В чём практическая разница между $stdDevPop и $stdDevSamp, и какой из них правильнее использовать, если данные в коллекции — это не вся генеральная совокупность, а лишь случайная выборка из неё?