공식문서 짜증나서 내가 그냥 한 번에 정리하는 시퀄라이즈

김가영·2021년 1월 2일
55

Node.js

목록 보기
23/34
post-thumbnail

공식문서 짜증나서 내가 그냥 한 번에 정리하는 시퀄라이즈 공식문서1

공식문서2
공식문서3

query 관련 blog

Eager loading

한 번에 여러 모델을 가져오는 query이다.

required eager loading

required: true 옵션 : associated model 이 존재하는 객체만을 Return 하도록 강제한다.

User.findAll({
  include: {
    model: Task,
    required: true
  }
});

nested column의 변수명을 상위의 WHERE가 참고하는 법

'$nested.column' syntax 를 이용한다.

User.findAll({
  where: {
    '$Instruments.size$': { [Op.ne]: 'small' }
  },
  include: [{
    model: Tool,
    as: 'Instruments'
  }]
});

모델 여러개 join 하기

Foo.findAll({
  include: [
    {
      model: Bar,
      required: true
    },
    {
      model: Baz,
      where: /* ... */
    },
    Qux // Shorthand syntax for { model: Qux } also works here
  ]
})

soft deleted model(paranoid)

paranoid 를 쓰면 Model.destroy() 로 모델을 삭제하였을 때 db에서 바로 사라지는 것이 아니라,deleted_at field 가 추가되게 된다.

이렇게 deleted_at을 통해 db에 남아있는 객체들을 모두 가져오려면

User.findAll({
  include: [{
    model: Tool,
    as: 'Instruments',
    where: { size: { [Op.ne]: 'small' } },
    paranoid: false
  }]
});

Ordering (Sorting)

nested 가 있을 경우에는 top-level에 order 옵션을 넣어준다.
sorting 하고 싶은 model 을 배열의 가장 처음에 넣어준다.

Company.findAll({
	include: Division,
    order: [ [Division, 'name', 'ASC'] ]
})
Compnay.findAll({
	include : {model : Division, as : 'Div' },
    order : [
    	[{model: Division, as : 'Div'}, 'name'. 'DESC' ]
    ]
})

연관된 객체의 개수로 sorting 하기


    getQuestionWithManyComment: async(question_id) => {
        const answer = await Answer.findAll({
            attributes : ['id', 'question_id', 
            [sequelize.fn('count', sequelize.col('Comments.id')), 'comment_count']],
            include : [{
                model: Comment,
                attributes : []
            }],
            group : ['id'],
            order : [[sequelize.literal('comment_count'), 'DESC']]
        });
        return answer;
    }

answer 객체에 대하여 연관된 comment 의 개수로 sorting 하는 함수. 죽겠다.

pagination

// Fetch 10 instances/rows
Project.findAll({ limit: 10 });

// Skip 8 instances/rows
Project.findAll({ offset: 8 });

// Skip 5 instances and fetch the 5 after that
Project.findAll({ offset: 5, limit: 5 });

개수 가져오기

const amount = await Project.count({
  where: {
    id: {
      [Op.gt]: 25
    }
  }
});
console.log(`There are ${amount} projects with an id greater than 25`);

Op 이용하기

상황에 따라 다른 query 적용시키기

category_id 가 null일 때엔 모든 카테고리에 대한 답변을 가져오고, not null일 때에는 해당하는 category_id 값만 가져오기

const category_attr = {};
            if ( category_id ) {
                category_attr[Op.eq]= category_id;
            } else {
                category_attr[Op.not]= null;
            }
const answers = await Answer.findAll({
                where: {
                    user_id,
                    content: {
                        [Op.not]: null,
                    },
                    [Op.or]: [{'$Question.title$' : {
                        [Op.like]: `%${query}%`}},
                        {content : {
                            [Op.like]: `%${query}`
                        }}
                    ],
                    '$Question.category_id$': category_attr,
                    public_flag: public_attr,
                },
                include : {
                    model : Question,
                    attributes: [],
                },
                raw : true,
                order :[['answer_date', 'DESC']],
                limit,
                offset : (page - 1) * 10,
            });

answers 를 선언해주는 곳에서 '$Question.category_id$': category_attr 부분을 보면 된다.
$ 를 써 준 이유는 category_id 를 Answer 객체에서 바로 참조하지 못하고 Answer와 연결된 Question 객체에서 참조하여야 하기 때문에 nested model 인 Question을 이용하기 위해 사용한 것.

[Op.or] 한 parameter에 대한 여러 조건

where: {[Op.or]: [{ a: 5 }, { b: 6 }]}, 이런 식으로 써주면 a = 5 or b = 6인 객체가 반환되고
where : { someAttributes: { [Op.or]: [5, 6],}} 이렇게 이용해주면 someAttributes가 5 or 6 인 객체들이 반환된다.

이를 이용하여 복수의 user_id 들에 대한 Answer 를 한 번에 가져오려면,

  1. 일단 원하는 유저들을 배열로 가져와서, id 만 가지고 있는 객체로 만든다.
