Оптимизация структуры хранения данных в базе данных

Стек: Python 3.12, PostgreSQL 15, SQLAlchemy 2.0

Есть несколько таблиц, что описывают объекты, с разными полями (за исключением id и is_available).

Под "объектами" я подразумеваю просто какие-то различные модели в приложении, как пример могут выступать: "Post" и "Comment", "Book" и "Review" и тому подобное. Просто для понимания того, что объединить их не получится.

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped, mapped_column


class Base(DeclarativeBase):
    __abstract__ = True


class User(Base):
    __tablename__ = "users"
    
    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    name: Mapped[str]


class ObjectBase(Base):
    __abstract__ = True

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    is_available: Mapped[bool] = mapped_column(default=True)


class ObjectA(ObjectBase):
    __tablename__ = "object_a"

    field_1: Mapped[str]
    field_2: Mapped[int]


class ObjectB(ObjectBase):
    __tablename__ = "object_b"

    field_3: Mapped[float]
    field_4: Mapped[str]

Мне нужны таблицы с жалобами на вышеописанные объекты, а также таблица, которая хранит в себе удаленные объекты, также из числа тех, что описаны выше.

Поле is_available позволяет хранить удаленные объекты, но не показывать их, однако, помимо факта удаления также нужны детали, которые хранить в самих таблицах нецелесообразно, вроде reason, deleted_by_id, deleted_at, а что касается жалоб, их в целом может быть несколько на один объект, следовательно, даже такое не получится реализовать.

Возможные варианты решения в моих глазах выглядят так:

  1. Создание для каждого объекта по таблице
from sqlalchemy import DateTime, ForeignKey
from sqlalchemy.sql import func


class ObjectAReport(Base):
    __tablename__ = "object_a_reports"

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    object_id: Mapped[int] = mapped_column(ForeignKey("object_a.id"))
    user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))  # Пользователь, отправивший жалобу
    reason: Mapped[str] = mapped_column(nullable=False)
    sent_at: Mapped[DateTime] = mapped_column(DateTime(timezone=True), server_default=func.now())


class ObjectBReport(Base):
    __tablename__ = "object_b_reports"

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    object_id: Mapped[int] = mapped_column(ForeignKey("object_b.id"))
    user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))  # Пользователь, отправивший жалобу
    reason: Mapped[str] = mapped_column(nullable=False)
    sent_at: Mapped[DateTime] = mapped_column(DateTime(timezone=True), server_default=func.now())


class DeletedObjectA(Base):
    __tablename__ = "deleted_object_a"

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    object_id: Mapped[int] = mapped_column(ForeignKey("object_a.id"))
    deleted_by_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
    reason: Mapped[str] = mapped_column(nullable=False)
    deleted_at: Mapped[DateTime] = mapped_column(DateTime(timezone=True), server_default=func.now())


class DeletedObjectB(Base):
    __tablename__ = "deleted_object_b"

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    object_id: Mapped[int] = mapped_column(ForeignKey("object_b.id"))
    deleted_by_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
    reason: Mapped[str] = mapped_column(nullable=False)
    deleted_at: Mapped[DateTime] = mapped_column(DateTime(timezone=True), server_default=func.now())

Данная реализация мне не особо нравится за счет того, что при создании нового объекта приходится создавать 2 побочные таблицы для него, следовательно, база данных будет довольно сильно расти.

  1. Создание общей таблицы для всех объектов
from sqlalchemy import DateTime, ForeignKey
from sqlalchemy.sql import func


class ObjectReport(Base):
    __tablename__ = "object_reports"

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    object_id: Mapped[int]
    object_type: Mapped[str]  # "object_a" или "object_b". Также как более надежный вариант - создание модели ObjectType, однако, я просто как пример в данном случае описал.
    user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))  # Пользователь, отправивший жалобу
    reason: Mapped[str] = mapped_column(nullable=False)
    sent_at: Mapped[DateTime] = mapped_column(DateTime(timezone=True), server_default=func.now())


class DeletedObject(Base):
    __tablename__ = "deleted_objects"

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    object_id: Mapped[int]
    object_type: Mapped[str]  # "object_a" или "object_b".
    deleted_by_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
    reason: Mapped[str] = mapped_column(nullable=False)
    deleted_at: Mapped[DateTime] = mapped_column(DateTime(timezone=True), server_default=func.now())

Данная реализация мне не нравится тем, что страдает целостность данных и нужно дополнительно ее обеспечивать на уровне приложения, а также посредством создания триггеров, которые нужно будет пересоздавать при создании нового объекта.

Мои вопросы состоят в следующем:

  1. Являются ли подобные реализации правильным решением и, если да, то какое из них наиболее подходит для использования (и какой наиболее производительный с точки зрения получения данных сразу для нескольких "объектов")?
  2. Есть ли другие варианты решения подобной задачи (подходит ли вообще реляционная модель базы данных для подобного или хранилища "ключ-значение" лучше?)?
  3. Имеет ли смысл создания модели на основе @hybrid_property из sqlalchemy.ext.hybrid?

Ответы (0 шт):