Термин «Нормализованный FEN» означает, что мы берем стандартную строку FEN (или DFEN) и отбрасываем от нее всё “лишнее”, оставляя только то, что физически определяет ситуацию на доске.

Стандартный FEN состоит из 6 полей (а DFEN из 7). Давай посмотрим на стартовую позицию: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 PPP

Вот эти поля по порядку:

  1. rnbq... — Расстановка фигур
  2. w — Чей ход (active color)
  3. KQkq — Права на рокировку
  4. - — Поле для взятия на проходе (en passant)
  5. 0 — Счетчик полуходов (нужен только для правила 50 ходов)
  6. 1 — Номер хода
  7. PPP — Кубики (в DFEN)

В чем проблема полного FEN?

Представь, что в одной партии игроки пришли к определенной расстановке фигур на 10-м ходу. А в другой партии они пришли к абсолютно такой же расстановке (те же фигуры, очередь хода и рокировки), но переставив ходы местами, и это случилось на 12-м ходу.

Если мы сравним их полные FEN-строки: Партия А: ... w KQkq - 2 10 Партия Б: ... w KQkq - 0 12 Для базы данных это две совершенно разные строки. Мы не сможем найти их вместе!

Что делает нормализация

Чтобы база данных понимала, что позиция на доске идентична, мы отбрасываем поля счетчиков (5 и 6) и кубики (7).

Нормализованный FEN содержит только первые 4 поля: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -

Теперь, на каком бы ходу ни возникла эта позиция, какая бы ни была история до этого и какие бы кубики ни выпали — строка всегда будет абсолютно одинаковой. Именно от этой укороченной строки мы считаем хеш и сохраняем её в positions. А кубики и номера ходов мы храним отдельно, в таблице turns.


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

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

1. Гибкость и скорость сложных поисковых запросов

Представь, что через полгода ты решишь провести исследование: “А в каком проценте партий у игроков вообще возникает ситуация с возможностью взятия на проходе (en passant)?”

Если у нас есть только строка normalized_fen (rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3), то базе данных придется делать поиск по подстроке: разбивать каждую из 50 миллионов строк по пробелам, брать 4-й элемент и проверять, не равен ли он дефису -. Текстовые операции (LIKE, split_part) работают очень медленно.

Если же у нас есть отдельная колонка en_passant, запрос выглядит так: SELECT COUNT(*) FROM positions WHERE en_passant != '-'; Это отработает за миллисекунды, особенно если на колонку повесить индекс.

То же самое касается поиска позиций, где, например, никто уже не может сделать рокировку: SELECT * FROM positions WHERE castling = '-';

2. Избежание ошибок при поиске текста

Искать w (ход белых) внутри целой строки normalized_fen через LIKE '% w %' опасно — буква w может случайно совпасть с чем-то еще (хотя в FEN это маловероятно, но в базах данных избегают поиска по паттернам, когда можно искать по точному совпадению в отдельной колонке).

3. Цена этого дублирования — нулевая

В PostgreSQL колонка CHAR(1) для цвета занимает 1 байт. Колонка VARCHAR(4) для рокировки — до 5 байт. В масштабах жесткого диска это абсолютные крохи (на 10 миллионах позиций это займет лишние ~50 мегабайт). При этом польза для аналитики колоссальная.

Продвинутый вариант (Generated Columns)

Если тебя смущает само наличие дублирования на этапе вставки данных (вдруг скрипт ошибется и запишет в active_color ‘b’, а в FEN будет ‘w’), в PostgreSQL есть элегантное решение — Генерируемые колонки (Generated Columns).

Мы можем сказать базе данных: “Вот тебе строка normalized_fen. Поля active_color и castling не нужно вставлять вручную. База, сама автоматически вырезай их из FEN-строки по пробелам при каждом сохранении”.

Тогда дублирования логики при импорте не будет вообще, а удобство запросов останется!

Если с таблицей positions теперь всё понятно, давай перейдем к games или turns?