次のモデルがあります。
User
、Customer
、Comment
ユーザーは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;
}
基本的に_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()
などを使用できます。詳細は documentation を参照してください。
さまざまなタイプのツリーの詳細: 階層データのモデル
ガブリエルが言ったように、他のデータモデルは、あなたが望むパフォーマンスを賢くするために優れています。それでもデータベースの設計を変更できない場合は、代替手段を使用できます(パフォーマンスが低下したり、見栄えがよくなりますが、本番環境で機能するのは結局のところすべてです)。
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つです。
コメントの数によっては、遅くなる可能性があります(たとえば、タイムスタンプと数で結果を制限して、十分なものにすることができますか?)。多くのコメント...