배열의 ObjectId에 대한 $ lookup


104

단일 ObjectId가 아닌 ObjectId의 배열 인 필드에서 $ lookup을 수행하는 구문은 무엇입니까?

주문 문서 예 :

{
  _id: ObjectId("..."),
  products: [
    ObjectId("..<Car ObjectId>.."),
    ObjectId("..<Bike ObjectId>..")
  ]
}

작동하지 않는 쿼리 :

db.orders.aggregate([
    {
       $lookup:
         {
           from: "products",
           localField: "products",
           foreignField: "_id",
           as: "productObjects"
         }
    }
])

원하는 결과

{
  _id: ObjectId("..."),
  products: [
    ObjectId("..<Car ObjectId>.."),
    ObjectId("..<Bike ObjectId>..")
  ],
  productObjects: [
    {<Car Object>},
    {<Bike Object>}
  ],
}

주문 문서의 예가 명확하지 않습니까? 제품에 대한 예제 문서를 원하십니까?
제이슨 린

SERVER-22881은 예상대로 배열이 작동하도록 추적합니다 (리터럴 값이 아님).
Asya Kamsky 2016 년

답변:


142

2017 업데이트

$ lookup은 이제 배열을 로컬 필드로 직접 사용할 수 있습니다 . $unwind더 이상 필요하지 않습니다.

이전 답변

$lookup집계 파이프 라인 단계는 배열을 직접 작동하지 않습니다. 디자인의 주요 목적은 가능한 관련 데이터에 대한 "일대 다"유형의 조인 (또는 실제로 "조회")으로서 "왼쪽 조인"을위한 것입니다. 그러나 값은 배열이 아닌 단수를위한 것입니다.

따라서이 $lookup작업 을 수행하려면 작업 을 수행하기 전에 먼저 내용을 "비정규 화"해야합니다 . 그리고 그것은 다음을 사용하는 것을 의미합니다 $unwind.

db.orders.aggregate([
    // Unwind the source
    { "$unwind": "$products" },
    // Do the lookup matching
    { "$lookup": {
       "from": "products",
       "localField": "products",
       "foreignField": "_id",
       "as": "productObjects"
    }},
    // Unwind the result arrays ( likely one or none )
    { "$unwind": "$productObjects" },
    // Group back to arrays
    { "$group": {
        "_id": "$_id",
        "products": { "$push": "$products" },
        "productObjects": { "$push": "$productObjects" }
    }}
])

$lookup일치 각 배열 구성원 결과가 있으므로, 배열 자체가 $unwind다시 $group$push최종 결과에 대한 새로운 배열.

찾을 수없는 "왼쪽 조인"일치 항목은 지정된 제품의 "productObjects"에 대한 빈 배열을 생성하므로 두 번째 $unwind가 호출 될 때 "제품"요소에 대한 문서가 무효화됩니다 .

배열에 직접 적용하는 것이 좋지만 단일 값을 가능한 많은 값에 일치시켜 현재 작동하는 방식입니다.

마찬가지로 $lookup기본적으로 아주 새로운, 그것은 현재 잘 알고있는 사람들에게 익숙 할 것으로 작동 몽구스 의는 "가난한 망 버전"과 같은 .populate()방법이있다은 제안했다. 차이점은 $lookup클라이언트 에서와는 반대로 "조인"의 "서버 측"처리 를 제공하고 일부 "성숙도" $lookup가 현재 .populate()제공 되는 것 (예 : 배열에서 직접 조회 보간)이 부족하다는 것 입니다.

이것은 실제로 SERVER-22881 개선을 위해 할당 된 문제 이므로 운이 좋으면 다음 릴리스 또는 곧 릴리스 될 것입니다.

디자인 원칙에 따라 현재 구조는 좋거나 나쁘지 않지만 "조인"을 만들 때 오버 헤드가 발생합니다. 따라서 처음에는 MongoDB의 기본 원칙이 적용됩니다. 여기서 하나의 컬렉션에 "사전 조인 된"데이터로 "살 수있는"경우 그렇게하는 것이 가장 좋습니다.

