HOWTO · MongoDB

MongoDB의 조회 연산자에서 파이프라인 사용

이 튜토리얼은 MongoDB의 조회 연산자에서 파이프라인을 사용하는 방법을 보여줍니다.

이 페이지의 내용

MongoDB의 $lookup 단계 안에 파이프라인을 사용하면 shopId 값이 같고 collection1 문서에 shopPosId 필드가 없는 경우 collection2의 문서를 collection1과 조인할 수 있습니다. $lookup은 왼쪽 외부 조인이므로 collection2의 입력 문서는 모두 결과에 남고, 일치하는 문서가 없는 행의 shopDescription 배열은 비어 있습니다. 조인된 문서가 있는 행만 원한다면 뒤에 $match 단계를 추가하세요.

집계 파이프라인이란

집계 파이프라인은 문서를 순서가 정해진 단계들로 처리합니다. 각 단계는 입력을 필터링하거나 형태를 바꾼 다음 그 결과를 다음 단계로 전달할 수 있습니다. 이 예제에서는 $lookup이 일치하는 문서를 추가하고, 뒤에 오는 $match로 조인 결과를 필터링할 수 있습니다. MongoDB는 map-reduce와 단일 목적 집계 연산도 제공하지만, 이 조인에는 필요하지 않습니다.

자세한 내용은 MongoDB 집계 파이프라인 개요를 참조하세요.

MongoDB의 $lookup 연산자란

$lookup은 같은 데이터베이스의 다른 컬렉션에서 문서를 조인하고, 일치하는 문서를 배열 필드에 기록합니다. from 옵션은 검색할 컬렉션을 지정하고, as는 각 입력 문서에 추가될 배열의 이름을 지정합니다. 파이프라인 형식을 사용하면 조인할 문서에 조건을 적용할 수 있습니다.

MongoDB $lookup 참조 문서에서 사용할 수 있는 구문 형식을 확인할 수 있습니다. 아래 예제는 상관 파이프라인을 사용합니다. let은 입력 문서의 값을 해당 파이프라인에서 사용할 수 있게 하고, $expr은 $match가 그 값과 필드를 비교할 수 있게 합니다. 이 글의 MongoDB 셸 명령은 MongoDB 데이터베이스에서 실행할 예제입니다. 설정된 글 실행 환경은 데이터베이스 작업을 실행하지 않습니다.

MongoDB에서 조건을 지정해 조인하도록 $lookup에 파이프라인 사용하기

다음 문서로 collection1과 collection2를 만듭니다. 설정 예제는 MongoDB 셸 명령으로 작성되어 있으므로, 사용하려는 데이터베이스에서 각 문장을 mongosh로 실행하세요.

db.createCollection('collection1'); db.createCollection('collection2'); db.collection1.insertMany([{"shopId":"001","shopPosId":"001","description":"description for 001"},{"shopId":"002","description":"description for 002"},{"shopId":"003","shopPosId":"003","description":"description for 003"},{"shopId":"004","description":"description for 004"}]); db.collection2.insertMany([{"shopId":"001","shopPosId":"0078","clientUid":"474192"},{"shopId":"002","shopPosId":"0012","clientUid":"474193"},{"shopId":"003","shopPosId":"0034","clientUid":"474194"},{"shopId":"004","shopPosId":"0056","clientUid":"474195"}]);

다음 독립 실행형 Python 예제는 같은 샘플 데이터 조인 결과를 보여 줍니다. collection1에서 shopId가 일치하고 shopPosId가 없는 문서가 있으므로 매장 002와 004에 조인된 설명이 생깁니다. 이 예제는 결과 로직을 설명하며 MongoDB 쿼리는 아닙니다.

다음 예제를 실행하세요.

collection1 = [
    {"shopId": "001", "shopPosId": "001", "description": "description for 001"},
    {"shopId": "002", "description": "description for 002"},
    {"shopId": "003", "shopPosId": "003", "description": "description for 003"},
    {"shopId": "004", "description": "description for 004"},
]
collection2 = [
    {"shopId": "001", "shopPosId": "0078", "clientUid": "474192"},
    {"shopId": "002", "shopPosId": "0012", "clientUid": "474193"},
    {"shopId": "003", "shopPosId": "0034", "clientUid": "474194"},
    {"shopId": "004", "shopPosId": "0056", "clientUid": "474195"},
]

for source in collection2:
    matches = [
        item["description"]
        for item in collection1
        if item["shopId"] == source["shopId"] and "shopPosId" not in item
    ]
    print(f'{source["shopId"]}: {matches}')
001: []
002: ['description for 002']
003: []
004: ['description for 004']

MongoDB 파이프라인에서 let: {shopId: "$shopId"}를 설정하면 각 collection2 행의 ID가 조회 파이프라인으로 전달됩니다. $expr: {$eq: ["$shopId", "$$shopId"]}로 비교합니다. $$ 접두사는 let으로 정의한 변수를 가리킵니다. 이 비교를 $and 아래의 {shopPosId: {$exists: false}}와 결합하면 조인 대상인 collection1 문서에 해당 필드가 없는 경우만 선택합니다.

이에 해당하는 첫 번째 쿼리는 db.collection2.aggregate([{$lookup: {from: "collection1", let: {shopId: "$shopId"}, pipeline: [{$match: {$and: [{$expr: {$eq: ["$shopId", "$$shopId"]}}, {shopPosId: {$exists: false}}]}}], as: "shopDescription"}}])입니다. 이 쿼리는 collection2의 입력 네 개를 모두 유지합니다. 매장 001과 003에는 조건을 충족하는 collection1 문서가 없으므로 shopDescription 배열이 비어 있습니다. 매장 002와 004는 조인된 문서에 shopPosId가 없으므로 일치합니다.

일치하는 행만 반환하려면 조인된 배열에 항목이 하나 이상 있는지 확인하는 $match 단계를 추가합니다: {$match: {shopDescription: {$ne: []}}}. 이를 포함한 전체 후속 쿼리는 db.collection2.aggregate([{$lookup: {from: "collection1", let: {shopId: "$shopId"}, pipeline: [{$match: {$and: [{$expr: {$eq: ["$shopId", "$$shopId"]}}, {shopPosId: {$exists: false}}]}}], as: "shopDescription"}}, {$match: {shopDescription: {$ne: []}}}])입니다. 샘플 데이터에서는 매장 002와 004의 원본 행이 반환됩니다. 첫 번째 $lookup 단계는 왼쪽 외부 조인으로 동작하고, 두 번째 단계가 조인 배열이 빈 행을 제거합니다. Python 예제의 출력과 그 안의 레이블은 바이트 동일성 보존을 위해 영어로 유지됩니다. 실행 증거에 따르면 이 예제는 매장 002와 004의 결과를 출력합니다.