MongoDB: Połącz dane z wielu kolekcji w jeden… jak?


229

Jak mogę (w MongoDB) łączyć dane z wielu kolekcji w jedną kolekcję?

Czy mogę użyć funkcji zmniejszania mapy, a jeśli tak, to w jaki sposób?

Byłbym bardzo wdzięczny za jakiś przykład, ponieważ jestem nowicjuszem.


18
Czy chcesz po prostu skopiować dokumenty z różnych kolekcji do jednej kolekcji lub jaki masz plan? Czy możesz podać „łącz”? Jeśli chcesz po prostu skopiować przez powłokę mongo, db.collection1.find().forEach(function(doc){db.collection2.save(doc)});wystarczy. Podaj używany sterownik (java, php, ...), jeśli nie korzystasz z powłoki mongo.
proximus

więc mam kolekcję (powiedzmy użytkowników) niż inne kolekcje, mówi kolekcja książek adresowych, lista kolekcji książek itp. Jak mogę w oparciu o powiedzmy klucz user_id połączyć te kolekcje w jedną kolekcję. ?
user697697,

Odpowiedzi:


147

Chociaż nie można tego robić w czasie rzeczywistym, można wielokrotnie uruchamiać funkcję zmniejszania mapy w celu scalenia danych za pomocą opcji „zmniejszania” w MongoDB 1.8+ map / zmniejsz (patrz http://www.mongodb.org/ display / DOCS / MapReduce # MapReduce-Outputoptions ). Musisz mieć klucz w obu kolekcjach, którego możesz użyć jako _id.

Załóżmy na przykład, że masz userskolekcję i commentskolekcję i chcesz mieć nową kolekcję, która zawiera informacje demograficzne użytkownika dla każdego komentarza.

Powiedzmy, że userskolekcja ma następujące pola:

  • _ID
  • Imię
  • nazwisko
  • kraj
  • płeć
  • wiek

A następnie commentskolekcja ma następujące pola:

  • _ID
  • identyfikator użytkownika
  • komentarz
  • Utworzony

Zrobiłbyś tę mapę / zmniejszyć:

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

W tym momencie będziesz mieć nową kolekcję o nazwie, users_commentsktóra zawiera scalone dane i możesz teraz z niej korzystać. Wszystkie te zredukowane kolekcje mają _idklucz, który emitowałeś w funkcjach mapy, a następnie wszystkie wartości są podkluczem wewnątrz valueklucza - wartości nie znajdują się na najwyższym poziomie tych zredukowanych dokumentów.

To jest dość prosty przykład. Możesz to powtórzyć z większą liczbą kolekcji, tak długo jak chcesz kontynuować tworzenie zmniejszonej kolekcji. Możesz także tworzyć podsumowania i agregacje danych. Prawdopodobnie zdefiniowałbyś więcej niż jedną funkcję redukującą, ponieważ logika agregacji i zachowania istniejących pól staje się bardziej złożona.

Zauważysz również, że dla każdego użytkownika jest teraz jeden dokument z wszystkimi komentarzami tego użytkownika w tablicy. Gdybyśmy scalali dane, które mają relację jeden-do-jednego, a nie jeden-do-wielu, byłoby to płaskie i można po prostu użyć funkcji redukcji w ten sposób:

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;
};

Jeśli chcesz spłaszczyć users_commentskolekcję, aby był to jeden dokument na komentarz, uruchom dodatkowo:

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"});

