#python #mysql #sqlalchemy #fastapi
Вопрос:
Я пытаюсь создать раздел комментариев в FastAPI, используя SQL Alchemy ORM в бэкэнде моего веб-сайта, который содержит много статей в базе данных MySQL.
У меня есть 3 таблицы -> Пользователи, Статьи и комментарии. user_id
и article_id
являются внешними ключами к таблице комментариев. Здесь на данный момент они жестко закодированы. Я попробовал некоторые рекурсивные функции, но это не сработало хорошо. Поскольку базы данных могут эффективно хранить и сортировать множество строк, я предпочитаю сортировать комментарии только в бэкэнде.
У меня есть следующее models.py
в FastAPI.
class Comments(Base):
__tablename__ = 'comment'
id = Column(Integer, primary_key=True, index=True)
text = Column(String(1000))
user_id = Column(Integer)
article_id = Column(Integer)
created_date_time = Column(DateTime)
parent_id = Column(Integer)
Я нашел однострочный SQL-запрос, который несколько близок к тому, что мне нужно, и работает для ответов на основные комментарии (комментарии, имеющие parent_id = NULL), но не работает для ответов на ответы основного комментария и не обеспечивает необходимый хронологический порядок.
SELECT * FROM comment ORDER BY COALESCE(parent_id, id);
Я хочу отсортировать данные, сохраняя родительские комментарии в обратном хронологическом порядке, т. Е. Последний родительский комментарий вверху и все его ответы (ответы на ответы) только с одним уровнем отступа, как в Instagram. Ответы (ответы на ответы) должны быть в обычном хронологическом порядке, т. е. первые комментарии вверху и последние внизу. Например:
ID | текст | идентификатор пользователя | article_id | created_date_time | parent_id |
---|---|---|---|---|---|
7 | 3 | 28 | 41 | 2021-06-30 18:50:38 | нулевой |
2 | 2 | 56 | 41 | 2021-06-27 10:00:04 | нулевой |
3 | 2.1 | 28 | 41 | 2021-06-27 12:33:41 | 2 |
4 | 2.1.1 | 56 | 41 | 2021-06-28 20:07:09 | 3 |
5 | 2.2 | 12 | 41 | 2021-06-29 06:22:11 | 2 |
1 | 1 | 12 | 41 | 2021-06-27 09:20:44 | нулевой |
6 | 1.1 | 28 | 41 | 2021-06-30 16:12:08 | 1 |
Ответ №1:
Возможно, есть какой-то лучший способ, но вы могли бы сделать это с помощью какой-то специальной маркировки/денормализации.
Например, если вы добавляете «thread_created_on» и «thread_id» в комментарии, в которых записана дата создания самого верхнего комментария, вы можете сделать тай-брейк, чтобы получить то, что вы хотите.
# RCO - reverse chronological order
# CO - chronological order
comments = session.query(Comments).order_by(
Comments.thread_created_on.desc(), #threads together, RCO
(Comments.thread_id == None).desc(), # True then False: thread comment top
Comments.created_date_time.asc()) # nested comments CO)
from datetime import datetime
from sqlalchemy import (
create_engine,
UnicodeText,
Integer,
String,
ForeignKey,
UniqueConstraint,
update,
DateTime
)
from sqlalchemy.schema import (
Table,
Column,
MetaData,
)
from sqlalchemy.sql import select
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
Base = declarative_base()
engine = create_engine("sqlite://", echo=False)
class Comments(Base):
__tablename__ = 'comment'
id = Column(Integer, primary_key=True, index=True)
text = Column(String(1000))
created_on = Column(DateTime)
parent_id = Column(Integer, ForeignKey('comment.id'), nullable=True)
thread_created_on = Column(DateTime)
thread_id = Column(Integer, ForeignKey('comment.id'), nullable=True)
Base.metadata.create_all(engine)
def created_on(d):
return datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
with Session(engine) as session:
d1 = created_on('2021-06-27 09:20:44')
thread1 = Comments(text='1', id=1, thread_created_on=d1, created_on=d1)
thread11 = Comments(text='1.1', id=6, parent_id=1, thread_id=1, thread_created_on=d1, created_on=created_on('2021-06-30 16:12:08'))
d2 = created_on('2021-06-27 10:00:04')
thread2 = Comments(text='2', id=2, thread_created_on=d2, created_on=d2)
thread21 = Comments(text='2.1', id=3, thread_id=2, parent_id=2, thread_created_on=d2, created_on=created_on('2021-06-27 12:33:41'))
thread211 = Comments(text='2.1.1', id=4, thread_id=2, parent_id=3, thread_created_on=d2, created_on=created_on('2021-06-28 20:07:09'))
thread22 = Comments(text='2.3', id=5, thread_id=2, parent_id=2, thread_created_on=d2, created_on=created_on('2021-06-29 06:22:11'))
d3 = created_on('2021-06-30 18:50:38')
thread3 = Comments(text='3', id=7, thread_created_on=d3, created_on=d3)
session.add_all([thread1, thread11, thread2, thread21, thread211, thread22, thread3])
session.commit()
from sqlalchemy import desc, nulls_first
comments = session.query(Comments).order_by(
Comments.thread_created_on.desc(), #threads together, RCO
(Comments.thread_id == None).desc(), # True then False: thread comment top
Comments.created_on.asc()) # nested comments CO)
props = ('id', 'text', 'created_on', 'parent_id', 'thread_id', 'thread_created_on')
fmt = ''.join([' s']*len(props))
print (fmt % props)
for comment in comments:
print (fmt % tuple([getattr(comment, prop) for prop in props]))
id text created_on parent_id thread_id thread_created_on
7 3 2021-06-30 18:50:38 None None 2021-06-30 18:50:38
2 2 2021-06-27 10:00:04 None None 2021-06-27 10:00:04
3 2.1 2021-06-27 12:33:41 2 2 2021-06-27 10:00:04
4 2.1.1 2021-06-28 20:07:09 3 2 2021-06-27 10:00:04
5 2.3 2021-06-29 06:22:11 2 2 2021-06-27 10:00:04
1 1 2021-06-27 09:20:44 None None 2021-06-27 09:20:44
6 1.1 2021-06-30 16:12:08 1 1 2021-06-27 09:20:44
Комментарии:
1. Этот подход имеет смысл в теории, но, опробовав его на практике, выполняется только заказ by
thread_created_on
, а не последние два, я не знаю почему.2. @JoshinRexy забыл, что я сделал полный пример, включил его сейчас
3. Действительно полезно. Так близко к тому, что мне нужно. Но скажите, если у меня есть комментарий 2.1.2 (ответ на ответы), он будет отображаться ниже 2.3 справа, а не ниже 2.1.1, так как мы сортируем дальше
created_on
?4. @joshinrexy да, я думаю, что меня смутили ваши описания, имеет ли дерево комментариев фиксированную глубину? То есть. Только темы(1,2,3 и т.д.), комментарии(1.1,2.1 и т.д.) и ответы на комментарии(1.2.1)? Это гораздо проще решить. Или у вас могут быть ответы на ответы неопределенной глубины, вроде Reddit?(т. Е. Как 1.2.1.1.1.1….)
5. Я думаю о том, чтобы иметь нефиксированную глубину, чтобы ответы могли отображаться чуть ниже родительского комментария, но при отображении на переднем конце все ответы и ответы на ответы будут иметь один уровень отступа, как в Instagram. Кроме того, не должно ли быть обычного способа сортировки вместо дублирования значений полей для
thread_created_on
иthread_id
. Что бы вы предложили?