У меня есть пользовательская коллекция с вложенным документом «музыка», в котором есть вложенный документ «лайки». Я хотел бы запустить поиск и найти 10 лучших пользователей, которым понравился конкретный художник, отсортированный по степени их популярности. это как набор данных структурирован
[
{
'_id' : ObjectId("507f1f77bcf86cd799439011"),
'user_name' : "John",
'music' : [
'likes' [
{'name': 'david bowie', 'strength': 50 },
{'name': 'john lennon', 'strength': 100 },
{'name': 'bob marley', 'strength': 20 },
]
]
},
{
'_id' : ObjectId("54304264e77cc5a1670cb318"),
'user_name' : "Paul",
'music' : [
'likes' [
{'name': 'david bowie', 'strength': 60 },
{'name': 'john lennon', 'strength': 70 },
{'name': 'bob marley', 'strength': 100 },
]
]
}
]
Я пытался использовать следующую агрегатную команду:
$artist = "david bowie";
$db->collection->aggregate(
array(
array( '$project' => array( 'Likes' => '$music.likes' ) ),
array( '$match' => array( 'Likes.name' => $artist ) ),
array( '$sort' => array( 'Likes.strength' => 1 ) ),
array( '$limit' => 10 )
)
);
матч действительно работает, но он сортирует только лайки, а не общие результаты.
также — есть ли способ вернуть не все элементы в документе Likes, а только тот, который связан с матчем?
вот результаты я получаю
[
{
["_id"]=> object(MongoId)#310 (1) { ["$id"]=> string(24) "507f1f77bcf86cd799439011",
["Likes"] => array(49) {
[0]=> array(2) { ["name"]=> string(11) "john lennon" ["strength"]=> float(100) },
[1]=> array(2) { ["name"]=> string(11) "david bowie" ["strength"]=> float(50) },
[2]=> array(2) { ["name"]=> string(11) "bob marley" ["strength"]=> float(20) },
...
}
},
{
["_id"]=> object(MongoId)#310 (1) { ["$id"]=> string(24) "54304264e77cc5a1670cb318",
["Likes"] => array(49) {
[0]=> array(2) { ["name"]=> string(11) "bob marley" ["strength"]=> float(100) },
[1]=> array(2) { ["name"]=> string(11) "john lennon" ["strength"]=> float(70) },
[2]=> array(2) { ["name"]=> string(11) "david bowie" ["strength"]=> float(60) },
...
}
}
]
я должен использовать другую комбинацию команд в совокупности?
Поэтому следует помнить, что «Likes» (из проекции) — это массив, встроенный в объект документа. Это означает, что хотя будут рассматриваться подполя, такие как «сила», на самом деле учитывается каждый элемент массива и каждое значение подполя.
Таким образом, в этом подходе нет ничего неправильного, но когда вы имеете дело с массивами в структуре агрегации, вы обычно хотите использовать $unwind
первый. В зависимости от того, где вы намерены «фильтровать» содержимое массива или нет, есть два основных подхода:
$artist = "david bowie";
$db->collection->aggregate(
array(
array( '$match' => array( 'music.likes.name' => $artist ) ),
array( '$project' => array( 'Likes' => '$music.likes' ) ),
array( '$unwind' => '$Likes' ),
array( '$match' => array( 'Likes.name' => $artist ) ),
array( '$group' => array(
'_id' => '$_id',
'Likes' => array( '$push' => '$Likes' )
)),
array( '$sort' => array( 'Likes.strength' => -1 ) ),
array( '$limit' => 10 )
)
);
Который по существу «фильтрует» содержимое массива в каждом документе только для элементов, которые соответствуют условию «исполнитель», поэтому здесь единственными оставшимися элементами для сортировки являются те, которые соответствуют.
$db->collection->aggregate(
array(
array( '$match' => array(music.likes.name' => $artist ) ),
array( '$project' => array( 'Likes' => '$music.likes' ) ),
array( '$unwind' => '$Likes' ),
array( '$group' => array(
'_id' => '$_id',
'Likes' => array( '$push' => '$Likes' ),
'strength' => array(
'$max' => array(
'$cond' => array(
array( '$eq' => array( '$Likes.name', $artist ) ),
'$Likes.strength',
0
)
)
)
)),
array( '$sort' => array( 'strength' => -1 ) ),
array( '$limit' => 10 )
)
);
Во втором случае вы в основном «строите» дополнительное поле, которое проверяет элементы в массиве и определяет, «использовать» ли это значение там, где оно совпадает с «Artist» с $eq
тест внутри $cond
оператор как троичное условие.
Как это происходит внутри $group
этап, это имеет смысл здесь просто применить $max
Значение найдено в соответствующих элементах массива, где, конечно, значение 0
возвращается из теста для элементов массива, которые не соответствуют условию.
Единственное, что следует отметить, это использование $match
этап первый. Как правило, вы хотите сначала «отфильтровать» условия в ваших документах, чтобы избежать ненужной работы. Это также ваш единственный шанс для конвейера использовать и «индексировать» вашу коллекцию, и вы захотите этого. Конечно, это также имеет смысл $sort
в обратном порядке с самыми высокими значениями «прочности» сверху.
Все сводится к тому, хотите ли вы «отфильтровать» массив или просто вернуть весь контент, но определите значение для сортировки.
Спасибо, я изменил ответ Нейла Ланна сверху, чтобы обработать несколько художников, вот как выглядит код
$artists = array('david bowie', 'bob marley');
$cursor = $user->collection->aggregate(
array(
array( '$match' => array(
'$and' => array(
array('music.likes.name' => $artists[0]),
array('music.likes.name' => $artists[1])
)
)
),
array( '$project' => array( 'Likes' => '$music.likes' ) ),
array( '$unwind' => '$Likes' ),
array( '$match' => array(
'$or' => array(
array('Likes.name' => $artists[0]),
array('Likes.name' => $artists[1])
)
)
),
array( '$group' => array(
'_id' => '$_id',
'Likes' => array( '$push' => '$Likes' )
)),
array( '$sort' => array( 'Likes.strength' => -1 ) ),
array( '$limit' => 10 )
)
);