Ta technika zdecydowanie nie powinna być wykonywana w locie. Nadaje się do zadania cron lub czegoś takiego, co okresowo aktualizuje scalone dane. Prawdopodobnie będziesz chciał uruchomić ensureIndexnową kolekcję, aby upewnić się, że zapytania, które wykonujesz przeciwko niej, działają szybko (pamiętaj, że twoje dane są nadal w valuekluczu, więc jeśli indeksowałbyś czas comments_with_demographicskomentowania created, byłoby todb.comments_with_demographics.ensureIndex({"value.created": 1});


1
Prawdopodobnie nigdy nie zrobiłbym tego w oprogramowaniu produkcyjnym, ale wciąż jest to niesamowita, fajna technika.
Dave Griffith,

3
Dzięki, Dave. Użyłem tej techniki do generowania tabel eksportu i raportowania dla witryny o dużym ruchu w produkcji przez ostatnie 3 miesiące bez problemu. Oto kolejny artykuł opisujący podobne zastosowanie tej techniki: tebros.com/2011/07/…
rmarscher

1
Dzięki @rmarscher twoje dodatkowe dane naprawdę pomogły mi lepiej zrozumieć wszystko.
benstr

5
Powinienem zaktualizować tę odpowiedź za pomocą przykładu, używając potoku agregacji i nowej operacji wyszukiwania $. Wspominając o tym tutaj, dopóki nie uda mi się zebrać odpowiedniego opisu. docs.mongodb.org/manual/reference/operator/aggregation/lookup
rmarscher

1
Dla tych, którzy chcą szybko sprawdzić, co to robi, oto, co znajduje się w users_commentskolekcji po pierwszym bloku kodu gist.github.com/nolanamy/83d7fb6a9bf92482a1c4311ad9c78835
Nolan Amy

127

MongoDB 3.2 pozwala teraz łączyć dane z wielu kolekcji w jedną poprzez etap agregacji wyszukiwania $ . Jako praktyczny przykład, powiedzmy, że masz dane o książkach podzielone na dwie różne kolekcje.

Pierwszy zbiór, nazywany books, zawierający następujące dane:

{
    "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"
}

I druga kolekcja, nazywana books_selling_data, zawierająca następujące dane:

{
    "_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
}

Aby połączyć obie kolekcje, wystarczy po prostu użyć $ lookup w następujący sposób:

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

Po tej agregacji bookskolekcja będzie wyglądać następująco:

{
    "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
        }
    ]
}

Ważne jest, aby zwrócić uwagę na kilka rzeczy:

  1. W tym przypadku books_selling_datanie można podzielić kolekcji „z” .
  2. Pole „as” będzie tablicą, jak w powyższym przykładzie.
  3. Zarówno opcje „localField”, jak i „ForeignField” na etapie wyszukiwania $ będą traktowane jako puste dla celów dopasowania, jeśli nie istnieją w odpowiednich kolekcjach (dokumentacja $ lookup ma na to doskonały przykład).

Podsumowując, jeśli chcesz skonsolidować obie kolekcje, mając w tym przypadku płaskie pole copy_sold z całkowitą liczbą sprzedanych kopii, będziesz musiał pracować trochę więcej, prawdopodobnie używając kolekcji pośredniej, która następnie być $ się do ostatecznego odbioru.


cześć, uprzejmie możesz powiedzieć, jaki będzie zoptymalizowany sposób zarządzania takimi danymi: Użytkownik, file.files i file.chunks to trzy kolekcje, chcę konkretnego użytkownika ze wszystkimi powiązanymi plikami w odpowiedzi, czy to możliwe.? {„name”: „batMan”, „email”: „bt@gmail.com”, „files”: [{file1}, {file2}, {file3}, ... itd.]}
mfaisalhyder

Oficjalne przykłady dokumentacji dla powyższego rozwiązania można znaleźć tutaj: docs.mongodb.com/manual/reference/operator/aggregation/lookup
Jakub Czaplicki

4
Właściwie moja odpowiedź zawierała już trzy linki do oficjalnej dokumentacji. Ale i tak dziękuję za Twój wkład. @JakubCzaplicki
Bruno Krebs

2
Mogę mieć całkowitą wadę mózgu (najprawdopodobniej), ale $lookupczy wszystkie „localField” i „ForeignField” nie powinny być równe „isbn”? nie „_id” i „isbn”?
Dev01

13

