본문 바로가기
Coding

MongoDB aggregate lookup으로 컬렉션 조인하는 방법

by Hide­ 2019. 7. 8.
반응형

MongoDB는 NoSQL이므로 관계형 데이터베이스와 달리 조인이라는 기능이 없다. 하지만 쿼리를 질의하다보면 가끔씩 조인이 필요한 경우가 있다. 그럴 때 aggregate()라는 집계함수를 사용하면 다른 컬렉션을 조인시킬 수 있다. 


aggregate는 조인만을 위한 함수는 아니고 보다 복잡한 질의문을 구성할 때 사용하는것인데, 여기서 제공하는 기능 중 $lookup을 사용하면 타 컬렉션과 조인을 할 수 있다고 생각하면 될 것 같다. 먼저 예시를 위해 아래와 같은 도큐먼트가 존재한다고 가정한다.


posts

> db.posts.find()
{ "_id" : ObjectId("5d22de26fc60aab610f73e04"), "post_id" : 1, "user_id" : 1, "body" : "first", "is_block" : false }
{ "_id" : ObjectId("5d22de26fc60aab610f73e05"), "post_id" : 2, "user_id" : 1, "body" : "second", "is_block" : false }
{ "_id" : ObjectId("5d22de26fc60aab610f73e06"), "post_id" : 3, "user_id" : 2, "body" : "third", "is_block" : false }
{ "_id" : ObjectId("5d22de26fc60aab610f73e07"), "post_id" : 4, "user_id" : 3, "body" : "lalala", "is_block" : false }
{ "_id" : ObjectId("5d22de26fc60aab610f73e08"), "post_id" : 5, "user_id" : 4, "body" : "gogo", "is_block" : false }
{ "_id" : ObjectId("5d22de27fc60aab610f73e09"), "post_id" : 6, "user_id" : 5, "body" : "gogo", "is_block" : true }

relationships

> db.relationships.find()
{ "_id" : ObjectId("5d22dd19fc60aab610f73e02"), "user_id" : 1, "target_user_id" : 2 }
{ "_id" : ObjectId("5d22dd1cfc60aab610f73e03"), "user_id" : 1, "target_user_id" : 3 }


여기서 다음과 같은 조건에 맞는 posts를 뽑아내려고 한다.


relationships를 통해 관계되어있는 유저가 작성한 글


만약 user_id=1인 유저가 조회자라고 가정한다면, 다음과 같은 결과가 나와야 한다.


{ "_id" : ObjectId("5d22de26fc60aab610f73e06"), "post_id" : 3, "user_id" : 2, "body" : "third", "is_block" : false }
{ "_id" : ObjectId("5d22de26fc60aab610f73e07"), "post_id" : 4, "user_id" : 3, "body" : "lalala", "is_block" : false }


relationships를 보면 조회하는 유저인 user_id=1에 대해서 2, 3번의 user_id가 관계로 엮여있다. 따라서 2, 3번 유저가 작성한 글만 뽑아져나왔다. 여기서 lookup을 사용하여 아래와 같은 쿼리를 날릴 수 있다.


db.posts.aggregate([
    {
        $lookup: {
            from: 'relationships',
            let: {
                user_id: "$user_id"
            },
            pipeline: [{
                $match: {
                    $expr: {
                        $and: [
                            {$eq: ["$target_user_id", "$$user_id"]}
                        ]
                    }
                }
            }],
            as: 'postdata'
        },
    },
    {
    $match: {
        'postdata': {$ne: []}
    }
    }
]).pretty()

- from에는 조인시킬 컬렉션 명을 적는다.

- let에 파이프라인에서 사용할 필드명: 변수명을 선언한다.

- pipeline에 실제 조건을 적는다. 나같은 경우, relationships의 target_user_id와 posts의 user_id가 동일한 값만 뽑아내기위한 조건을 줬다. 참고로 말하자면, $$(달러 2개)는 조인 대상 컬렉션의 필드이고 $(달러 1개)는 현재 조회 대상 컬렉션의 필드이다.

- 뽑아낸 값을 as를 통해 담아줬다.

- 마지막으로 $match를 통해 위에서 as를 통해 담아준 값이 []이 아닌 경우 즉, 빈 값이 아닌 경우(존재하는 경우)만 출력하도록 했다.


위 쿼리를 실행하면 아래와 같은 결과를 볼 수 있다.


{
    "_id" : ObjectId("5d22de26fc60aab610f73e06"),
    "post_id" : 3,
    "user_id" : 2,
    "body" : "third",
    "is_block" : false,
    "postdata" : [
        {
            "_id" : ObjectId("5d22de2dfc60aab610f73e0a"),
            "user_id" : 1,
            "target_user_id" : 2
        }
    ]
}
{
    "_id" : ObjectId("5d22de26fc60aab610f73e07"),
    "post_id" : 4,
    "user_id" : 3,
    "body" : "lalala",
    "is_block" : false,
    "postdata" : [
        {
            "_id" : ObjectId("5d22de2efc60aab610f73e0b"),
            "user_id" : 1,
            "target_user_id" : 3
        }
    ]
}