$lookup일반적인 원칙 으로 말할 수있는 다른 한 가지는 여기에서 "조인"의 의도가 여기에 표시된 것과 다른 방식으로 작동한다는 것입니다. 따라서 "상위"문서 내에 다른 문서의 "관련 ID"를 유지하는 대신 "관련 문서"가 "상위"에 대한 참조를 포함하는 경우 가장 잘 작동하는 일반적인 원칙이 있습니다.

따라서 $lookup몽구스와 같은 .populate()것이 클라이언트 측 조인을 수행 하는 방식의 반대 인 "관계 설계"로 "가장 잘 작동"한다고 할 수 있습니다 . 내 "을"idendifying으로 각 "다"대신 다음 방금 필요없이 관련 항목을 끌어 $unwind먼저 배열입니다.


감사합니다! 내 데이터가 제대로 구조화 / 정규화되지 않았 음을 나타내는 지표입니까?
Jason Lin

1
@JasonLin "좋음 / 나쁨"만큼 어렵지 않으므로 답변에 약간의 설명이 추가되었습니다. 그것은 당신에게 맞는 것에 달려 있습니다.
Blakes Seven

2
현재 구현은 다소 의도하지 않습니다. 로컬 필드의 배열에서 모든 값을 조회하는 것이 합리적입니다. 문자 그대로 배열을 사용하는 것은 타당하지 않으므로 SERVER-22881이 수정을 추적합니다.
Asya Kamsky 2016 년

@AsyaKamsky 말이 되네요. 나는 일반적으로 inquires re $lookup및 Document 유효성 검사를 초기 단계의 기능으로 취급하고 개선 할 가능성이 있습니다. 따라서 결과를 필터링하기위한 "쿼리"와 마찬가지로 어레이에서 직접 확장하는 것이 좋습니다. 둘 다 .populate()많은 사람들이 익숙한 몽구스 과정 과 훨씬 더 많이 일치 합니다. 답변 내용에 문제 링크를 직접 추가합니다.
Blakes 세븐

2
이 답변 아래의 답변에 따라 이것은 이제 구현되었으며 $lookup이제 어레이에서 직접 작동합니다.
Adam Reis


15

pipeline스테이지를 사용하여 하위 문서 배열에 대한 검사를 수행 할 수도 있습니다.

다음은 python(죄송합니다 나는 뱀 사람입니다) 를 사용한 예 입니다.

