본문 바로가기
프로그래밍/Javascript

sequelize.js left join/is null과 limit 사용시의 문제

by Loper Lee 2021. 8. 27.

sequelize.js left join/is null과 limit 사용시의 문제

최근 sequelize를 통한 ORM으로 DB를 다루는 중 불편한 문제점이 생겼다.
평소에는 일반 쿼리를 이용해 left join/is null을 통해서 결과값을 구하던 행위가 sequelize에서는 안되던것.
이유는 실로 간단했는데, 시퀄라이즈에서 limit과 join(include)를 사용시 기본적으로 limit은 서브쿼리로 취급되게 된다.
아무리 찾아봐도 이 문제를 해결할 방법이 없었는데, 결국 서브쿼리를 이용하는 방식으로 변경하여 동일한 결과값을 가져오게 되었다.

Problem

문제의 시퀄라이즈 코드는 아래와같았다.

const getItem = async () => {
  try {
    const items = await Item.findAll({
      include: [{
        model: Join, // left Join할 모델
        attribute: [],
      }],
      offset: 0,
      limit: 10,
      where: {
        '$join.id$': { [Op.is]: null },
      }
    });
    return items;
  }
  // 생략
}

offset, limit은 페이징을 위한 옵션으로 mysql에서는 limit 0, 10 과 같이 생성된다.
다만 여기서 문제점은 서브쿼리로서 생성이 되고 join은 서브쿼리 밖에서 이뤄진다는거다.
때문에 sequelize에서 자동으로 생성해 보내는 쿼리는 아래와 같아진다.

SELECT items. ... // 생략
FORM ( SELECT * FROM item LIMIT 0, 10 ) // 문제의 시작점
LEFT JOIN // 생략

어느 문서를 찾아보아도 이 서브쿼리 사용을 중지하고 LIMIT을 밖으로 뺄 방법이 존재하지 않았다 ㅡ,,ㅡ

Solve

고통받던 필자는 결국 서브쿼리를 사용한 NOT IN 을 이용하기로 한다.

const sub = `(SELECT itemId FROM join)`;
const getItem = async () => {
  try {
    const items = await Item.findAll({
      offset: 0,
      limit: 10,
      where: {
        id: { [Op.is]: Sequelize.literal(sub) }, // 해결 코드
      }
    });
    return items;
  }
  // 생략
}

본래 A테이블에만 존재하는 결과값을 구하기 위한 방법으로, LEFT JOIN/IS NULL과 SUBQUERY/NOT IN 이 존재한다.
퍼포먼스적인 차이는 아래 참고자료에서 확인하길 바란다. 아니면 실제로 생성된 쿼리를 실행계획을 통해서 확인해봐도 괜찮다.

자료 - https://stackoverflow.com/questions/2246772/whats-the-difference-between-not-exists-vs-not-in-vs-left-join-where-is-null

결론

최종적으로 문제는 해결하였고, 새롭게 알아가는 부분도 존재했었다.
언젠가는 같은 문제를 느낄 개발자 혹은 미래의 나를 위해서 해당글을 남겨놓는다.