Jeśli nie ma wstawiania zbiorczego do mongodb, zapętlamy wszystkie obiekty w small_collectioni wstawiamy je jeden po drugim do big_collection:

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

db.colleciton.insert ([{}, {}, {}]) Wstaw przyjmuje tablice.
sierpień

2
działa to dobrze w przypadku małych kolekcji, ale nie zapomnij migrować indeksów :)
Sebastien Lorber

12

Bardzo prosty przykład z wyszukiwaniem $.

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 }}
])

Tutaj jest używany

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

Zamiast

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

Ponieważ {$ unwind: "$ userRoleData"} zwróci pusty lub wynik 0, jeśli nie znaleziono pasującego rekordu przy wyszukiwaniu $.


11

Tworzenie związków w MongoDB w sposób „SQL UNION” jest możliwe przy użyciu agregacji wraz z odnośnikami w jednym zapytaniu. Oto przykład, który przetestowałem, który działa z 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' } }
  ]);

Oto wyjaśnienie, jak to działa:

  1. Instancję aggregateSpośród każdej kolekcji swojej bazie danych, która ma co najmniej jeden dokument w nim. Jeśli nie możesz zagwarantować, że jakakolwiek kolekcja Twojej bazy danych nie będzie pusta, możesz obejść ten problem, tworząc w bazie danych pewnego rodzaju „obojętny” zbiór zawierający pojedynczy pusty dokument, który będzie tam specjalnie do wykonywania zapytań dotyczących związków.

  2. Zrób pierwszy etap swojego rurociągu { $limit: 1 }. Spowoduje to usunięcie wszystkich dokumentów z kolekcji oprócz pierwszego.

  3. Usuń wszystkie pola z pozostałego dokumentu za pomocą $projectetapu:

    { $project: { _id: '$$REMOVE' } }
  4. Twój agregat zawiera teraz pojedynczy, pusty dokument. Czas dodać wyszukiwania dla każdej kolekcji, którą chcesz połączyć. Możesz użyć tego pipelinepola, aby wykonać określone filtrowanie, lub pozostawić localFieldi foreignFieldzerować, aby dopasować całą kolekcję.

    { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
    { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
    { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } }
  5. Masz teraz agregat zawierający pojedynczy dokument zawierający 3 tablice takie jak to:

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

    Następnie możesz scalić je razem w jedną tablicę przy użyciu stołu $projectmontażowego wraz z $concatArraysoperatorem agregacji:

    {
      "$project" :
      {
        "Union" : { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
      }
    }
  6. Masz teraz agregat zawierający pojedynczy dokument, w którym znajduje się tablica zawierająca twoją unię kolekcji. Co pozostaje do zrobienia jest dodanie $unwindi na $replaceRootscenę, aby podzielić swoją tablicę w oddzielnych dokumentach:

    { $unwind: "$Union" },
    { $replaceRoot: { newRoot: "$Union" } }
  7. Voilà. Masz teraz zestaw wyników zawierający kolekcje, które chcesz połączyć ze sobą. Następnie możesz dodać kolejne etapy, aby dalej filtrować, sortować, stosować skip () i limit (). Prawie wszystko, co chcesz.


Zapytanie kończy się niepowodzeniem z komunikatem „$ projection wymaga co najmniej jednego pola wyjściowego”.
abhishek_ganta

@abhishek Jeśli to otrzymasz, to dlatego, że próbowałeś usunąć wszystkie pola z jednego dokumentu na jednym etapie projekcji. MongoDB nie pozwoli ci tego zrobić. Aby obejść ten problem, należy wykonać 2 kolejne projekcje, w których pierwsza usuwa wszystko oprócz _id, a druga usuwa pozostałe _id.
sboisse

@abhishek Uprościłem jeszcze bardziej zapytanie, zastępując etapy projektu $ w jednym, który używa zmiennej „$$ REMOVE”. Dodałem także konkretny przykład, który możesz po prostu skopiować i wkleić bezpośrednio w testerze zapytań, aby zobaczyć, czy to działa.
sboisse

@sboisse, to rozwiązanie działa w przypadku mniejszych kolekcji, jednak jeśli chcę to zrobić w dużych kolekcjach (ponad 100 000 dokumentów), natrafiam na „Całkowity rozmiar dokumentów w collectionToUnion1 przekracza maksymalny rozmiar dokumentu”. W dokumentacji sugeruje umieszczenie $ odprężenia bezpośrednio po wyszukiwaniu $, aby uniknąć tworzenia dużych dokumentów pośrednich. Nie udało mi się zmodyfikować tego rozwiązania przy użyciu tej metody. Czy napotkałeś ten problem i musiałeś użyć tej metody? Link do dokumentów, do których się odwołuję : [link] ( docs.mongodb.com/manual/core/aggregation-pipeline-optimization/… )
lucky7samson

@ lucky7samson niestety ilość danych, z którymi miałem do czynienia, nie była aż tak duża. Więc nie musiałem stawić czoła problemowi, o którym mówisz. W moim przypadku mogłem zastosować filtrowanie kolekcji w celu wyszukiwania przed scaleniem rekordów z resztą, więc ilość danych do unii była dość mała.
sboisse

9

użyj wielu $ lookup dla wielu kolekcji w agregacji

pytanie:

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
  }
])

