Połączenie z MongoDB (z kursu):
mongo "mongodb://cluster0-shard-00-00-jxeqq.mongodb.net:27017,cluster0-shard-00-01-jxeqq.mongodb.net:27017,cluster0-shard-00-02-jxeqq.mongodb.net:27017/aggregations?replicaSet=Cluster0-shard-0" \
--authenticationDatabase admin \
--ssl -u m121 -p aggregations --norc
Polecenia:
- Wylistowanie baz danych:
show dbs
- Wylistowanie kolekcji (aktualna db):
Cluster0-shard-0:PRIMARY> show collections
air_airlines
air_alliances
air_routes
bronze_banking
customers
employees
exoplanets
gold_banking
icecream_data
movies
nycFacilities
silver_banking
solarSystem
stocks
system.views
Pipeline:
- to composition of stages - kompozycja etapów przetwarzania
- każdy stage jest konfigurowalny do transformacji danych.
- dokumenty przepływają przez etapy jak na linii produkcyjnej.
- etapy można układać na dowolne sposoby.
Składnia agregacji
db.userColl.aggregate([
{stage1},
{stage2},
{...stageN}
], {options})
Przykład:
// simple first example
db.solarSystem.aggregate([{
"$match": {
"atmosphericComposition": {"$in": [/O2/]},
"meanTemperature": {$gte: -40, "$lte": 40}
}
}, {
"$project": {
"_id": 0,
"name": 1,
"hasMoons": {"$gt": ["$numberOfMoons", 0]}
}
}], {"allowDiskUse": true});
Aggregate operators: $match
, $project
Query operators: $in
, $gte
Operatory zawsze występują jako klucze w dokumencie zapytania. Wyrażenia zawsze występują jako wartości.
$gt
- może być operatorem (np. w $match), może być funkcją/wyrażeniem (np. w
$project)
temperature: {$gt: 10}
- tutaj jakoquery operator
hasMoons: {$gt: ["$numberOfMoons", 0]}
- tutaj jako wyrażenie
Dostęp do danych w wyrażeniach:
- ścieżka do pola:
"$fieldName"
- zmienna systemowa:
"$$UPPERCASE"
("$$CURRENT"
- bieżący dokument) - zmienna użytkownika:
"$foo"
Struktura & reguły składniowe
- Pipeline - to zawsze tablica 1 lub więcej stage'ów
- Etapy są skomponowane z jednego lub więcej operatorów agregacyjnych albo
wyrażeń
- wyrażenie może przyjmować jeden argument lub tablicę - to zależy od typu wyrażenia
Lepiej jest o nim myśleć jako o filter
niż jako o find
, chociaż używa tej
samej składni. Czyli:
konfigurujemy filtry na etapie $match
i dokumenty, które spełniają kryteria
filtrów, przechodzą dalej w przetwarzaniu pipeline'a.
-
$match
używa standardowych operatorów zapytań w MongoDB. Możemy używać dowolnych; możemy używać dopasowań bazujących na porównaniach, logice i tablicach; nie możemy używać operatora$where
. -
jeśli chcemy użyć operatora
$test
,$match
musi być pierwszym etapem w pipeline. -
jeśli
$match
jest pierwszym etapem, może wykorzystać indeksy, co przyspieszy przetwarzanie całej operacji. -
$match
nie pozwala na projekcję danych; jest ona możliwa na innym etapie.
Przykładowe zapytania:
// $match all celestial bodies, not equal to Star
db.solarSystem.aggregate([{
"$match": {"type": {"$ne": "Star"}}
}]).pretty()
// same query using find command
db.solarSystem.find({"type": {"$ne": "Star"}}).pretty();
// count the number of matching documents
db.solarSystem.count();
// using $count
db.solarSystem.aggregate([{
"$match": {"type": {"$ne": "Star"}}
}, {
"$count": "planets"
}]);
// matching on value, and removing ``_id`` from projected document
db.solarSystem.find({"name": "Earth"}, {"_id": 0});
My answer:
var pipeline = [
{
$match: {
"imdb.rating": {$gte: 7},
$and: [
{genres: {$ne: "Crime"}},
{genres: {$ne: "Horror"}}
],
rated: {$in: ["PG", "G"]},
languages: {$all: ["English", "Japanese"]},
}
}
];
Correct answer:
var pipeline = [
{
$match: {
"imdb.rating": {$gte: 7},
genres: {$nin: ["Crime", "Horror"]},
rated: {$in: ["PG", "G"]},
languages: {$all: ["English", "Japanese"]}
}
}
]
Z dokumentacji $nin
:
Syntax: { field: { $nin: [ , ... ]} }
If the field holds an array, then the $nin operator selects the documents whose field holds an array with no element equal to a value in the specified array (e.g. , , etc.).
Składnia:
{
$project: {
<specification(s)>
}
}
- Jeżeli określimy pole, które chcemy zachować, musimy też podać wszystkie inne
pola, które też mają być zachowane. Jedynym wyjątkiem jest
_id
.- np.
{$project: {fieldA: 1, fieldB: 1}}
- np.
_id
trzeba też jawnie usunąć z wyniku, jeśli jest to pożądane:{_id: 0}
- Poza prostym usuwaniem/pozostawieniem pól,
$project
pozwala definiować nowe pola. $project
może być użyty wielokrotnie w całym aggregation pipeline.$project
może być użyty do przepisywania wartości pól do nowych, lub do tworzenia nowych na podstawie istniejących wartości.
Przykłady:
// project ``name`` and remove ``_id``
db.solarSystem.aggregate([{"$project": {"_id": 0, "name": 1}}]);
// project ``name`` and ``gravity`` fields, including default ``_id``
db.solarSystem.aggregate([{"$project": {"name": 1, "gravity": 1}}]);
// using dot-notation to express the projection fields
db.solarSystem.aggregate([{
"$project": {
"_id": 0,
"name": 1,
"gravity.value": 1
}
}]);
// reassing ``gravity`` field with value from ``gravity.value`` embeded field
db.solarSystem.aggregate([{
"$project": {
"_id": 0,
"name": 1,
"gravity": "$gravity.value"
}
}]);
// creating a document new field ``surfaceGravity``
db.solarSystem.aggregate([{
"$project": {
"_id": 0,
"name": 1,
"surfaceGravity": "$gravity.value"
}
}]);
// creating a new field ``myWeight`` using expressions
db.solarSystem.aggregate([{
"$project": {
"_id": 0,
"name": 1,
"myWeight": {"$multiply": [{"$divide": ["$gravity.value", 9.8]}, 86]}
}
}]);
My answer:
var aggregation = [
{
$match: {
"imdb.rating": {$gte: 7},
$and: [
{genres: {$ne: "Crime"}},
{genres: {$ne: "Horror"}}
],
rated: {$in: ["PG", "G"]},
languages: {$all: ["English", "Japanese"]},
}
},
{
$project: {
_id: 0,
title: 1,
rated: 1
}
}
]
Correct answer:
var pipeline = [
{
$match: {
"imdb.rating": {$gte: 7},
genres: {$nin: ["Crime", "Horror"]},
rated: {$in: ["PG", "G"]},
languages: {$all: ["English", "Japanese"]}
}
},
{
$project: {_id: 0, title: 1, "rated": 1}
}
]
My answer:
db.movies.aggregate([
{
$project: {
titleWordsCount: {$size: {$split: ["$title", ' ']}}
}
},
{
$match: {
titleWordsCount: {$eq: 1}
}
}
]).itcount()
Correct answer:
db.movies.aggregate([
{
$match: {
title: {
$type: "string"
}
}
},
{
$project: {
title: {$split: ["$title", " "]},
_id: 0
}
},
{
$match: {
title: {$size: 1}
}
}
]).itcount()
$map
to operator, który potrafi zmapować tablicę. Składnia:
$map: {
input: "$writers", // wyrażenie dające w wyniku tablicę
as: "writer", // opcjonalna nazwa zmiennej - iteratora (domyślnie odwołujemy się przez $$this)
in: "$$writer" // wyrażenie mapujące element w tablicy
}
Przykład:
db.movies.aggregate([
{
$project: {
cast: 1,
writers: {
$map: {
input: "$writers",
as: "writer",
in: {
$arrayElemAt: [
{
$split: ["$$writer", " ("]
},
0
]
}
}
},
_id: 0
}
},
])
$setIntersection
- przecięcie zbiorów; parametrem są wyrażenia ewaluujące się
do tablic elementów, z których obliczana jest część wspólna. Jeżeli nie
znaleziono części wspólnej, zwracana jest pusta tablica.
Zadanie:
Let's find how many movies in our movies collection are a "labor of love",
where the same person appears in cast, directors, and writers.
My answer:
db.movies.aggregate([
{
$project: {
_id: 0,
laborsOfLove: {
$setIntersection: [
"$directors", "$cast", {
$map: {
input: "$writers",
as: "writer",
in: {
$arrayElemAt: [
{
$split: ["$$writer", " ("]
},
0
]
}
}
}
]
}
}
},
{
$match: {
laborsOfLove: {$elemMatch: {$exists: true}}
}
},
{
$count: "labors of love"
}
])
Correct answer:
db.movies.aggregate([
{
$match: {
cast: {$elemMatch: {$exists: true}},
directors: {$elemMatch: {$exists: true}},
writers: {$elemMatch: {$exists: true}}
}
},
{
$project: {
_id: 0,
cast: 1,
directors: 1,
writers: {
$map: {
input: "$writers",
as: "writer",
in: {
$arrayElemAt: [
{
$split: ["$$writer", " ("]
},
0
]
}
}
}
}
},
{
$project: {
labor_of_love: {
$gt: [
{$size: {$setIntersection: ["$cast", "$directors", "$writers"]}},
0
]
}
}
},
{
$match: {labor_of_love: true}
},
{
$count: "labors of love"
}
])
Działa nieco podobnie jak $project
z tą różnicą, że definiuje nowe pola, nie
usuwając żadnych pól w dokumencie wyjściowym.
Outputs documents in order of nearest to farthest from a specified point.
- Kolekcja może mieć tylko jeden indeks -
2dsphere
- Używając
2dsphere
, dystans jest zwracany w metrach. Starsze koordynaty są zwracane w radianach. $geoNear
musi być pierwszym stagem w aggregation pipeline.
Funkcje kursorowe: skip, limit, sort - są dostępne nie tylko w przypadku find(), ale także jako stage's w aggregation pipeline.
W przypadku find możemy używać ich w następujący sposób:
// project fields ``numberOfMoons`` and ``name``
db.solarSystem.find({}, {"_id": 0, "name": 1, "numberOfMoons": 1}).pretty();
// count the number of documents
db.solarSystem.find({}, {"_id": 0, "name": 1, "numberOfMoons": 1}).count();
// skip documents
db.solarSystem.find({}, {
"_id": 0,
"name": 1,
"numberOfMoons": 1
}).skip(5).pretty();
// limit documents
db.solarSystem.find({}, {
"_id": 0,
"name": 1,
"numberOfMoons": 1
}).limit(5).pretty();
// sort documents
db.solarSystem.find({}, {
"_id": 0,
"name": 1,
"numberOfMoons": 1
}).sort({"numberOfMoons": -1}).pretty();
Podobnie podczas agregacji używamy ich w następujący sposób:
// ``$limit`` stage
db.solarSystem.aggregate([{
"$project": {
"_id": 0,
"name": 1,
"numberOfMoons": 1
}
},
{"$limit": 5}]).pretty();
// ``skip`` stage
db.solarSystem.aggregate([{
"$project": {
"_id": 0,
"name": 1,
"numberOfMoons": 1
}
}, {
"$skip": 1
}]).pretty()
// ``$count`` stage
db.solarSystem.aggregate([{
"$match": {
"type": "Terrestrial planet"
}
}, {
"$project": {
"_id": 0,
"name": 1,
"numberOfMoons": 1
}
}, {
"$count": "terrestrial planets"
}]).pretty();
// removing ``$project`` stage since it does not interfere with our count
db.solarSystem.aggregate([{
"$match": {
"type": "Terrestrial planet"
}
}, {
"$count": "terrestrial planets"
}]).pretty();
// ``$sort`` stage
db.solarSystem.aggregate([{
"$project": {
"_id": 0,
"name": 1,
"numberOfMoons": 1
}
}, {
"$sort": {"numberOfMoons": -1}
}]).pretty();
// sorting on more than one field
db.solarSystem.aggregate([{
"$project": {
"_id": 0,
"name": 1,
"hasMagneticField": 1,
"numberOfMoons": 1
}
}, {
"$sort": {"hasMagneticField": -1, "numberOfMoons": -1}
}]).pretty();
// setting ``allowDiskUse`` option
db.solarSystem.aggregate([{
"$project": {
"_id": 0,
"name": 1,
"hasMagneticField": 1,
"numberOfMoons": 1
}
}, {
"$sort": {"hasMagneticField": -1, "numberOfMoons": -1}
}], {"allowDiskUse": true}).pretty();
Podsumowując:
$sort
,$skip
,$limit
i$count
są stage'ami odpowiadającymi metodom kursorowym.$sort
może wykorzystać indeksy, jeśli jest wczesnym etapem w aggregation pipeline.- domyślnie
$sort
może używać do 100MB; ustawienie opcjiallowDiskUse: true
pozwala na sortowanie większych zbiorów (wykorzystując do tego pamięć trwałą).
Składnia: {$sample: {size: N}}
Wybiera losowo zbiór dokumentów z kolekcji na dwa sposoby:
- Z użyciem pseudolosowego kursora, jeżeli spełnione są warunki:
- N nie przekracza 5% wszystkich dokumentów w kolekcji;
- kolekcja źródłowa ma co najmniej 100 dokumentów;
$sample
jest pierwszym stagem w aggregation pipeline;
- Bez kursora - sortując i wybierając pseudolosowo w pamięci operacyjnej.
- Podlega tym samym ograniczeniom, co stage
$sort
(może używać do 100MB).
- Podlega tym samym ograniczeniom, co stage
My answer:
db.getCollection('movies').aggregate([
{
$match: {
"tomatoes.viewer.rating": {$gte: 3},
countries: {$eq: "USA"},
cast: {$elemMatch: {$exists: true}}
}
},
{
$addFields: {
num_favs: {
$size: {
$setIntersection: [
"$cast", [
"Sandra Bullock",
"Tom Hanks",
"Julia Roberts",
"Kevin Spacey",
"George Clooney"
]
]
}
}
}
},
{
$sort: {
num_favs: -1,
"tomatoes.viewer.rating": -1,
title: -1
}
},
{
$skip: 24
},
{
$limit: 1
}
])
Correct answer:
var favorites = [
"Sandra Bullock",
"Tom Hanks",
"Julia Roberts",
"Kevin Spacey",
"George Clooney"]
db.movies.aggregate([
{
$match: {
"tomatoes.viewer.rating": {$gte: 3},
countries: "USA",
cast: {
$in: favorites
}
}
},
{
$project: {
_id: 0,
title: 1,
"tomatoes.viewer.rating": 1,
num_favs: {
$size: {
$setIntersection: [
"$cast",
favorites
]
}
}
}
},
{
$sort: {num_favs: -1, "tomatoes.viewer.rating": -1, title: -1}
},
{
$skip: 24
},
{
$limit: 1
}
])
Z dokumentacji $in
:
{ field: { $in: [value1, value2, ... valueN ] } }
The $in operator selects the documents where the value of a field equals any value in the specified array. To specify an $in expression, use the following prototype:
If the field holds an array, then the $in operator selects the documents whose field holds an array that contains at least one element that matches a value in the specified array (for example, value1, value2, and so on).
My answer:
var votes_max = 1521105,
votes_min = 5,
min = 1,
max = 10;
// scaledVotes = min + (max - min) * (votes - votes_min) / (votes_max - votes_min)
db.movies.aggregate([
{
$match: {
languages: "English",
"imdb.rating": {$gte: 1},
"imdb.votes": {$gte: 1},
released: {$gte: ISODate("1990-01-01T00:00:00.000Z")}
},
}, {
$addFields: {
scaled_votes: {
$add: [
min,
{
$multiply: [
(max - min),
{
$divide: [
{$subtract: ["$imdb.votes", votes_min]},
(votes_max - votes_min)
]
}
]
}
]
}
}
},
{
$addFields: {
normalized_rating: {
$avg: [
"$scaled_votes",
"$imdb.rating"
]
}
}
},
{
$sort: {
normalized_rating: 1
}
},
{
$limit: 1
},
{
$project: {
_id: 0,
title: 1
}
}
])
Correct answer:
db.movies.aggregate([
{
$match: {
year: {$gte: 1990},
languages: {$in: ["English"]},
"imdb.votes": {$gte: 1},
"imdb.rating": {$gte: 1}
}
},
{
$project: {
_id: 0,
title: 1,
"imdb.rating": 1,
"imdb.votes": 1,
normalized_rating: {
$avg: [
"$imdb.rating",
{
$add: [
1,
{
$multiply: [
9,
{
$divide: [
{$subtract: ["$imdb.votes", 5]},
{$subtract: [1521105, 5]}
]
}
]
}
]
}
]
}
}
},
{$sort: {normalized_rating: 1}},
{$limit: 1}
])
Grupuje dokumenty po podanym _id
. Składnia:
{
$group:
{
_id: <expression>, // Group By Expression
<field1>: { <accumulator1> : <expression1> },
...
}
}
Z dokumentacji:
_id: Required. If you specify an _id value of null, or any other constant value, the $group stage calculates accumulated values for all the input documents as a whole. See example of Group by Null.
field: Optional. Computed using the accumulator operators.
Warto zapamiętać:
- _id może być wyrażeniem, nie tylko polem, po którym grupujemy dokumenty
- możemy uzywać dowolnych wyrażeń akumulacyjnych wewnątrz
$group
- może zaistnieć potrzeba przygotowania danych (bo np. są nulle)
Przykład 1:
db.movies.aggregate([
{
$group: {
_id: "$year",
numOfDocs: {$sum: 1}
}
},
{
$sort: {numOfDocs: -1}
}
])
Przykład 2:
db.movies.aggregate([
{
$match: {
metacritic: {$gt: 0}
}
},
{
$group: {
_id: {
numDirectors: {
$cond: {
if: {$isArray: "$directors"},
then: {$size: "$directors"},
else: 0
}
}
},
numFilms: {$sum: 1},
avgMetacritics: {$avg: "$metacritic"}
}
},
{
$sort: {"id.numDirectors": -1}
}
])
Z dokumentacji $cond
:
Syntax:
{ $cond: { if: , then: , else: } }
{ $cond: [ , , ] }
Evaluates a boolean expression to return one of the two specified return expressions. If the evaluates to true, then $cond evaluates and returns the value of the expression. Otherwise, $cond evaluates and returns the value of the expression.
Wyrażenia akumulacyjne w $project
operują na tablicach tylko w obrębie
bieżącego (pojedynczego) dokumentu, nie przenoszą w żaden sposób wartości ze
wszystkich dokumentów.
db.getCollection('icecream_data').aggregate([
{
$project: {
_id: 0,
max_high: {
$reduce: {
input: "$trends",
initialValue: -Infinity,
in: {
$cond: [
{$gt: ["$$this.avg_high_tmp", "$$value"]},
"$$this.avg_high_tmp",
"$$value"
]
}
}
}
}
}
])
Z dokumentacji $reduce
:
Applies an expression to each element in an array and combines them into a single value. Składnia:
{
$reduce: {
input: <array>, //
initialValue: <expression>,
in: <expression>
}
}
- input - wyrażenie ewaluujące się do tablicy; jeśli null -> $reduce zwróci null
- initialValue - wartość początkowoa akumulatora, nadawana przed pierwszym odpaleniem in
- in - wyrażenie, liczące nową wartość akumulatora; dostępne zmienne to:
$$this
- aktualnie przetwarzany element tablicy$$value
- dotychczasowa wartość akumulatora
Wyznacza największą wartość z tablicy, np.
db.getCollection('icecream_data').aggregate([
{
$project: {
_id: 0,
max_high: {
$max: "$trends.avg_high_tmp"
}
}
}
])
Wyznacza najmniejszą wartość z tablicy, np.
db.getCollection('icecream_data').aggregate([
{
$project: {
_id: 0,
min_low: {
$min: "$trends.avg_low_tmp"
}
}
}
])
Średnia oraz odchylenie standardowe populacji:
db.getCollection('icecream_data').aggregate([
{
$project: {
_id: 0,
average_cpi: {$avg: "$trends.icecream_cpi"},
cpi_deviation: {$stdDevPop: "$trends.icecream_cpi"}
}
}
])
Sumuje wyrażenia dla każdego elementu z tablicy:
db.getCollection('icecream_data').aggregate([
{
$project: {
_id: 0,
"yearly_sales (millions)": {
$sum: "$trends.icecream_sales_in_millions"
}
}
}
])
Zapamiętać, że:
- Dostępne wyrażenia akumulacyjne:
$sum
,$avg
,$max
,$min
,$stdDevPop
,$stdDevSamp
- Wyrażenia nie dzielą pamięci pomiędzy dokumentami
- Czasem trzeba użyć
$map
lub$reduce
przy bardziej skomplikowanych obliczeniach.
Moje rozwiązanie:
db.getCollection('movies').aggregate([
{
$match: {
awards: {$regex: /Won\ [\d]+\ Oscars?/},
"imdb.rating": {$gt: 0}
}
},
{
$group: {
_id: null,
highest_rating: {$max: "$imdb.rating"},
lowest_rating: {$min: "$imdb.rating"},
average_rating: {$avg: "$imdb.rating"},
deviation: {$stdDevSamp: "$imdb.rating"}
}
},
{$project: {_id: 0}}
])
Wariant 1 (short form):
{ $unwind: <field path> }
Z dokumentacji:
You can pass the array field path to $unwind. When using this syntax, $unwind does not output a document if the field value is null, missing, or an empty array. Przykład:
db.getCollection('movies').aggregate([
{
$match: {
"imdb.rating": {$gt: 0},
year: {$gte: 2010, $lte: 2015},
runtime: {$gte: 90}
}
},
{
$unwind: "$genres"
},
{
$group: {
_id: {
year: "$year",
genre: "$genres"
},
average_rating: {$avg: "$imdb.rating"}
}
},
{
$sort: {
"_id.year": -1,
average_rating: -1
}
},
{
$group: {
_id: "$_id.year",
genre: {$first: "$_id.genre"},
average_rating: {$first: "$average_rating"}
}
},
{
$sort: {
_id: -1
}
}
])
Wariant 2 (long form):
$unwind: {
path: <field path>,
includeArrayIndex: <string>,
preserveNulllAndEmptyArrays: <boolean>
}
Warto zapamiętać:
$unwind
działa tylko na wartościach tablicowych.- Są dwie formy: długa (obiekt) i krótka (nazwa pola).
- Używanie
$unwind
na dużych kolekcjach z dużymi dokumentami może prowadzić do problemów z wydajnością.
Moje rozwiązanie:
db.getCollection('movies').aggregate([
{
$unwind: "$cast"
},
{
$group: {
_id: "$cast",
numFilms: {$sum: 1},
average: {$avg: "$imdb.rating"}
}
},
{
$project: {
_id: 1,
numFilms: 1,
average: {$trunc: ["$average", 1]}
}
}
])
db.getCollection('movies').aggregate([
{
$unwind: "$cast"
},
{
$addFields: {
isEnglish: {
$size: {
$setIntersection: [
{
$cond: [{
$isArray: "$languages",
}, "$languages", []]
}, ["English"]
]
}
}
}
},
{
$group: {
_id: "$cast",
numOfFilms: {$sum: 1},
numOfEnglishFilms: {$sum: "$isEnglish"},
average: {$avg: "$imdb.rating"}
}
},
{
$sort: {
numOfEnglishFilms: -1
}
},
{
$project: {
_id: 1,
numOfFilms: 1,
average: {$trunc: ["$average", 1]}
}
}
])
Correct answer:
db.movies.aggregate([
{
$match: {
languages: "English"
}
},
{
$project: {_id: 0, cast: 1, "imdb.rating": 1}
},
{
$unwind: "$cast"
},
{
$group: {
_id: "$cast",
numFilms: {$sum: 1},
average: {$avg: "$imdb.rating"}
}
},
{
$project: {
numFilms: 1,
average: {
$divide: [{$trunc: {$multiply: ["$average", 10]}}, 10]
}
}
},
{
$sort: {numFilms: -1}
},
{
$limit: 1
}
])
Tworzy połączenie typu left-outer join między kolekcjami. Składnia:
$lookup: {
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
from
- kolekcją, którą dołączamy
localField
- może być pojedynczą wartością lub tablicą,
foreignField
- może być pojedynczą wartością lub tablicą.
$lookup
posługuje się operatorem === (strict equality comparison) do
porównywania dokumentów.
Przykład:
db.getCollection('air_alliances').aggregate([
{
$lookup: {
from: "air_airlines",
localField: "airlines",
foreignField: "name",
as: "airlines"
}
}
])
Zapamiętać, że:
- kolekcja
from
nie może być sharded (podzielona między węzły w klastrze), - kolekcja
from
musi być w tej samej bazie danych, - Wartości w
localField
iforeignField
są dopasowywane wg równości, as
może być dowolną nazwą, ale jeśli w dokumentach źródłowych istnieje pole o takiej nazwie, to zostanie ono nadpisane tablicą wynikową.
Moje rozwiązanie:
db.getCollection('air_routes').aggregate([
{
$match: {
airplane: /747|380/
}
},
{
$lookup: {
from: "air_alliances",
localField: "airline.name",
foreignField: "airlines",
as: "alliances"
}
},
{$unwind: "$alliances"},
{
$project: {
airplane: 1,
alliance: "$alliances.name"
}
},
{
$group: {
_id: "$alliance",
total: {$sum: 1}
}
},
{
$sort: {
total: -1
}
}
])
db.getCollection('air_alliances').aggregate([
{
$lookup: {
from: "air_routes",
localField: "airlines",
foreignField: "airline.name",
as: "routes"
}
},
{
$unwind: "$routes"
},
{
$project: {
name: 1,
airplane: "$routes.airplane"
}
},
{
$match: {
airplane: /747|380/
}
},
{
$group: {
_id: "$name",
numRoutes: {$sum: 1}
}
},
{
$sort: {
numRoutes: -1
}
}
])
Umożliwia rekursywne oepracje na dokumentach.
Składnia:
$graphLookup: {
from: <lookup table>,
startWith: <expression for value to start from>,
connectFromField: <field name to connect from>,
connectToField: <field name to connect to>,
as: <field name for result array>,
maxDepth: <number of iterations to perform>,
depthField: <field name for number of iterations to reach this node>,
restrictSearchWithMatch: <match condition to apply to lookup>
}
Przykład:
Hierarchia w dół (podwładni):
db.getCollection('parent_reference').aggregate([
{
$match: {
name: "Eliot",
}
},
{
$graphLookup: {
from: "parent_reference",
startWith: "$_id",
connectFromField: "_id",
connectToField: "reports_to",
as: "all_reports"
}
}
])
Hierarchia w górę (szefowie):
db.getCollection('parent_reference').aggregate([
{
$match: {
name: "Shannon",
}
},
{
$graphLookup: {
from: "parent_reference",
startWith: "$reports_to",
connectFromField: "reports_to",
connectToField: "_id",
as: "bosses"
}
}
])
db.getCollection('child_reference').aggregate([
{
$match: {
name: "Dev",
}
},
{
$graphLookup: {
from: "child_reference",
startWith: "$direct_reports",
connectFromField: "direct_reports",
connectToField: "name",
as: "all_reports"
}
}
])
Parametr maxDepth
- poziom rekurencji; 0 - tylko wartość
początkowa (startWith
). Parametr depthField
- pole w wyniku, do którego
wpisany jest poziom zagnieżdżenia rekurencyjneg (long). `
db.getCollection('child_reference').aggregate([
{
$match: {
name: "Dev",
}
},
{
$graphLookup: {
from: "child_reference",
startWith: "$direct_reports",
connectFromField: "direct_reports",
connectToField: "name",
as: "till_2_level_reports",
maxDepth: 0,
depthField: "level"
}
}
])
Przykład:
db.airlines.aggregate([
{$match: {name: "TAP Portugal"}},
{
$graphLookup: {
from: "air_routes",
as: "chain",
startWith: "$base",
connectFromField: "src_airport",
connectToField: "dst_airport",
maxDepth: 1
}
}
])