Сортируйте родительские комментарии вместе с их дочерними комментариями в бэкэнде

#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 . Что бы вы предложили?