NestJS nodejsは、関係のある1つのクエリでネストされたコメントをロードしますか?


10

次のモデルがあります。

UserCustomerComment

ユーザーはにコメントできCustomer、ユーザーは別のユーザーのコメントに再帰的に無制限に返信できます。

私はこれを実行しましたが、返信は1つだけに制限されており、すべての返信をネストします。

public async getCommentsForCustomerId(customerId: string): Promise<CustomerComment[]> {
    return this.find({where: {customer: {id: customerId}, parentComment: null}, relations: ['childComments']});
}

ただし、取得した応答は1つのレベルでのみネストされます。

[
    {
        "id": "7b5b654a-efb0-4afa-82ee-c00c38725072",
        "content": "test",
        "created_at": "2019-12-03T15:14:48.000Z",
        "updated_at": "2019-12-03T15:14:49.000Z",
        "childComments": [
            {
                "id": "7b5b654a-efb0-4afa-82ee-c00c38725073",
                "content": "test reply",
                "created_at": "2019-12-03T15:14:48.000Z",
                "updated_at": "2019-12-03T15:14:49.000Z",
                "parentCommentId": "7b5b654a-efb0-4afa-82ee-c00c38725072"
            }
        ]
    }
]

それらをすべてtypeormでネストするクエリを作成するにはどうすればよいですか?

エンティティの定義(お客様の名前がLeadに変更されていることに注意してください)

@Entity('leads_comments')
export class LeadComment {

  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ManyToOne(type => LeadComment, comment => comment.childComments, {nullable: true})
  parentComment: LeadComment;

  @OneToMany(type => LeadComment, comment => comment.parentComment)
  @JoinColumn({name: 'parentCommentId'})
  childComments: LeadComment[];

  @RelationId((comment: LeadComment) => comment.parentComment)
  parentCommentId: string;

  @ManyToOne(type => User, {cascade: true})
  user: User | string;

  @RelationId((comment: LeadComment) => comment.user, )
  userId: string;

  @ManyToOne(type => Lead, lead => lead.comments, {cascade: true})
  lead: Lead | string;

  @RelationId((comment: LeadComment) => comment.lead)
  leadId: string;

  @Column('varchar')
  content: string;

  @CreateDateColumn()
  created_at: Date;

  @UpdateDateColumn()
  updated_at: Date;
}

1
エンティティ定義を追加できますか?
zenbeni

@zenbeni追加の感謝
Ben Beri

回答:


7

基本的にを使用していAdjacency list Treeます。

隣接リストは、自己参照を持つ単純なモデルです。このアプローチの利点は単純さですが、欠点は、これでは深いツリーを処理できないことです。

隣接リストを使用して再帰的に行う方法がありますが、MySQLでは機能しません。

解決策は、別のタイプのツリーを使用することです。その他の可能なツリーは次のとおりです。

  • ネストされたセット:読み取りには非常に効率的ですが、書き込みには適していません。ネストされたセットに複数のルートを含めることはできません。
  • マテリアライズドパス:(パス列挙とも呼ばれます)はシンプルで効果的です。
  • クロージャテーブル:親と子の関係を別のテーブルに保存します。読み取りと書き込みの両方で効率的です(コンポーネントの親の更新または削除はまだ実装されていません)
@Entity()
@Tree("nested-set") // or @Tree("materialized-path") or @Tree("closure-table")
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @TreeChildren()
    children: Category[];

    @TreeParent()
    parent: Category;
}

ツリーをロードするには:

const manager = getManager();
const trees = await manager.getTreeRepository(Category).findTrees();

ツリーリポジトリを取得したら、次の機能を使用できます findTrees(), findRoots(), findDescendants(), findDescendantsTree()。詳細については、ドキュメントを参照してください。

さまざまなタイプのツリーの詳細:階層データのモデル


1

ガブリエルが言ったように、他のデータモデルは、あなたが望むパフォーマンスを賢くするために優れています。それでもデータベースの設計を変更できない場合は、代替案を使用できます(パフォーマンスが低下したり、見栄えがよくなりますが、本番環境で機能するのは結局のところすべてです)。

LeadCommentでLead値を設定する際に、返信の作成時のルートコメントへの返信にもこの値を設定することをお勧めします(コードでは簡単なはずです)。このようにして、1つのクエリ(返信を含む)で顧客に関するすべてのコメントを取得できます。

const lead = await leadRepository.findOne(id);
const comments = await commentRepository.find({lead});

もちろん、欠落している列の値を入力するためにSQLバッチを実行する必要がありますが、これは一度だけであり、コードベースにもパッチが適用されると、その後何も実行する必要がなくなります。また、データベースの構造は変更されません(データが入力される方法のみ)。

その後、nodejsですべてのもの(返信のリスト)を構築できます。「ルート」コメントを取得するには、返信ではない(親を持たない)コメントでフィルタリングするだけです。データベースのルートコメントだけが必要な場合は、クエリをこれらのコメントのみに変更することもできます(SQL列でparentComment nullを使用)。

function sortComment(c1: LeadComment , c2: LeadComment ): number {
    if (c1.created_at.getTime() > c2.created_at.getTime()) {
    return 1;
    }
    if (c1.created_at.getTime() < c2.created_at.getTime()) {
        return -1;
    }
    return 0;
}
const rootComments = comments
    .filter(c => !c.parentComment)
    .sort(sortComment);

次に、rootCommentsに対する応答を取得し、ノード全体で再帰的にリスト全体を作成できます。

function buildCommentList(currentList: LeadComment[], allComments: LeadComment[]): LeadComment[] {
    const lastComment = currentList[currentList.length - 1];
    const childComments = allComments
        .filter(c => c.parentComment?.id === lastComment.id)
        .sort(sortComment);
    if (childComments.length === 0) {
        return currentList;
    }
    const childLists = childComments.flatMap(c => buildCommentList([c], allComments));
    return [...currentList, ...childLists];
}

const listsOfComments = rootComments.map(r => buildCommentList([r], comments));

おそらくこれらのリストを計算するためのより最適化された方法があるでしょう、これは私にとって作ることができる最も単純なものの1つです。

コメントの数によっては、遅くなる可能性があります(たとえば、タイムスタンプと数で結果を制限して、十分なものにすることができますか?)。多くのコメント...

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.