MongoDB: รวมข้อมูลจากหลาย ๆ คอลเลคชันเป็นหนึ่งเดียว ..


229

ฉันจะ (ใน MongoDB) รวมข้อมูลจากหลาย ๆ คอลเลคชันเป็นหนึ่งคอลเล็กชันได้อย่างไร

ฉันสามารถใช้แผนที่ลดขนาดและถ้าเป็นเช่นนั้นได้อย่างไร

ฉันจะซาบซึ้งอย่างมากในขณะที่ฉันเป็นสามเณร


18
คุณเพียงต้องการคัดลอกเอกสารจากคอลเลกชันที่แตกต่างกันลงในคอลเลกชันเดียวหรือไม่หรือแผนของคุณคืออะไร? คุณสามารถระบุ "รวม" ได้ไหม หากคุณต้องการคัดลอกผ่าน mongo shell a db.collection1.find().forEach(function(doc){db.collection2.save(doc)});ก็เพียงพอแล้ว โปรดระบุไดร์เวอร์ที่ใช้แล้วของคุณ (java, php, ... ) ถ้าคุณไม่ใช้เชลล์ mongo
proximus

ดังนั้นฉันจึงมีคอลเลกชัน (พูดว่าผู้ใช้) มากกว่าที่จะมีคอลเลกชันอื่น ๆ กล่าวว่าการเก็บรวบรวมสมุดที่อยู่รายชื่อคอลเลกชันหนังสือ ฯลฯ ฉันจะใช้คีย์ว่า user_id ที่รวมคอลเลกชันเหล่านี้ไว้ในคอลเล็กชันเดียวได้อย่างไร ?
user697697

ที่เกี่ยวข้อง: stackoverflow.com/q/2350495/435605
AlikElzin-kilaka

คำตอบ:


147

