NestJS nodejs는 관계가있는 하나의 쿼리에서 중첩 된 주석을로드합니까?


10

다음과 같은 모델이 있습니다.

User, Customer,Comment

사용자는에 댓글을 달 수 있고 Customer, 다른 사용자의 댓글에 재귀 적으로 무제한으로 댓글을 달 수 있습니다.

이 작업을 수행했지만 한 번의 답변으로 제한되며 모든 답글을 NESTED로 받고 싶습니다.

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

그러나 내가 얻는 응답은 한 수준에만 중첩됩니다.

[
    {
        "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로 바 note) :

@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있습니다.

인접 목록은 자체 참조가있는 간단한 모델입니다. 이 방법의 이점은 단순하지만, 단점은 깊은 나무를 처리 할 수 ​​없다는 것입니다.

Adjacency 목록을 사용하여 재귀적인 방법이 있지만 MySQL에서는 작동하지 않습니다.

해결책은 다른 유형의 트리를 사용하는 것입니다. 다른 가능한 나무는 다음과 같습니다.

  • 중첩 집합 : 읽기에는 매우 효율적이지만 쓰기에는 좋지 않습니다. 중첩 세트에 여러 개의 루트를 가질 수 없습니다.
  • 구체화 된 경로 (Path Enumeration이라고도 함)는 간단하고 효과적입니다.
  • 폐쇄 테이블 : 부모와 자식 사이의 관계를 별도의 테이블에 저장합니다. 읽기 및 쓰기 모두에서 효율적입니다 (구성 요소의 부모 업데이트 또는 제거는 아직 구현되지 않았습니다).
@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 값을 설정할 때 회신 작성에 대한 루트 주석의 응답에도이 값을 설정하도록 제안 할 수 있습니다 (코드에서는 쉬워야 함). 이를 통해 고객의 모든 의견을 하나의 쿼리 (답글 포함)로 가져올 수 있습니다.

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));

이 목록을 계산하는 데 더 최적화 된 방법이있을 수 있습니다. 이것이 가장 간단한 방법 중 하나입니다.

주석의 수에 따라 속도가 느려질 수 있습니다 (예를 들어 타임 스탬프 및 숫자로 결과를 제한하여 충분해야합니까?).주의하십시오. 많은 의견들 ...

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.