wynik:

{
    "_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 ma tę funkcję dołączania do wszystkiego, co jest już w bazie danych, więc to zachowanie można wykorzystać do łączenia dwóch kolekcji:

  1. kolekcja mongodump 1
  2. kolekcja2.rename (kolekcja1)
  3. sklep mongorestore

Nie próbowałem tego jeszcze, ale może działać szybciej niż podejście do mapy / zmniejszania.


1

Zaczynając Mongo 4.4, możemy osiągnąć to połączenie w ramach potoku agregacji, łącząc nowy $unionWithetap agregacji z $groupnowym $accumulatoroperatorem:

// > 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łączy rekordy z danej kolekcji w dokumentach już w potoku agregacji. Po 2 etapach związkowych mamy zatem wszystkich użytkowników, książki i nagrania filmowe w przygotowaniu.

  • Następnie $grouprejestrujemy $useri gromadzimy elementy za pomocą $accumulatoroperatora, umożliwiając niestandardowe gromadzenie dokumentów podczas ich grupowania:

    • pola, które chcemy gromadzić, są zdefiniowane accumulateArgs.
    • init określa stan, który będzie się kumulował, gdy grupujemy elementy.
    • accumulatefunkcja umożliwia wykonywanie akcja niestandardowa z rekordem są grupowane w celu zbudowania nagromadzony stan. Na przykład, jeśli element zgrupowany ma bookzdefiniowane pole, wówczas aktualizujemy booksczęść stanu.
    • mergesłuży do łączenia dwóch stanów wewnętrznych. Jest używany tylko w przypadku agregacji działających na dzielonych klastrach lub gdy operacja przekracza limity pamięci.

czy można pobrać podobne dane wyjściowe dla: wersji 4.2.6
Nixit Patel

0

Tak, możesz: skorzystaj z tej funkcji narzędzia, którą napisałem dzisiaj:

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);
        }
    )
  }
}

Możesz przekazać do tej funkcji dowolną liczbę kolekcji, pierwsza będzie docelowa. Wszystkie pozostałe kolekcje są źródłami, które należy przenieść do kolekcji docelowej.


-1

Fragment kodu. Dzięki uprzejmości wiele postów na przepełnieniu stosu, w tym ten.

 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

Musisz to zrobić w warstwie aplikacji. Jeśli używasz ORM, może użyć adnotacji (lub czegoś podobnego), aby pobrać odniesienia istniejące w innych kolekcjach. Pracowałem tylko z Morphia , a @Referenceadnotacja pobiera przywoływany byt, gdy jest pytany, więc jestem w stanie uniknąć robienia tego samemu w kodzie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.