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 이 존재한다.
퍼포먼스적인 차이는 아래 참고자료에서 확인하길 바란다. 아니면 실제로 생성된 쿼리를 실행계획을 통해서 확인해봐도 괜찮다.
결론
최종적으로 문제는 해결하였고, 새롭게 알아가는 부분도 존재했었다.
언젠가는 같은 문제를 느낄 개발자 혹은 미래의 나를 위해서 해당글을 남겨놓는다.