Как хранить ФИО в базе данных
Две крайности
При проектировании БД для хранения ФИО обычно выбирают между:
- Одно поле —
full_name VARCHAR(255) - Три поля —
surname,first_name,patronymic
Оба подхода имеют проблемы. Разберём их и выберем лучший вариант.
Подход 1: Одно поле
CREATE TABLE users (
id SERIAL PRIMARY KEY,
full_name VARCHAR(255) NOT NULL
);
INSERT INTO users (full_name) VALUES ('Иванов Сергей Петрович');
Плюсы
- Простота: один столбец, один индекс
- Гибкость: любой формат («Иванов С.П.», «Sergey Ivanov»)
- Быстрый ввод: пользователь заполняет одно поле
Минусы
- Поиск по фамилии требует LIKE или полнотекстовый индекс
- Сортировка по имени невозможна без парсинга
- Персонализация («Уважаемый Сергей») требует парсинга
- Дубликаты: «Иванов С.П.» и «Сергей Иванов» — разные записи
Подход 2: Три поля
CREATE TABLE users (
id SERIAL PRIMARY KEY,
surname VARCHAR(100) NOT NULL,
first_name VARCHAR(100) NOT NULL,
patronymic VARCHAR(100)
);
INSERT INTO users (surname, first_name, patronymic)
VALUES ('Иванов', 'Сергей', 'Петрович');
Плюсы
- Точный поиск по любому компоненту
- Сортировка по фамилии, имени
- Персонализация без парсинга
- Дедупликация проще
Минусы
- Форма ввода сложнее (3 поля вместо 1)
- Импорт данных требует парсинга
- Иностранные имена не всегда укладываются в схему
Рекомендуемый подход: 4 поля
Храните и исходные данные, и распарсенные:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
-- Исходные данные (как ввёл пользователь)
full_name_original VARCHAR(255) NOT NULL,
-- Распарсенные компоненты
surname VARCHAR(100),
first_name VARCHAR(100),
patronymic VARCHAR(100),
-- Метаданные
gender CHAR(1), -- 'M', 'F', NULL
confidence DECIMAL(3,2), -- 0.00-1.00
-- Индексы
CONSTRAINT gender_check CHECK (gender IN ('M', 'F') OR gender IS NULL)
);
-- Индексы для поиска
CREATE INDEX idx_users_surname ON users(surname);
CREATE INDEX idx_users_full_name ON users USING gin(to_tsvector('russian', full_name_original));
Почему это работает
-
full_name_original — сохраняем как ввёл пользователь. Полезно для аудита и отладки.
-
surname, first_name, patronymic — заполняем автоматически через NER-парсинг. Используем для поиска и персонализации.
-
gender — определяем автоматически по имени. Используем для обращений.
-
confidence — насколько уверены в парсинге. При низкой уверенности — ручная проверка.
Пример заполнения через API
import requests
def parse_and_store_user(db, full_name: str):
# Парсим ФИО через API
response = requests.post(
"https://api.humandata.ru/v1/clean",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"query": full_name, "type": "name"}
)
data = response.json()
# Сохраняем в БД
db.execute("""
INSERT INTO users (full_name_original, surname, first_name, patronymic, gender, confidence)
VALUES (%s, %s, %s, %s, %s, %s)
""", (
full_name,
data.get("surname"),
data.get("name"),
data.get("patronymic"),
data.get("gender"),
data.get("confidence")
))
# Использование
parse_and_store_user(db, "иванов сергей петрович")
# Результат: surname='Иванов', first_name='Сергей', patronymic='Петрович', gender='M', confidence=0.98
Нормализация при сохранении
Перед записью в БД приводите данные к единому формату:
def normalize_name_component(value: str | None) -> str | None:
if not value:
return None
# Удаляем лишние пробелы
value = " ".join(value.split())
# Первая буква заглавная, остальные строчные
# Исключение: двойные фамилии (Салтыков-Щедрин)
parts = value.split("-")
normalized = "-".join(p.capitalize() for p in parts)
return normalized
# Примеры
normalize_name_component("ИВАНОВ") # "Иванов"
normalize_name_component("салтыков-щедрин") # "Салтыков-Щедрин"
normalize_name_component(" сергей ") # "Сергей"
Поиск по ФИО
С 4-польной схемой поиск работает быстро:
-- Точный поиск по фамилии
SELECT * FROM users WHERE surname = 'Иванов';
-- Поиск по части имени
SELECT * FROM users
WHERE surname ILIKE 'иван%'
OR first_name ILIKE 'иван%';
-- Полнотекстовый поиск по оригиналу
SELECT * FROM users
WHERE to_tsvector('russian', full_name_original) @@ to_tsquery('russian', 'Иванов & Сергей');
Миграция с одного поля на четыре
Если у вас уже есть таблица с full_name:
-- 1. Добавляем новые столбцы
ALTER TABLE users
ADD COLUMN surname VARCHAR(100),
ADD COLUMN first_name VARCHAR(100),
ADD COLUMN patronymic VARCHAR(100),
ADD COLUMN gender CHAR(1),
ADD COLUMN confidence DECIMAL(3,2);
-- 2. Переименовываем старый столбец
ALTER TABLE users RENAME COLUMN full_name TO full_name_original;
-- 3. Заполняем новые столбцы через batch-обработку (Python скрипт)
-- 4. Создаём индексы
CREATE INDEX idx_users_surname ON users(surname);
Итого
| Подход | Когда использовать |
|---|---|
| Одно поле | MVP, прототип, если ФИО только отображается |
| Три поля | Если ФИО вводится структурированно (госсистемы) |
| 4 поля | Рекомендуется: гибкость + точный поиск |
Рекомендация: используйте 4-польную схему с автоматическим парсингом через API. Это даёт максимум гибкости при минимуме усилий пользователя.