db.products.aggregate([
  { '$lookup': {
      'from': 'products',
      'let': { 'pid': '$products' },
      'pipeline': [
        { '$match': { '$expr': { '$in': ['$_id', '$$pid'] } } }
        // Add additional stages here 
      ],
      'as':'productObjects'
  }
])

여기서 잡는 것은 ObjectId array( field / prop에 _id있는 외래)의 모든 객체를 일치 시키는 것입니다 .localproducts

stage위의 주석에 표시된대로 추가 s를 사용 하여 외부 레코드를 정리하거나 프로젝션 할 수도 있습니다 .


4

$ unwind 를 사용 하면 객체 배열 대신 첫 번째 객체를 얻을 수 있습니다.

질문:

db.getCollection('vehicles').aggregate([
  {
    $match: {
      status: "AVAILABLE",
      vehicleTypeId: {
        $in: Array.from(newSet(d.vehicleTypeIds))
      }
    }
  },
  {
    $lookup: {
      from: "servicelocations",
      localField: "locationId",
      foreignField: "serviceLocationId",
      as: "locations"
    }
  },
  {
    $unwind: "$locations"
  }
]);

결과:

{
    "_id" : ObjectId("59c3983a647101ec58ddcf90"),
    "vehicleId" : "45680",
    "regionId" : 1.0,
    "vehicleTypeId" : "10TONBOX",
    "locationId" : "100",
    "description" : "Isuzu/2003-10 Ton/Box",
    "deviceId" : "",
    "earliestStart" : 36000.0,
    "latestArrival" : 54000.0,
    "status" : "AVAILABLE",
    "accountId" : 1.0,
    "locations" : {
        "_id" : ObjectId("59c3afeab7799c90ebb3291f"),
        "serviceLocationId" : "100",
        "regionId" : 1.0,
        "zoneId" : "DXBZONE1",
        "description" : "Masafi Park Al Quoz",
        "locationPriority" : 1.0,
        "accountTypeId" : 0.0,
        "locationType" : "DEPOT",
        "location" : {
            "makani" : "",
            "lat" : 25.123091,
            "lng" : 55.21082
        },
        "deliveryDays" : "MTWRFSU",
        "timeWindow" : {
            "timeWindowTypeId" : "1"
        },
        "address1" : "",
        "address2" : "",
        "phone" : "",
        "city" : "",
        "county" : "",
        "state" : "",
        "country" : "",
        "zipcode" : "",
        "imageUrl" : "",
        "contact" : {
            "name" : "",
            "email" : ""
        },
        "status" : "",
        "createdBy" : "",
        "updatedBy" : "",
        "updateDate" : "",
        "accountId" : 1.0,
        "serviceTimeTypeId" : "1"
    }
}


{
    "_id" : ObjectId("59c3983a647101ec58ddcf91"),
    "vehicleId" : "81765",
    "regionId" : 1.0,
    "vehicleTypeId" : "10TONBOX",
    "locationId" : "100",
    "description" : "Hino/2004-10 Ton/Box",
    "deviceId" : "",
    "earliestStart" : 36000.0,
    "latestArrival" : 54000.0,
    "status" : "AVAILABLE",
    "accountId" : 1.0,
    "locations" : {
        "_id" : ObjectId("59c3afeab7799c90ebb3291f"),
        "serviceLocationId" : "100",
        "regionId" : 1.0,
        "zoneId" : "DXBZONE1",
        "description" : "Masafi Park Al Quoz",
        "locationPriority" : 1.0,
        "accountTypeId" : 0.0,
        "locationType" : "DEPOT",
        "location" : {
            "makani" : "",
            "lat" : 25.123091,
            "lng" : 55.21082
        },
        "deliveryDays" : "MTWRFSU",
        "timeWindow" : {
            "timeWindowTypeId" : "1"
        },
        "address1" : "",
        "address2" : "",
        "phone" : "",
        "city" : "",
        "county" : "",
        "state" : "",
        "country" : "",
        "zipcode" : "",
        "imageUrl" : "",
        "contact" : {
            "name" : "",
            "email" : ""
        },
        "status" : "",
        "createdBy" : "",
        "updatedBy" : "",
        "updateDate" : "",
        "accountId" : 1.0,
        "serviceTimeTypeId" : "1"
    }
}

0

로 집계 $lookup이후이 $group그렇다면, 꽤 복잡하다 (그리고 경우 매체의 그)는 노드 몽구스 또는 스키마에 몇 가지 힌트와 지원 라이브러리를 사용하여, 당신은이 사용할 수있는 .populate()해당 문서를 가져 :

var mongoose = require("mongoose"),
    Schema = mongoose.Schema;

var productSchema = Schema({ ... });

var orderSchema = Schema({
  _id     : Number,
  products: [ { type: Schema.Types.ObjectId, ref: "Product" } ]
});

var Product = mongoose.model("Product", productSchema);
var Order   = mongoose.model("Order", orderSchema);

...

Order
    .find(...)
    .populate("products")
    ...

0

동의하지 않습니다. $ match 스테이지로 시작하면 $ lookup이 ID 배열로 작동하도록 만들 수 있습니다.

// replace IDs array with lookup results
db.products.aggregate([
    { $match: { products : { $exists: true } } },
    {
        $lookup: {
            from: "products",
            localField: "products",
            foreignField: "_id",
            as: "productObjects"
        }
    }
])

조회 결과를 파이프 라인으로 전달하려면 더 복잡해집니다. 그러나 다시 그렇게 할 수있는 방법이 있습니다 (이미 @ user12164에 의해 제 안됨).

// replace IDs array with lookup results passed to pipeline
db.products.aggregate([
    { $match: { products : { $exists: true } } },
    {
        $lookup: {
            from: "products",
             let: { products: "$products"},
             pipeline: [
                 { $match: { $expr: {$in: ["$_id", "$$products"] } } },
                 { $project: {_id: 0} } // suppress _id
             ],
            as: "productObjects"
        }
    }
])

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.