แม้ว่าคุณจะไม่สามารถทำตามเวลาจริงได้ แต่คุณสามารถเรียกใช้แผนที่ลดหลาย ๆ ครั้งเพื่อรวมข้อมูลเข้าด้วยกันโดยใช้ตัวเลือก "ลด" ออกใน MongoDB 1.8+ แผนที่ / ลด (ดูhttp://www.mongodb.org/ display / DOCS / MapReduce # MapReduce-Outputoptions ) คุณต้องมีคีย์ในคอลเลกชันทั้งสองที่คุณสามารถใช้เป็น _id ได้

ตัวอย่างเช่นสมมติว่าคุณมีusersคอลเล็กชันและcommentsคอลเล็กชันและคุณต้องการมีคอลเล็กชันใหม่ที่มีข้อมูลกลุ่มผู้ใช้บางส่วนสำหรับความคิดเห็นแต่ละรายการ

สมมติว่าusersคอลเล็กชันมีฟิลด์ต่อไปนี้:

  • _id
  • ชื่อจริง
  • นามสกุล
  • ประเทศ
  • เพศ
  • อายุ

แล้วcommentsคอลเลกชันมีฟิลด์ต่อไปนี้:

  • _id
  • หมายเลขผู้ใช้
  • คิดเห็น
  • สร้าง

คุณจะทำแผนที่นี้ / ลด:

var mapUsers, mapComments, reduce;
db.users_comments.remove();

// setup sample data - wouldn't actually use this in production
db.users.remove();
db.comments.remove();
db.users.save({firstName:"Rich",lastName:"S",gender:"M",country:"CA",age:"18"});
db.users.save({firstName:"Rob",lastName:"M",gender:"M",country:"US",age:"25"});
db.users.save({firstName:"Sarah",lastName:"T",gender:"F",country:"US",age:"13"});
var users = db.users.find();
db.comments.save({userId: users[0]._id, "comment": "Hey, what's up?", created: new ISODate()});
db.comments.save({userId: users[1]._id, "comment": "Not much", created: new ISODate()});
db.comments.save({userId: users[0]._id, "comment": "Cool", created: new ISODate()});
// end sample data setup

mapUsers = function() {
    var values = {
        country: this.country,
        gender: this.gender,
        age: this.age
    };
    emit(this._id, values);
};
mapComments = function() {
    var values = {
        commentId: this._id,
        comment: this.comment,
        created: this.created
    };
    emit(this.userId, values);
};
reduce = function(k, values) {
    var result = {}, commentFields = {
        "commentId": '', 
        "comment": '',
        "created": ''
    };
    values.forEach(function(value) {
        var field;
        if ("comment" in value) {
            if (!("comments" in result)) {
                result.comments = [];
            }
            result.comments.push(value);
        } else if ("comments" in value) {
            if (!("comments" in result)) {
                result.comments = [];
            }
            result.comments.push.apply(result.comments, value.comments);
        }
        for (field in value) {
            if (value.hasOwnProperty(field) && !(field in commentFields)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};
db.users.mapReduce(mapUsers, reduce, {"out": {"reduce": "users_comments"}});
db.comments.mapReduce(mapComments, reduce, {"out": {"reduce": "users_comments"}});
db.users_comments.find().pretty(); // see the resulting collection

ณ จุดนี้คุณจะมีคอลเลกชันใหม่ที่เรียกusers_commentsว่ามีข้อมูลที่ผสานและตอนนี้คุณสามารถใช้งานได้ คอลเลกชันที่ลดลงเหล่านี้มีทั้งหมด_idซึ่งเป็นกุญแจสำคัญที่คุณเปล่งในฟังก์ชั่นแผนที่ของคุณและค่าทั้งหมดนั้นเป็นวัตถุย่อยภายในvalueคีย์ - ค่าไม่ได้อยู่ในระดับสูงสุดของเอกสารที่ลดลงเหล่านี้

นี่เป็นตัวอย่างที่ค่อนข้างง่าย คุณสามารถทำซ้ำสิ่งนี้ได้ด้วยคอลเล็กชั่นอื่น ๆ อีกมากมายตามที่คุณต้องการสร้างคอลเลกชันที่ลดลงเรื่อย ๆ คุณยังสามารถทำข้อสรุปและการรวมข้อมูลในกระบวนการ คุณอาจจะกำหนดฟังก์ชั่นลดมากกว่าหนึ่งฟังก์ชั่นเป็นตรรกะสำหรับการรวมและรักษาเขตข้อมูลที่มีอยู่จะซับซ้อนมากขึ้น

คุณจะทราบว่าขณะนี้มีเอกสารหนึ่งฉบับสำหรับผู้ใช้แต่ละรายที่มีความคิดเห็นทั้งหมดของผู้ใช้นั้นอยู่ในอาร์เรย์ ถ้าเรากำลังรวมข้อมูลที่มีความสัมพันธ์แบบหนึ่งต่อหนึ่งมากกว่าหนึ่งต่อหนึ่งมันจะแบนและคุณสามารถใช้ฟังก์ชั่นลดเช่นนี้:

reduce = function(k, values) {
    var result = {};
    values.forEach(function(value) {
        var field;
        for (field in value) {
            if (value.hasOwnProperty(field)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};

หากคุณต้องการแผ่users_commentsคอลเลกชันให้เป็นเอกสารเดียวต่อความคิดเห็นให้เรียกใช้สิ่งนี้เพิ่มเติม:

var map, reduce;
map = function() {
    var debug = function(value) {
        var field;
        for (field in value) {
            print(field + ": " + value[field]);
        }
    };
    debug(this);
    var that = this;
    if ("comments" in this.value) {
        this.value.comments.forEach(function(value) {
            emit(value.commentId, {
                userId: that._id,
                country: that.value.country,
                age: that.value.age,
                comment: value.comment,
                created: value.created,
            });
        });
    }
};
reduce = function(k, values) {
    var result = {};
    values.forEach(function(value) {
        var field;
        for (field in value) {
            if (value.hasOwnProperty(field)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};
db.users_comments.mapReduce(map, reduce, {"out": "comments_with_demographics"});

เทคนิคนี้ไม่ควรดำเนินการทันที มันเหมาะสำหรับงาน cron หรือบางอย่างเช่นที่ปรับปรุงข้อมูลที่ผสานเป็นระยะ คุณอาจต้องการเรียกใช้ensureIndexคอลเลกชันใหม่เพื่อให้แน่ใจว่าคำสั่งที่คุณดำเนินการกับมันทำงานได้อย่างรวดเร็ว (โปรดทราบว่าข้อมูลของคุณยังคงอยู่ในvalueคีย์ดังนั้นหากคุณต้องจัดทำดัชนีcomments_with_demographicsในcreatedเวลาแสดงความคิดเห็นdb.comments_with_demographics.ensureIndex({"value.created": 1});


1
ฉันอาจจะไม่เคยทำเช่นนั้นในซอฟต์แวร์การผลิต แต่ก็ยังคงเป็นเทคนิคที่ยอดเยี่ยม
Dave Griffith

3
ขอบคุณเดฟ ฉันใช้เทคนิคนี้ในการสร้างตารางการส่งออกและการรายงานสำหรับไซต์ที่มีปริมาณการใช้งานสูงในช่วง 3 เดือนที่ผ่านมาโดยไม่มีปัญหา นี่เป็นอีกบทความที่อธิบายการใช้เทคนิคที่คล้ายกัน: tebros.com/2011/07/…
rmarscher

1
ขอบคุณ @rmarscher รายละเอียดเพิ่มเติมของคุณช่วยให้ฉันเข้าใจทุกอย่างได้ดียิ่งขึ้น
เริ่ม

5
ฉันควรอัปเดตคำตอบนี้ด้วยตัวอย่างโดยใช้ขั้นตอนการสรุปรวมและการดำเนินการค้นหา $ ใหม่ กล่าวถึงที่นี่จนกว่าฉันจะสามารถรวบรวมบทความที่เหมาะสม docs.mongodb.org/manual/reference/operator/aggregation/lookup
rmarscher

1
FYI สำหรับผู้ที่ต้องการติดตามสิ่งนี้อย่างรวดเร็วนี่คือสิ่งที่อยู่ในusers_commentsคอลเล็กชันหลังจากบล็อกแรกของรหัสgist.github.com/nolanamy/83d7fb6a9bf92482a1c4311ad9c78835
Nolan Amy

127

MongoDB 3.2 ในขณะนี้ช่วยให้หนึ่งในการรวมข้อมูลจากคอลเลกชันเป็นหนึ่งในหลายผ่าน$ ค้นหาเวทีรวม เป็นตัวอย่างที่ใช้งานได้จริงสมมติว่าคุณมีข้อมูลเกี่ยวกับหนังสือแบ่งออกเป็นสองกลุ่มแตกต่างกัน

การรวบรวมแรกเรียกว่าbooksมีข้อมูลต่อไปนี้:

{
    "isbn": "978-3-16-148410-0",
    "title": "Some cool book",
    "author": "John Doe"
}
{
    "isbn": "978-3-16-148999-9",
    "title": "Another awesome book",
    "author": "Jane Roe"
}

และชุดที่สองเรียกว่าbooks_selling_dataมีข้อมูลต่อไปนี้:

{
    "_id": ObjectId("56e31bcf76cdf52e541d9d26"),
    "isbn": "978-3-16-148410-0",
    "copies_sold": 12500
}
{
    "_id": ObjectId("56e31ce076cdf52e541d9d28"),
    "isbn": "978-3-16-148999-9",
    "copies_sold": 720050
}
{
    "_id": ObjectId("56e31ce076cdf52e541d9d29"),
    "isbn": "978-3-16-148999-9",
    "copies_sold": 1000
}

ในการผสานทั้งสองคอลเล็กชันเป็นเพียงเรื่องของการใช้ $ lookup ในวิธีต่อไปนี้:

db.books.aggregate([{
    $lookup: {
            from: "books_selling_data",
            localField: "isbn",
            foreignField: "isbn",
            as: "copies_sold"
        }
}])

หลังจากการรวมนี้การbooksรวบรวมจะมีลักษณะดังต่อไปนี้:

{
    "isbn": "978-3-16-148410-0",
    "title": "Some cool book",
    "author": "John Doe",
    "copies_sold": [
        {
            "_id": ObjectId("56e31bcf76cdf52e541d9d26"),
            "isbn": "978-3-16-148410-0",
            "copies_sold": 12500
        }
    ]
}
{
    "isbn": "978-3-16-148999-9",
    "title": "Another awesome book",
    "author": "Jane Roe",
    "copies_sold": [
        {
            "_id": ObjectId("56e31ce076cdf52e541d9d28"),
            "isbn": "978-3-16-148999-9",
            "copies_sold": 720050
        },
        {
            "_id": ObjectId("56e31ce076cdf52e541d9d28"),
            "isbn": "978-3-16-148999-9",
            "copies_sold": 1000
        }
    ]
}

เป็นสิ่งสำคัญที่ควรทราบบางสิ่ง:

  1. books_selling_dataไม่สามารถคัดลอกคอลเลกชัน "จาก" ในกรณีนี้ได้
  2. ฟิลด์ "เป็น" จะเป็นอาร์เรย์ตามตัวอย่างด้านบน
  3. ทั้งตัวเลือก "localField" และ "foreignField" ในขั้นตอนการค้นหา $จะถือเป็นโมฆะเพื่อวัตถุประสงค์ในการจับคู่หากไม่มีในคอลเลกชันนั้น ๆ ( เอกสารการค้นหา $มีตัวอย่างที่สมบูรณ์แบบเกี่ยวกับเรื่องนี้)

ดังนั้นโดยสรุปหากคุณต้องการรวมทั้งคอลเลกชันที่มีในกรณีนี้เขตคัดลอกแบนที่มียอดขายรวมสำเนาคุณจะต้องทำงานอีกเล็กน้อยอาจจะใช้คอลเลกชันตัวกลางที่จะแล้ว เป็น$ outสำหรับคอลเล็กชันสุดท้าย


สวัสดีคุณช่วยบอกได้ว่าอะไรจะเป็นวิธีที่ดีที่สุดในการจัดการข้อมูลเช่นนี้: ผู้ใช้ file.files และ file.chunks เป็นคอลเลกชันสามชุดฉันต้องการผู้ใช้เฉพาะกับไฟล์ที่เกี่ยวข้องทั้งหมดในการตอบสนองหรือไม่? {"name": "batMan", "email ':" bt@gmail.com "," files ": [{file1}, {file2}, {file3}, .... เป็นต้น]]
mfaisalhyder

ตัวอย่างเอกสารอย่างเป็นทางการสำหรับการแก้ปัญหาข้างต้นสามารถพบได้ที่นี่: docs.mongodb.com/manual/reference/operator/aggregation/lookup
Jakub Czaplicki

4
จริงๆแล้วคำตอบของฉันมีสามลิงก์ไปยังเอกสารอย่างเป็นทางการแล้ว แต่ขอบคุณสำหรับการสนับสนุนของคุณอยู่ดี @JakubCzaplicki
Bruno Krebs

2
ฉันอาจจะมีความผิดปกติของสมองทั้งหมด (เป็นไปได้มากที่สุด) แต่ใน$lookupทั้ง "localField" และ "foreignField" เท่ากับ "isbn" ไม่ควรใช่หรือไม่ ไม่ใช่ "_id" และ "isbn"
Dev01

13

หากไม่มีการแทรกขนาดใหญ่ลงใน mongodb เราจะวนลูปวัตถุทั้งหมดในsmall_collectionและแทรกทีละหนึ่งเข้าไปในbig_collection:

db.small_collection.find().forEach(function(obj){ 
   db.big_collection.insert(obj)
});

db.colleciton.insert ([{}, {}, {}]) แทรกยอมรับอาร์เรย์
augurone

2
นี้ทำงานที่ดีสำหรับคอลเลกชันขนาดเล็ก แต่ไม่ลืมที่จะจัดทำดัชนีโยกย้าย :)
เซบาสเตียน Lorber

12

ตัวอย่างพื้นฐานที่มีการค้นหา $

db.getCollection('users').aggregate([
    {
        $lookup: {
            from: "userinfo",
            localField: "userId",
            foreignField: "userId",
            as: "userInfoData"
        }
    },
    {
        $lookup: {
            from: "userrole",
            localField: "userId",
            foreignField: "userId",
            as: "userRoleData"
        }
    },
    { $unwind: { path: "$userInfoData", preserveNullAndEmptyArrays: true }},
    { $unwind: { path: "$userRoleData", preserveNullAndEmptyArrays: true }}
])

ที่นี่มีการใช้

 { $unwind: { path: "$userInfoData", preserveNullAndEmptyArrays: true }}, 
 { $unwind: { path: "$userRoleData", preserveNullAndEmptyArrays: true }}

แทน

{ $unwind:"$userRoleData"} 
{ $unwind:"$userRoleData"}

เนื่องจาก{$ คลาย: "$ userRoleData"}สิ่งนี้จะส่งคืนค่าว่างหรือ 0 ผลลัพธ์หากไม่พบระเบียนที่ตรงกันที่มีการค้นหา $


11

การทำสหภาพใน MongoDB ในรูปแบบ 'SQL UNION' เป็นไปได้โดยใช้การรวมตัวพร้อมกับการค้นหาในแบบสอบถามเดียว นี่คือตัวอย่างที่ฉันได้ทดสอบที่ทำงานกับ MongoDB 4.0:

// Create employees data for testing the union.
db.getCollection('employees').insert({ name: "John", type: "employee", department: "sales" });
db.getCollection('employees').insert({ name: "Martha", type: "employee", department: "accounting" });
db.getCollection('employees').insert({ name: "Amy", type: "employee", department: "warehouse" });
db.getCollection('employees').insert({ name: "Mike", type: "employee", department: "warehouse"  });

// Create freelancers data for testing the union.
db.getCollection('freelancers').insert({ name: "Stephany", type: "freelancer", department: "accounting" });
db.getCollection('freelancers').insert({ name: "Martin", type: "freelancer", department: "sales" });
db.getCollection('freelancers').insert({ name: "Doug", type: "freelancer", department: "warehouse"  });
db.getCollection('freelancers').insert({ name: "Brenda", type: "freelancer", department: "sales"  });

// Here we do a union of the employees and freelancers using a single aggregation query.
db.getCollection('freelancers').aggregate( // 1. Use any collection containing at least one document.
  [
    { $limit: 1 }, // 2. Keep only one document of the collection.
    { $project: { _id: '$$REMOVE' } }, // 3. Remove everything from the document.

    // 4. Lookup collections to union together.
    { $lookup: { from: 'employees', pipeline: [{ $match: { department: 'sales' } }], as: 'employees' } },
    { $lookup: { from: 'freelancers', pipeline: [{ $match: { department: 'sales' } }], as: 'freelancers' } },

    // 5. Union the collections together with a projection.
    { $project: { union: { $concatArrays: ["$employees", "$freelancers"] } } },

    // 6. Unwind and replace root so you end up with a result set.
    { $unwind: '$union' },
    { $replaceRoot: { newRoot: '$union' } }
  ]);

นี่คือคำอธิบายวิธีการทำงาน:

  1. ยกตัวอย่างaggregateจากการรวบรวมฐานข้อมูลใด ๆของคุณที่มีเอกสารอย่างน้อยหนึ่งรายการ หากคุณไม่สามารถรับประกันได้ว่าการรวบรวมฐานข้อมูลใด ๆ ของคุณจะไม่ว่างเปล่าคุณสามารถแก้ไขปัญหานี้ได้โดยสร้างในฐานข้อมูลของคุณบางคอลเลกชัน 'ดัมมี่' ที่มีเอกสารเปล่าฉบับเดียวอยู่ในนั้น

  2. { $limit: 1 }ทำให้ขั้นตอนแรกของท่อของคุณจะ สิ่งนี้จะตัดเอกสารทั้งหมดของการรวบรวมยกเว้นเอกสารแรก

  3. ตัดฟิลด์ทั้งหมดของเอกสารที่เหลือโดยใช้ส$projectเตจ:

    { $project: { _id: '$$REMOVE' } }
  4. ตอนนี้การรวมของคุณมีเอกสารเปล่าหนึ่งฉบับ ได้เวลาเพิ่มการค้นหาสำหรับแต่ละคอลเล็กชันที่คุณต้องการรวมเข้าด้วยกัน คุณอาจใช้pipelineข้อมูลที่จะทำกรองเฉพาะบางส่วนหรือออกlocalFieldและforeignFieldเป็นโมฆะเพื่อให้ตรงกับคอลเลกชันทั้งหมด

    { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
    { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
    { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } }
  5. ตอนนี้คุณมีการรวมที่มีเอกสารเดียวที่มี 3 อาร์เรย์ดังนี้:

    {
        Collection1: [...],
        Collection2: [...],
        Collection3: [...]
    }

    จากนั้นคุณสามารถรวมเข้าด้วยกันเป็นอาร์เรย์เดียวโดยใช้ส$projectเตจพร้อมกับ$concatArraysตัวดำเนินการรวม:

    {
      "$project" :
      {
        "Union" : { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
      }
    }
  6. ตอนนี้คุณมีการรวมที่มีเอกสารเดียวซึ่งอยู่ในอาร์เรย์ที่มีการรวมกลุ่มของคุณ สิ่งที่ต้องทำคือการเพิ่ม$unwindและ$replaceRootขั้นตอนในการแบ่งอาร์เรย์ของคุณเป็นเอกสารแยกต่างหาก:

    { $unwind: "$Union" },
    { $replaceRoot: { newRoot: "$Union" } }
  7. voila ตอนนี้คุณมีชุดผลลัพธ์ที่มีคอลเลกชันที่คุณต้องการรวมเข้าด้วยกัน จากนั้นคุณสามารถเพิ่มขั้นตอนเพิ่มเติมเพื่อกรองเพิ่มเติมเรียงลำดับใช้ข้าม () และ จำกัด () สวยทุกอย่างที่คุณต้องการ


แบบสอบถามล้มเหลวพร้อมข้อความ "$ projection ต้องการฟิลด์เอาต์พุตอย่างน้อยหนึ่งฟิลด์"
abhishek_ganta

@abhishek ถ้าคุณเข้าใจว่าเป็นเพราะคุณพยายามที่จะดึงเอาฟิลด์ทั้งหมดออกจากเอกสารเดี่ยวในขั้นตอนการฉายภาพเดียว MongoDB จะไม่ยอมให้คุณทำเช่นนี้ ในการแก้ไขปัญหานี้คุณต้องทำการฉายภาพต่อเนื่อง 2 ภาพโดยที่ภาพแรกฉายทุกอย่างยกเว้น _id และอีกภาพที่สองตัดแถบ _id ที่เหลืออยู่ออก
วิส

@abhishek ฉันได้ทำให้การสืบค้นง่ายขึ้นโดยแทนที่ระยะ $ project ในอันเดียวที่ใช้ตัวแปร '$$ REMOVE' ฉันยังได้เพิ่มตัวอย่างที่เป็นรูปธรรมที่คุณสามารถคัดลอกและวางโดยตรงในเครื่องมือทดสอบคิวรีเพื่อดูว่ามันทำงานได้หรือไม่
วิส

@sboisse โซลูชันนี้ใช้ได้กับคอลเลกชันขนาดเล็ก แต่ถ้าฉันต้องการทำเช่นนี้กับคอลเลกชันขนาดใหญ่ (100,000+ เอกสาร) ฉันพบ "ขนาดเอกสารทั้งหมดในคอลเล็กชัน ToUnion1 เกินขนาดเอกสารสูงสุด" ในเอกสารแนะนำให้วาง $ คลายโดยตรงหลังจากการค้นหา $ เพื่อหลีกเลี่ยงการสร้างเอกสารระดับกลางขนาดใหญ่ ฉันยังไม่ประสบความสำเร็จในการแก้ไขโซลูชันนี้โดยใช้วิธีการนั้น คุณพบปัญหานี้และต้องใช้วิธีการนั้นหรือไม่ ลิงก์ไปยังเอกสารที่ฉันอ้างอิงถึง: [ลิงค์] ( docs.mongodb.com/manual/core/aggregation-pipeline-optimization/ ...... )
lucky7samson

@ lucky7samson น่าเสียดายที่ปริมาณข้อมูลที่ฉันต้องจัดการมีไม่มาก ดังนั้นฉันไม่ต้องเผชิญหน้ากับปัญหาที่คุณอ้างถึง ในกรณีของฉันฉันสามารถใช้การกรองในการรวบรวมเพื่อค้นหาก่อนที่จะรวมระเบียนกับส่วนที่เหลือดังนั้นจำนวนข้อมูลที่รวมเข้าด้วยกันจึงค่อนข้างน้อย
วิส

9

ใช้การค้นหาหลาย ๆ$สำหรับการรวบรวมหลาย ๆ

แบบสอบถาม:

db.getCollection('servicelocations').aggregate([
  {
    $match: {
      serviceLocationId: {
        $in: ["36728"]
      }
    }
  },
  {
    $lookup: {
      from: "orders",
      localField: "serviceLocationId",
      foreignField: "serviceLocationId",
      as: "orders"
    }
  },
  {
    $lookup: {
      from: "timewindowtypes",
      localField: "timeWindow.timeWindowTypeId",
      foreignField: "timeWindowTypeId",
      as: "timeWindow"
    }
  },
  {
    $lookup: {
      from: "servicetimetypes",
      localField: "serviceTimeTypeId",
      foreignField: "serviceTimeTypeId",
      as: "serviceTime"
    }
  },
  {
    $unwind: "$orders"
  },
  {
    $unwind: "$serviceTime"
  },
  {
    $limit: 14
  }
])

ผลลัพธ์:

{
    "_id" : ObjectId("59c3ac4bb7799c90ebb3279b"),
    "serviceLocationId" : "36728",
    "regionId" : 1.0,
    "zoneId" : "DXBZONE1",
    "description" : "AL HALLAB REST EMIRATES MALL",
    "locationPriority" : 1.0,
    "accountTypeId" : 1.0,
    "locationType" : "SERVICELOCATION",
    "location" : {
        "makani" : "",
        "lat" : 25.119035,
        "lng" : 55.198694
    },
    "deliveryDays" : "MTWRFSU",
    "timeWindow" : [ 
        {
            "_id" : ObjectId("59c3b0a3b7799c90ebb32cde"),
            "timeWindowTypeId" : "1",
            "Description" : "MORNING",
            "timeWindow" : {
                "openTime" : "06:00",
                "closeTime" : "08:00"
            },
            "accountId" : 1.0
        }, 
        {
            "_id" : ObjectId("59c3b0a3b7799c90ebb32cdf"),
            "timeWindowTypeId" : "1",
            "Description" : "MORNING",
            "timeWindow" : {
                "openTime" : "09:00",
                "closeTime" : "10:00"
            },
            "accountId" : 1.0
        }, 
        {
            "_id" : ObjectId("59c3b0a3b7799c90ebb32ce0"),
            "timeWindowTypeId" : "1",
            "Description" : "MORNING",
            "timeWindow" : {
                "openTime" : "10:30",
                "closeTime" : "11:30"
            },
            "accountId" : 1.0
        }
    ],
    "address1" : "",
    "address2" : "",
    "phone" : "",
    "city" : "",
    "county" : "",
    "state" : "",
    "country" : "",
    "zipcode" : "",
    "imageUrl" : "",
    "contact" : {
        "name" : "",
        "email" : ""
    },
    "status" : "ACTIVE",
    "createdBy" : "",
    "updatedBy" : "",
    "updateDate" : "",
    "accountId" : 1.0,
    "serviceTimeTypeId" : "1",
    "orders" : [ 
        {
            "_id" : ObjectId("59c3b291f251c77f15790f92"),
            "orderId" : "AQ18O1704264",
            "serviceLocationId" : "36728",
            "orderNo" : "AQ18O1704264",
            "orderDate" : "18-Sep-17",
            "description" : "AQ18O1704264",
            "serviceType" : "Delivery",
            "orderSource" : "Import",
            "takenBy" : "KARIM",
            "plannedDeliveryDate" : ISODate("2017-08-26T00:00:00.000Z"),
            "plannedDeliveryTime" : "",
            "actualDeliveryDate" : "",
            "actualDeliveryTime" : "",
            "deliveredBy" : "",
            "size1" : 296.0,
            "size2" : 3573.355,
            "size3" : 240.811,
            "jobPriority" : 1.0,
            "cancelReason" : "",
            "cancelDate" : "",
            "cancelBy" : "",
            "reasonCode" : "",
            "reasonText" : "",
            "status" : "",
            "lineItems" : [ 
                {
                    "ItemId" : "BNWB020",
                    "size1" : 15.0,
                    "size2" : 78.6,
                    "size3" : 6.0
                }, 
                {
                    "ItemId" : "BNWB021",
                    "size1" : 20.0,
                    "size2" : 252.0,
                    "size3" : 11.538
                }, 
                {
                    "ItemId" : "BNWB023",
                    "size1" : 15.0,
                    "size2" : 285.0,
                    "size3" : 16.071
                }, 
                {
                    "ItemId" : "CPMW112",
                    "size1" : 3.0,
                    "size2" : 25.38,
                    "size3" : 1.731
                }, 
                {
                    "ItemId" : "MMGW001",
                    "size1" : 25.0,
                    "size2" : 464.375,
                    "size3" : 46.875
                }, 
                {
                    "ItemId" : "MMNB218",
                    "size1" : 50.0,
                    "size2" : 920.0,
                    "size3" : 60.0
                }, 
                {
                    "ItemId" : "MMNB219",
                    "size1" : 50.0,
                    "size2" : 630.0,
                    "size3" : 40.0
                }, 
                {
                    "ItemId" : "MMNB220",
                    "size1" : 50.0,
                    "size2" : 416.0,
                    "size3" : 28.846
                }, 
                {
                    "ItemId" : "MMNB270",
                    "size1" : 50.0,
                    "size2" : 262.0,
                    "size3" : 20.0
                }, 
                {
                    "ItemId" : "MMNB302",
                    "size1" : 15.0,
                    "size2" : 195.0,
                    "size3" : 6.0
                }, 
                {
                    "ItemId" : "MMNB373",
                    "size1" : 3.0,
                    "size2" : 45.0,
                    "size3" : 3.75
                }
            ],
            "accountId" : 1.0
        }, 
        {
            "_id" : ObjectId("59c3b291f251c77f15790f9d"),
            "orderId" : "AQ137O1701240",
            "serviceLocationId" : "36728",
            "orderNo" : "AQ137O1701240",
            "orderDate" : "18-Sep-17",
            "description" : "AQ137O1701240",
            "serviceType" : "Delivery",
            "orderSource" : "Import",
            "takenBy" : "KARIM",
            "plannedDeliveryDate" : ISODate("2017-08-26T00:00:00.000Z"),
            "plannedDeliveryTime" : "",
            "actualDeliveryDate" : "",
            "actualDeliveryTime" : "",
            "deliveredBy" : "",
            "size1" : 28.0,
            "size2" : 520.11,
            "size3" : 52.5,
            "jobPriority" : 1.0,
            "cancelReason" : "",
            "cancelDate" : "",
            "cancelBy" : "",
            "reasonCode" : "",
            "reasonText" : "",
            "status" : "",
            "lineItems" : [ 
                {
                    "ItemId" : "MMGW001",
                    "size1" : 25.0,
                    "size2" : 464.38,
                    "size3" : 46.875
                }, 
                {
                    "ItemId" : "MMGW001-F1",
                    "size1" : 3.0,
                    "size2" : 55.73,
                    "size3" : 5.625
                }
            ],
            "accountId" : 1.0
        }, 
        {
            "_id" : ObjectId("59c3b291f251c77f15790fd8"),
            "orderId" : "AQ110O1705036",
            "serviceLocationId" : "36728",
            "orderNo" : "AQ110O1705036",
            "orderDate" : "18-Sep-17",
            "description" : "AQ110O1705036",
            "serviceType" : "Delivery",
            "orderSource" : "Import",
            "takenBy" : "KARIM",
            "plannedDeliveryDate" : ISODate("2017-08-26T00:00:00.000Z"),
            "plannedDeliveryTime" : "",
            "actualDeliveryDate" : "",
            "actualDeliveryTime" : "",
            "deliveredBy" : "",
            "size1" : 60.0,
            "size2" : 1046.0,
            "size3" : 68.0,
            "jobPriority" : 1.0,
            "cancelReason" : "",
            "cancelDate" : "",
            "cancelBy" : "",
            "reasonCode" : "",
            "reasonText" : "",
            "status" : "",
            "lineItems" : [ 
                {
                    "ItemId" : "MMNB218",
                    "size1" : 50.0,
                    "size2" : 920.0,
                    "size3" : 60.0
                }, 
                {
                    "ItemId" : "MMNB219",
                    "size1" : 10.0,
                    "size2" : 126.0,
                    "size3" : 8.0
                }
            ],
            "accountId" : 1.0
        }
    ],
    "serviceTime" : {
        "_id" : ObjectId("59c3b07cb7799c90ebb32cdc"),
        "serviceTimeTypeId" : "1",
        "serviceTimeType" : "nohelper",
        "description" : "",
        "fixedTime" : 30.0,
        "variableTime" : 0.0,
        "accountId" : 1.0
    }
}

1

Mongorestore มีคุณสมบัตินี้ต่อท้ายสิ่งที่มีอยู่แล้วในฐานข้อมูลดังนั้นพฤติกรรมนี้สามารถใช้สำหรับการรวมสองคอลเลกชัน:

  1. ชุดสะสม mongodump1
  2. collection2.rename (collection1)
  3. mongorestore

ยังไม่ได้ลอง แต่อาจทำงานได้เร็วกว่าแผนที่ / ลดการเข้าใกล้


1

เริ่มต้นMongo 4.4เราสามารถบรรลุการเข้าร่วมนี้ภายใน$unionWithขั้นตอนการรวมโดยการรวมขั้นตอนการรวมใหม่กับตัวดำเนินการ$groupใหม่$accumulator:

// > db.users.find()
//   [{ user: 1, name: "x" }, { user: 2, name: "y" }]
// > db.books.find()
//   [{ user: 1, book: "a" }, { user: 1, book: "b" }, { user: 2, book: "c" }]
// > db.movies.find()
//   [{ user: 1, movie: "g" }, { user: 2, movie: "h" }, { user: 2, movie: "i" }]
db.users.aggregate([
  { $unionWith: "books"  },
  { $unionWith: "movies" },
  { $group: {
    _id: "$user",
    user: {
      $accumulator: {
        accumulateArgs: ["$name", "$book", "$movie"],
        init: function() { return { books: [], movies: [] } },
        accumulate: function(user, name, book, movie) {
          if (name) user.name = name;
          if (book) user.books.push(book);
          if (movie) user.movies.push(movie);
          return user;
        },
        merge: function(userV1, userV2) {
          if (userV2.name) userV1.name = userV2.name;
          userV1.books.concat(userV2.books);
          userV1.movies.concat(userV2.movies);
          return userV1;
        },
        lang: "js"
      }
    }
  }}
])
// { _id: 1, user: { books: ["a", "b"], movies: ["g"], name: "x" } }
// { _id: 2, user: { books: ["c"], movies: ["h", "i"], name: "y" } }
  • $unionWithรวมบันทึกจากการรวบรวมที่ได้รับภายในเอกสารที่มีอยู่แล้วในขั้นตอนการสรุปรวม หลังจากการรวม 2 ขั้นตอนเราจึงมีผู้ใช้หนังสือและภาพยนตร์ทั้งหมดบันทึกอยู่ในท่อ

  • จากนั้นเราจะ$groupบันทึก$userและสะสมรายการโดยใช้$accumulatorโอเปอเรเตอร์ที่อนุญาตให้มีการสะสมเอกสารแบบกำหนดเองตามที่ได้รับการจัดกลุ่ม:

    • accumulateArgsสาขาที่เรากำลังสนใจในการสะสมจะถูกกำหนดด้วย
    • init กำหนดสถานะที่จะสะสมเมื่อเราจัดกลุ่มองค์ประกอบ
    • accumulateฟังก์ชั่นที่ช่วยให้การดำเนินการกระทำที่กำหนดเองที่มีการบันทึกถูกจัดกลุ่มเพื่อที่จะสร้างรัฐสะสม ตัวอย่างเช่นหากรายการที่ถูกจัดกลุ่มมีการbookกำหนดฟิลด์เราจะอัพเดทbooksส่วนของรัฐ
    • mergeใช้เพื่อผสานสถานะภายในสองสถานะ มันใช้สำหรับการรวมที่ทำงานบนกลุ่มที่มีเศษซากหรือเมื่อการดำเนินการเกินขีด จำกัด หน่วยความจำ

เป็นไปได้ไหมที่จะดึงเอาท์พุทที่คล้ายกันออกมาสำหรับ: 4.2.6 เวอร์ชั่น
Nixit Patel

0

ใช่คุณทำได้: ใช้ฟังก์ชั่นยูทิลิตี้ที่ฉันเขียนวันนี้:

function shangMergeCol() {
  tcol= db.getCollection(arguments[0]);
  for (var i=1; i<arguments.length; i++){
    scol= db.getCollection(arguments[i]);
    scol.find().forEach(
        function (d) {
            tcol.insert(d);
        }
    )
  }
}

คุณสามารถส่งไปยังฟังก์ชันนี้ได้ไม่ว่าจะเป็นคอลเล็กชั่นใดก็ตามอันแรกจะเป็นเป้าหมายหนึ่ง คอลเลกชันที่เหลือทั้งหมดเป็นแหล่งที่จะโอนไปยังเป้าหมาย


-1

ข้อมูลโค้ด มารยาท - โพสต์หลายรายการในสแต็คล้นรวมถึงโพสต์นี้

 db.cust.drop();
 db.zip.drop();
 db.cust.insert({cust_id:1, zip_id: 101});
 db.cust.insert({cust_id:2, zip_id: 101});
 db.cust.insert({cust_id:3, zip_id: 101});
 db.cust.insert({cust_id:4, zip_id: 102});
 db.cust.insert({cust_id:5, zip_id: 102});

 db.zip.insert({zip_id:101, zip_cd:'AAA'});
 db.zip.insert({zip_id:102, zip_cd:'BBB'});
 db.zip.insert({zip_id:103, zip_cd:'CCC'});

mapCust = function() {
    var values = {
        cust_id: this.cust_id
    };
    emit(this.zip_id, values);
};

mapZip = function() {
    var values = {
    zip_cd: this.zip_cd
    };
    emit(this.zip_id, values);
};

reduceCustZip =  function(k, values) {
    var result = {};
    values.forEach(function(value) {
    var field;
        if ("cust_id" in value) {
            if (!("cust_ids" in result)) {
                result.cust_ids = [];
            }
            result.cust_ids.push(value);
        } else {
    for (field in value) {
        if (value.hasOwnProperty(field) ) {
                result[field] = value[field];
        }
         };  
       }
      });
       return result;
};


db.cust_zip.drop();
db.cust.mapReduce(mapCust, reduceCustZip, {"out": {"reduce": "cust_zip"}});
db.zip.mapReduce(mapZip, reduceCustZip, {"out": {"reduce": "cust_zip"}});
db.cust_zip.find();


mapCZ = function() {
    var that = this;
    if ("cust_ids" in this.value) {
        this.value.cust_ids.forEach(function(value) {
            emit(value.cust_id, {
                zip_id: that._id,
                zip_cd: that.value.zip_cd
            });
        });
    }
};

reduceCZ = function(k, values) {
    var result = {};
    values.forEach(function(value) {
        var field;
        for (field in value) {
            if (value.hasOwnProperty(field)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};
db.cust_zip_joined.drop();
db.cust_zip.mapReduce(mapCZ, reduceCZ, {"out": "cust_zip_joined"}); 
db.cust_zip_joined.find().pretty();


var flattenMRCollection=function(dbName,collectionName) {
    var collection=db.getSiblingDB(dbName)[collectionName];

    var i=0;
    var bulk=collection.initializeUnorderedBulkOp();
    collection.find({ value: { $exists: true } }).addOption(16).forEach(function(result) {
        print((++i));
        //collection.update({_id: result._id},result.value);

        bulk.find({_id: result._id}).replaceOne(result.value);

        if(i%1000==0)
        {
            print("Executing bulk...");
            bulk.execute();
            bulk=collection.initializeUnorderedBulkOp();
        }
    });
    bulk.execute();
};


flattenMRCollection("mydb","cust_zip_joined");
db.cust_zip_joined.find().pretty();

-2

คุณต้องทำอย่างนั้นในเลเยอร์แอปพลิเคชันของคุณ หากคุณใช้ ORM อาจใช้คำอธิบายประกอบ (หรือสิ่งที่คล้ายกัน) เพื่อดึงข้อมูลอ้างอิงที่มีอยู่ในคอลเล็กชันอื่น ๆ ฉันเพิ่งทำงานร่วมกับMorphiaเท่านั้นและ@Referenceคำอธิบายประกอบจะดึงเอนทิตีที่อ้างอิงเมื่อมีการสอบถามดังนั้นฉันจึงสามารถหลีกเลี่ยงได้ด้วยตนเองในโค้ด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.