Оптимизация структуры хранения данных в базе данных
Стек: 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
, а что касается жалоб, их в целом может быть несколько на один объект, следовательно, даже такое не получится реализовать.
Возможные варианты решения в моих глазах выглядят так:
- Создание для каждого объекта по таблице
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 побочные таблицы для него, следовательно, база данных будет довольно сильно расти.
- Создание общей таблицы для всех объектов
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())
Данная реализация мне не нравится тем, что страдает целостность данных и нужно дополнительно ее обеспечивать на уровне приложения, а также посредством создания триггеров, которые нужно будет пересоздавать при создании нового объекта.
Мои вопросы состоят в следующем:
- Являются ли подобные реализации правильным решением и, если да, то какое из них наиболее подходит для использования (и какой наиболее производительный с точки зрения получения данных сразу для нескольких "объектов")?
- Есть ли другие варианты решения подобной задачи (подходит ли вообще реляционная модель базы данных для подобного или хранилища "ключ-значение" лучше?)?
- Имеет ли смысл создания модели на основе
@hybrid_property
изsqlalchemy.ext.hybrid
?