NestJS nodejs tải các bình luận lồng nhau trong một truy vấn có quan hệ?


10

Tôi có các mô hình sau:

User, Customer,Comment

Người dùng có thể nhận xét về một Customer, người dùng có thể trả lời nhận xét của người dùng khác, không giới hạn đệ quy.

Tôi đã làm điều này nhưng nó chỉ giới hạn ở một câu trả lời và tôi muốn nhận được tất cả các câu trả lời NESTED:

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

Tuy nhiên, phản hồi tôi nhận được chỉ được lồng trên một cấp độ:

[
    {
        "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"
            }
        ]
    }
]

Làm thế nào tôi có thể thực hiện một truy vấn để lồng tất cả chúng trong typorm?

Định nghĩa thực thể (lưu ý khách hàng đổi tên thành Khách hàng tiềm năng) :

@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
Bạn có thể thêm định nghĩa thực thể của bạn?
zenbeni

@zenbeni Đã thêm lời cảm ơn
Ben Beri

Câu trả lời:


7

Về cơ bản bạn đang sử dụng một Adjacency list Tree.

Danh sách điều chỉnh là một mô hình đơn giản với tự tham khảo. Lợi ích của phương pháp này là sự đơn giản, NHƯNG nhược điểm là bạn không thể xử lý những cây sâu với điều đó.

Có một cách đệ quy để thực hiện với danh sách Adjacency, nhưng nó không hoạt động với MySQL.

Giải pháp là sử dụng một loại cây khác. Các cây khác có thể là:

  • Nested set : Nó rất hiệu quả để đọc, nhưng xấu cho việc viết. Bạn không thể có nhiều gốc trong tập hợp lồng nhau.
  • Đường dẫn vật chất : (còn gọi là Đường dẫn liệt kê) đơn giản và hiệu quả.
  • Bảng đóng cửa : lưu trữ quan hệ giữa cha mẹ và con cái trong một bảng riêng biệt. Có hiệu quả trong cả đọc và ghi (Cập nhật hoặc xóa cha mẹ của thành phần chưa được triển khai)
@Entity()
@Tree("nested-set") // or @Tree("materialized-path") or @Tree("closure-table")
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @TreeChildren()
    children: Category[];

    @TreeParent()
    parent: Category;
}

Để tải cây, sử dụng:

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

Sau khi bạn có được một kho lưu trữ cây, bạn có thể sử dụng các chức năng tiếp theo: findTrees(), findRoots(), findDescendants(), findDescendantsTree()và các chức năng khác. Xem tài liệu để biết thêm.

Tìm hiểu thêm về các loại cây khác nhau: Mô hình cho dữ liệu phân cấp


1

Như Gabriel đã nói, các mô hình dữ liệu khác tốt hơn để làm những gì bạn muốn hiệu suất khôn ngoan. Tuy nhiên, nếu bạn không thể thay đổi thiết kế cơ sở dữ liệu, bạn có thể sử dụng các lựa chọn thay thế (ít hiệu suất hơn hoặc đẹp hơn, nhưng những gì hoạt động trong sản xuất là tất cả những vấn đề cuối cùng).

Khi bạn đặt giá trị Khách hàng tiềm năng trong Khách hàng tiềm năng của mình, tôi có thể đề nghị bạn cũng đặt giá trị này cho các phản hồi về nhận xét gốc về tạo phản hồi (nên dễ dàng trong mã). Bằng cách này, bạn có thể tìm nạp tất cả các nhận xét về khách hàng của mình trong một truy vấn (bao gồm cả các câu trả lời).

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

Tất nhiên, bạn sẽ phải chạy một lô SQL để điền các giá trị cột bị thiếu, nhưng đó là một lần duy nhất và một khi cơ sở mã của bạn được vá cũng như bạn sẽ không phải chạy bất cứ thứ gì sau đó. Và nó không thay đổi cấu trúc cơ sở dữ liệu của bạn (chỉ là cách dữ liệu được điền).

Sau đó, bạn có thể xây dựng trong nodejs toàn bộ nội dung (danh sách trả lời). Để nhận được bình luận "gốc", chỉ cần lọc theo bình luận không trả lời (không có phụ huynh). Nếu bạn chỉ muốn các nhận xét gốc từ cơ sở dữ liệu, bạn thậm chí có thể thay đổi truy vấn thành chỉ những câu hỏi này (với cha mẹ null null trong cột SQL).

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

Sau đó, bạn có thể nhận được trả lời trên rootComments và xây dựng toàn bộ danh sách đệ quy trong nút.

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

Có lẽ có nhiều cách tối ưu hơn để tính toán các danh sách này, đây là một trong những cách đơn giản nhất có thể được thực hiện.

Tùy thuộc vào số lượng bình luận, nó có thể bị chậm (ví dụ: bạn có thể giới hạn kết quả bằng dấu thời gian và số để nó đủ tốt?) Vì vậy, hãy cẩn thận, đừng lấy vũ trụ của bình luận về khách hàng tiềm năng "Justin Bieber" mà nhận được nhiều bình luận ...

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.