let users = await Follow.findAll({
                where: {
                    followed_id: user_id,
                },
                attributes: [['follower_id', 'id']],
                raw: true,
  1. users 객체에서 id만 가지고 와 배열로 만든다 ex : [1,2,3,4]
users = users.map(i => i.id);
  1. user_id 의 where 절에 [Op.or] 로 users 배열을 속성으로 넣어준다.
const answers = await Answer.findAll({
            where : {
                user_id : {
                    [Op.or] : users,
                },
                public_flag: true,
                content: {
                    [Op.not] : null,
                }
            },
            attributes:['id'],
            order: [['answer_date', 'DESC']],
            raw : true,
            limit: 10,
            offset: (page - 1) * 10,
        });

Primary key 추가하기

belongsToMany의 join table의 경우 primary key 가 생성되지 않는다. 직접 model 에서 id 를 추가해줘야 하는데..!

const { User } = require("./index");
const Sequelize = require('sequelize');

module.exports = (sequelize, DataTypes) => {
    return sequelize.define('RecentSearch', {

        id: {
            type: Sequelize.INTEGER,
            primaryKey: true,
            autoIncrement: true,
        },
	...
};

point 는 기존의 Datatypes.INTEGER 대신에 Sequelize.INTEGER를 이용해줘야 한다는 것.

내가 겪은 문제점

시퀄라이즈 객체의 attribute 변환이 불가.

시퀄라이즈 객체를 console 에 찍어보면

[
  Comment {
    dataValues: {
      id: 1,
      content: '잘 읽고 갑니다~',
      public_flag: false,
      user_id: 1,
      answer_id: 1,
      createdAt: 2021-01-01T09:06:53.000Z,
      updatedAt: 2021-01-01T09:27:43.000Z,
      Children: [Array]
    },
    _previousDataValues: {
      id: 1,
      content: '잘 읽고 갑니다~',
      public_flag: false,
      user_id: 1,
      answer_id: 1,
      createdAt: 2021-01-01T09:06:53.000Z,
      updatedAt: 2021-01-01T09:27:43.000Z,
      Children: [Array]
    },
    _changed: Set(0) {},
    _options: {
      isNewRecord: false,
      _schema: null,
      _schemaDelimiter: '',
      include: [Array],
      includeNames: [Array],
      includeMap: [Object],
      includeValidated: true,
      attributes: [Array],
      raw: true
    },
    isNewRecord: false,
    Children: [ [Comment] ]
  }
]

이런식으로 dataValues 외에 다른 값들이 추가적으로 많이 들어가있다. 바로 이 객체를 return 시에는 dataValues 만 자동으로 전달되기 때문에 문제가 없지만, 객체에 새로운 변수를 추가하거나 기존 변수의 값을 변경하는 것이 불가했다.

  • comment.id = 1 , comment.dataValues.id = 3 이 모두 작동하지 않았다.

solution1

comment = await Comment.findAll({ raw : true });

raw:true 옵션을 설정하면 dataValues 만 return 된다.
하지만 include 하는 테이블이 있는 경우, 모든 변수가 객체의 형태 대신 comment.Children.id 이런 방식으로 변수 이름을 가지고 나타난다.

  • nested: true 옵션을 더해주면 include 도 가능해진다. 하지만 두 개 이상의 객체를 Include 하지는 못하고 하나씩만 해준다. child 가 2개인 경우에 parent 역시 2번 등장하는 것.

solution2

const re = answer.dataValues;
re.comment = comments.map(m => m.dataValues)

직접 접근해주기.

solution3

result = result.map(el => el.get({ plain: true }));

solution2와 같은 방법. findAll 한 데이터에서 dataValues 부분만 반환된다.


계속 고생하다가 그냥
comments = comments.map(i => i.dataValues);
를 써주기로 결정.

for (parent of comments) {
	parent.Children = parent.Children.map(i => i.dataValues)};

child 의 경우 위와 같이 처리해주었다.

Where 절 추가시 객체 join의 결과로 null 이 반환되는 경우

let answer = await Answer.findOne({
                where : {
                    id : answer_id,
                },
                include : {
                    model: Comment,
                    include : {
                        model : Comment,
                        as : 'Children'
                    },
                    // where : {
                    //     parent_id : null,
                    // }
                },
                attributes: { exclude: ['createdAt', 'updatedAt'] }
            });

answer 에서 댓글과 대댓글을 불러오기 위한 쿼리문이다.
원래 Include 를 해줘도 객체가 없으면 빈 배열([])을 반환하는데, 이 경우에는 answer 객체가 존재하여도 연관된 Comment 가 없으면 null 이 됐다.

→ 아이고 where 절을 추가하면 option의 requiredtrue가 된다고 한다. 시퀄라이즈 공식 문서

profile
개발블로그

6개의 댓글

comment-user-thumbnail
2021년 1월 10일

와... 정리 너무 잘하셨네요! 정말 좋은 정보 감사합니다:)
구독하고 갑니다~~! ㅈㅇ

1개의 답글
comment-user-thumbnail
2021년 2월 2일

sequelize 공식문서는 진짜 거지같죠

답글 달기
comment-user-thumbnail
2021년 12월 9일

잘봤습니다^^

답글 달기
comment-user-thumbnail
2022년 9월 2일

공식문서 킹받았는데 잘보고갑니다

답글 달기
comment-user-thumbnail
2024년 2월 1일

좋은 정보 감사합니다

답글 달기