Лучший способ, с которым я столкнулся, -
Линейный подход descr. можно найти там, где, например, Здесь или здесь . Что касается функции - , что - это то, что у меня получилось.
В конце - получилось более или менее простое, относительно быстрое и простое решение.
Тело функции
-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$
CREATE DEFINER=`root`@`localhost` FUNCTION `get_lineage`(the_id INT) RETURNS text CHARSET utf8
READS SQL DATA
BEGIN
DECLARE v_rec INT DEFAULT 0;
DECLARE done INT DEFAULT FALSE;
DECLARE v_res text DEFAULT '';
DECLARE v_papa int;
DECLARE v_papa_papa int DEFAULT -1;
DECLARE csr CURSOR FOR
select _id,parent_id -- @n:=@n+1 as rownum,T1.*
from
(SELECT @r AS _id,
(SELECT @r := table_parent_id FROM table WHERE table_id = _id) AS parent_id,
@l := @l + 1 AS lvl
FROM
(SELECT @r := the_id, @l := 0,@n:=0) vars,
table m
WHERE @r <> 0
) T1
where T1.parent_id is not null
ORDER BY T1.lvl DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
open csr;
read_loop: LOOP
fetch csr into v_papa,v_papa_papa;
SET v_rec = v_rec+1;
IF done THEN
LEAVE read_loop;
END IF;
-- add first
IF v_rec = 1 THEN
SET v_res = v_papa_papa;
END IF;
SET v_res = CONCAT(v_res,'-',v_papa);
END LOOP;
close csr;
return v_res;
END
И тогда вы просто
select get_lineage(the_id)
Надеюсь, это поможет кому-то:)
Я не совсем уверен, что ваш существующий конвейер является наиболее оптимальным, но без примерных данных, которые можно использовать, трудно действительно сказать иначе. Так что просто продолжаем работать с тем, что у вас есть:
var pipeline = [
// { $unwind: '$prices' }, // note: should not need this past MongoDB 3.0
{ $lookup: {
from: 'prices',
localField: 'prices',
foreignField: '_id',
as: 'prices'
}},
{ $unwind: '$prices' },
{ $lookup: {
from: 'stores',
localField: 'prices.store',
foreignField: '_id',
as: 'prices.store'
}},
// Changes from here
{ $unwind: '$prices.store' },
{ $match: {'prices.store._id': mongoose.Types.ObjectId(storeId) } },
{ $group: {
_id: ' Я не совсем уверен, что ваш существующий конвейер является наиболее оптимальным, но без примерных данных, которые можно использовать, трудно действительно сказать иначе. Так что просто продолжаем работать с тем, что у вас есть:
Использование $ unwind
[110] Точки там начинаются с:
-
Initial [ 116] - не требуется. Только в очень ранних выпусках MongoDB 3.0 это когда-либо было требованием $unwind
массива значений перед использованием $lookup
для этих значений.
-
$unwind
после $lookup
- Всегда требуется, если вы ожидаете совпадения с «единичным» объектом, поскольку $lookup
всегда возвращает массив. [ 1178] [тысяча сто семьдесят два]
-
$match
после $unwind
- фактически «оптимизация» для конвейерной обработки и фактически требование для «фильтрации» [ 1160]. Без $unwind
это просто подтверждение того, что «что-то есть» , но элементы, которые не совпадают, не будут удалены.
-
$push
в $group
- Это фактическая часть, которая перестраивает массив "prices"
.
Ключевым моментом, который вы в основном упускали, было использование $first
для содержимого «всего документа». Вы действительно никогда не хотите этого, и даже если вы хотите больше, чем просто "name"
, вы всегда хотите $push
"prices"
.
На самом деле вам, вероятно, нужно больше полей, чем просто name
из исходного документа, но на самом деле вам следует использовать вместо этого следующую форму.
Expressive $ lookup
Альтернатива доступна с большинством современных выпусков MongoDB начиная с MongoDB 3.6, которые, честно говоря, вы должны использовать как минимум:
var pipeline = [
{ $lookup: {
from: 'prices',
let: { prices: '$prices' },
pipeline: [
{ $match: {
store: mongoose.Types.ObjectId(storeId),
$expr: { $in: [ ' Я не совсем уверен, что ваш существующий конвейер является наиболее оптимальным, но без примерных данных, которые можно использовать, трудно действительно сказать иначе. Так что просто продолжаем работать с тем, что у вас есть:
Использование $ unwind
[110] Точки там начинаются с:
-
Initial [ 116] - не требуется. Только в очень ранних выпусках MongoDB 3.0 это когда-либо было требованием $unwind
массива значений перед использованием $lookup
для этих значений.
-
$unwind
после $lookup
- Всегда требуется, если вы ожидаете совпадения с «единичным» объектом, поскольку $lookup
всегда возвращает массив. [ 1178] [тысяча сто семьдесят два]
-
$match
после $unwind
- фактически «оптимизация» для конвейерной обработки и фактически требование для «фильтрации» [ 1160]. Без $unwind
это просто подтверждение того, что «что-то есть» , но элементы, которые не совпадают, не будут удалены.
-
$push
в $group
- Это фактическая часть, которая перестраивает массив "prices"
.
Ключевым моментом, который вы в основном упускали, было использование $first
для содержимого «всего документа». Вы действительно никогда не хотите этого, и даже если вы хотите больше, чем просто "name"
, вы всегда хотите $push
"prices"
.
На самом деле вам, вероятно, нужно больше полей, чем просто name
из исходного документа, но на самом деле вам следует использовать вместо этого следующую форму.
Expressive $ lookup
Альтернатива доступна с большинством современных выпусков MongoDB начиная с MongoDB 3.6, которые, честно говоря, вы должны использовать как минимум:
[111] Итак, первое, на что стоит обратить внимание Это «внешний» pipeline
на самом деле является просто одной $lookup
стадией, так как все, что ему действительно нужно сделать, это «присоединиться» к коллекции prices
. С точки зрения присоединения к вашей исходной коллекции это также верно, поскольку дополнительный $lookup
в приведенном выше примере фактически связан с prices
другой коллекцией.
Это именно то, что делает эта новая форма, поэтому вместо использования $unwind
в результирующем массиве, а затем после объединения, только соответствующие элементы для «цен». "затем" присоединяются "к коллекции" stores ", и перед они возвращаются в массив. Конечно, поскольку существует отношение «один к одному» с «магазином», это действительно $unwind
.
Короче говоря, вывод этого просто содержит исходный документ с массивом "prices"
внутри. Таким образом, нет необходимости перестраивать через $group
и не путать то, что вы используете $first
и что вы $push
. [ 1186]
ПРИМЕЧАНИЕ : я более чем подозреваю ваше утверждение «хранилища фильтров» и пытаюсь сопоставить поле store
, как представлено в коллекции "prices"
. Вопрос показывает ожидаемый результат из двух разных магазинов, даже если вы укажете совпадение равенства.
Если что-то подозреваю, вы могли бы означать «список магазинов» , который вместо этого был бы больше похож на:
store: { $in: storeList.map(store => mongoose.Types.ObjectId(store)) }
То, как вы бы это сделали работать с «списком строк» в обоих случаях, используя $in
для сопоставления со «списком» и Array.map()
для работы с предоставленным списком и возвращать каждый как ObjectId()
значения.
СОВЕТ : С mongoose вы используете «модель» вместо того, чтобы работать с именами коллекций, и фактические имена коллекций MongoDB обычно являются множественным числом от имени модели, которое вы зарегистрировали.
Таким образом, вам не нужно «жестко» кодировать фактические имена коллекций для $lookup
, просто используйте:
Model.collection.name
blockquote> .collection.name
является доступным свойством во всех моделях, и может избавить вас от необходимости помнить, как на самом деле назвать коллекцию для $lookup
. Он также защищает вас, если вы когда-либо измените регистрацию вашего экземпляра mongoose.model()
таким образом, чтобы изменить имя хранимой коллекции с помощью MongoDB.
Полная демонстрация
Ниже приведен отдельный список, демонстрирующий оба подхода как работу и то, как они дают одинаковые результаты:
const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');
const uri = 'mongodb://localhost:27017/shopping';
const opts = { useNewUrlParser: true };
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
mongoose.set('debug', true);
const storeSchema = new Schema({
name: { type: String }
});
const priceSchema = new Schema({
price: { type: Number },
store: { type: Schema.Types.ObjectId, ref: 'Store' }
});
const productSchema = new Schema({
name: { type: String },
prices: [{ type: Schema.Types.ObjectId, ref: 'Price' }]
});
const Store = mongoose.model('Store', storeSchema);
const Price = mongoose.model('Price', priceSchema);
const Product = mongoose.model('Product', productSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// Clean data
await Promise.all(
Object.entries(conn.models).map(([k, m]) => m.deleteMany())
);
// Insert working data
let [StoreA, StoreB, StoreC] = await Store.insertMany(
["StoreA", "StoreB", "StoreC"].map(name => ({ name }))
);
let [PriceA, PriceB, PriceC, PriceD, PriceE, PriceF]
= await Price.insertMany(
[[StoreA,1],[StoreB,2],[StoreA,3],[StoreC,4],[StoreB,5],[StoreC,6]]
.map(([store, price]) => ({ price, store }))
);
let [Milk, Cheese, Bread] = await Product.insertMany(
[
{ name: 'Milk', prices: [PriceA, PriceB] },
{ name: 'Cheese', prices: [PriceC, PriceD] },
{ name: 'Bread', prices: [PriceE, PriceF] }
]
);
// Test 1
{
log("Single Store - expressive")
const pipeline = [
{ '$lookup': {
'from': Price.collection.name,
'let': { prices: '$prices' },
'pipeline': [
{ '$match': {
'store': ObjectId(StoreA._id), // demo - it's already an ObjectId
'$expr': { '$in': [ ' Я не совсем уверен, что ваш существующий конвейер является наиболее оптимальным, но без примерных данных, которые можно использовать, трудно действительно сказать иначе. Так что просто продолжаем работать с тем, что у вас есть:
Использование $ unwind
[110] Точки там начинаются с:
-
Initial [ 116] - не требуется. Только в очень ранних выпусках MongoDB 3.0 это когда-либо было требованием $unwind
массива значений перед использованием $lookup
для этих значений.
-
$unwind
после $lookup
- Всегда требуется, если вы ожидаете совпадения с «единичным» объектом, поскольку $lookup
всегда возвращает массив. [ 1178] [тысяча сто семьдесят два]
-
$match
после $unwind
- фактически «оптимизация» для конвейерной обработки и фактически требование для «фильтрации» [ 1160]. Без $unwind
это просто подтверждение того, что «что-то есть» , но элементы, которые не совпадают, не будут удалены.
-
$push
в $group
- Это фактическая часть, которая перестраивает массив "prices"
.
Ключевым моментом, который вы в основном упускали, было использование $first
для содержимого «всего документа». Вы действительно никогда не хотите этого, и даже если вы хотите больше, чем просто "name"
, вы всегда хотите $push
"prices"
.
На самом деле вам, вероятно, нужно больше полей, чем просто name
из исходного документа, но на самом деле вам следует использовать вместо этого следующую форму.
Expressive $ lookup
Альтернатива доступна с большинством современных выпусков MongoDB начиная с MongoDB 3.6, которые, честно говоря, вы должны использовать как минимум:
[111] Итак, первое, на что стоит обратить внимание Это «внешний» pipeline
на самом деле является просто одной $lookup
стадией, так как все, что ему действительно нужно сделать, это «присоединиться» к коллекции prices
. С точки зрения присоединения к вашей исходной коллекции это также верно, поскольку дополнительный $lookup
в приведенном выше примере фактически связан с prices
другой коллекцией.
Это именно то, что делает эта новая форма, поэтому вместо использования $unwind
в результирующем массиве, а затем после объединения, только соответствующие элементы для «цен». "затем" присоединяются "к коллекции" stores ", и перед они возвращаются в массив. Конечно, поскольку существует отношение «один к одному» с «магазином», это действительно $unwind
.
Короче говоря, вывод этого просто содержит исходный документ с массивом "prices"
внутри. Таким образом, нет необходимости перестраивать через $group
и не путать то, что вы используете $first
и что вы $push
. [ 1186]
ПРИМЕЧАНИЕ : я более чем подозреваю ваше утверждение «хранилища фильтров» и пытаюсь сопоставить поле store
, как представлено в коллекции "prices"
. Вопрос показывает ожидаемый результат из двух разных магазинов, даже если вы укажете совпадение равенства.
Если что-то подозреваю, вы могли бы означать «список магазинов» , который вместо этого был бы больше похож на:
[112] То, как вы бы это сделали работать с «списком строк» в обоих случаях, используя $in
для сопоставления со «списком» и Array.map()
для работы с предоставленным списком и возвращать каждый как ObjectId()
значения.
СОВЕТ : С mongoose вы используете «модель» вместо того, чтобы работать с именами коллекций, и фактические имена коллекций MongoDB обычно являются множественным числом от имени модели, которое вы зарегистрировали.
Таким образом, вам не нужно «жестко» кодировать фактические имена коллекций для $lookup
, просто используйте:
[113] blockquote> .collection.name
является доступным свойством во всех моделях, и может избавить вас от необходимости помнить, как на самом деле назвать коллекцию для $lookup
. Он также защищает вас, если вы когда-либо измените регистрацию вашего экземпляра mongoose.model()
таким образом, чтобы изменить имя хранимой коллекции с помощью MongoDB.
Полная демонстрация
Ниже приведен отдельный список, демонстрирующий оба подхода как работу и то, как они дают одинаковые результаты:
[114] , который выдает результат: [1194 ]
Mongoose: stores.deleteMany({}, {})
Mongoose: prices.deleteMany({}, {})
Mongoose: products.deleteMany({}, {})
Mongoose: stores.insertMany([ { _id: 5c7c79bcc78675135c09f54b, name: 'StoreA', __v: 0 }, { _id: 5c7c79bcc78675135c09f54c, name: 'StoreB', __v: 0 }, { _id: 5c7c79bcc78675135c09f54d, name: 'StoreC', __v: 0 } ], {})
Mongoose: prices.insertMany([ { _id: 5c7c79bcc78675135c09f54e, price: 1, store: 5c7c79bcc78675135c09f54b, __v: 0 }, { _id: 5c7c79bcc78675135c09f54f, price: 2, store: 5c7c79bcc78675135c09f54c, __v: 0 }, { _id: 5c7c79bcc78675135c09f550, price: 3, store: 5c7c79bcc78675135c09f54b, __v: 0 }, { _id: 5c7c79bcc78675135c09f551, price: 4, store: 5c7c79bcc78675135c09f54d, __v: 0 }, { _id: 5c7c79bcc78675135c09f552, price: 5, store: 5c7c79bcc78675135c09f54c, __v: 0 }, { _id: 5c7c79bcc78675135c09f553, price: 6, store: 5c7c79bcc78675135c09f54d, __v: 0 } ], {})
Mongoose: products.insertMany([ { prices: [ 5c7c79bcc78675135c09f54e, 5c7c79bcc78675135c09f54f ], _id: 5c7c79bcc78675135c09f554, name: 'Milk', __v: 0 }, { prices: [ 5c7c79bcc78675135c09f550, 5c7c79bcc78675135c09f551 ], _id: 5c7c79bcc78675135c09f555, name: 'Cheese', __v: 0 }, { prices: [ 5c7c79bcc78675135c09f552, 5c7c79bcc78675135c09f553 ], _id: 5c7c79bcc78675135c09f556, name: 'Bread', __v: 0 } ], {})
"Single Store - expressive"
Mongoose: products.aggregate([ { '$lookup': { from: 'prices', let: { prices: '$prices' }, pipeline: [ { '$match': { store: 5c7c79bcc78675135c09f54b, '$expr': { '$in': [ ' Я не совсем уверен, что ваш существующий конвейер является наиболее оптимальным, но без примерных данных, которые можно использовать, трудно действительно сказать иначе. Так что просто продолжаем работать с тем, что у вас есть:
Использование $ unwind
[110] Точки там начинаются с:
-
Initial [ 116] - не требуется. Только в очень ранних выпусках MongoDB 3.0 это когда-либо было требованием $unwind
массива значений перед использованием $lookup
для этих значений.
-
$unwind
после $lookup
- Всегда требуется, если вы ожидаете совпадения с «единичным» объектом, поскольку $lookup
всегда возвращает массив. [ 1178] [тысяча сто семьдесят два]
-
$match
после $unwind
- фактически «оптимизация» для конвейерной обработки и фактически требование для «фильтрации» [ 1160]. Без $unwind
это просто подтверждение того, что «что-то есть» , но элементы, которые не совпадают, не будут удалены.
-
$push
в $group
- Это фактическая часть, которая перестраивает массив "prices"
.
Ключевым моментом, который вы в основном упускали, было использование $first
для содержимого «всего документа». Вы действительно никогда не хотите этого, и даже если вы хотите больше, чем просто "name"
, вы всегда хотите $push
"prices"
.
На самом деле вам, вероятно, нужно больше полей, чем просто name
из исходного документа, но на самом деле вам следует использовать вместо этого следующую форму.
Expressive $ lookup
Альтернатива доступна с большинством современных выпусков MongoDB начиная с MongoDB 3.6, которые, честно говоря, вы должны использовать как минимум:
[111] Итак, первое, на что стоит обратить внимание Это «внешний» pipeline
на самом деле является просто одной $lookup
стадией, так как все, что ему действительно нужно сделать, это «присоединиться» к коллекции prices
. С точки зрения присоединения к вашей исходной коллекции это также верно, поскольку дополнительный $lookup
в приведенном выше примере фактически связан с prices
другой коллекцией.
Это именно то, что делает эта новая форма, поэтому вместо использования $unwind
в результирующем массиве, а затем после объединения, только соответствующие элементы для «цен». "затем" присоединяются "к коллекции" stores ", и перед они возвращаются в массив. Конечно, поскольку существует отношение «один к одному» с «магазином», это действительно $unwind
.
Короче говоря, вывод этого просто содержит исходный документ с массивом "prices"
внутри. Таким образом, нет необходимости перестраивать через $group
и не путать то, что вы используете $first
и что вы $push
. [ 1186]
ПРИМЕЧАНИЕ : я более чем подозреваю ваше утверждение «хранилища фильтров» и пытаюсь сопоставить поле store
, как представлено в коллекции "prices"
. Вопрос показывает ожидаемый результат из двух разных магазинов, даже если вы укажете совпадение равенства.
Если что-то подозреваю, вы могли бы означать «список магазинов» , который вместо этого был бы больше похож на:
[112] То, как вы бы это сделали работать с «списком строк» в обоих случаях, используя $in
для сопоставления со «списком» и Array.map()
для работы с предоставленным списком и возвращать каждый как ObjectId()
значения.
СОВЕТ : С mongoose вы используете «модель» вместо того, чтобы работать с именами коллекций, и фактические имена коллекций MongoDB обычно являются множественным числом от имени модели, которое вы зарегистрировали.
Таким образом, вам не нужно «жестко» кодировать фактические имена коллекций для $lookup
, просто используйте:
[113] blockquote> .collection.name
является доступным свойством во всех моделях, и может избавить вас от необходимости помнить, как на самом деле назвать коллекцию для $lookup
. Он также защищает вас, если вы когда-либо измените регистрацию вашего экземпляра mongoose.model()
таким образом, чтобы изменить имя хранимой коллекции с помощью MongoDB.
Полная демонстрация
Ниже приведен отдельный список, демонстрирующий оба подхода как работу и то, как они дают одинаковые результаты:
[114] , который выдает результат: [1194 ] [115]id', '$prices' ] } } }, { '$lookup': { from: 'stores', let: { store: '$store' }, pipeline: [ { '$match': { '$expr': { '$eq': [ '
Я не совсем уверен, что ваш существующий конвейер является наиболее оптимальным, но без примерных данных, которые можно использовать, трудно действительно сказать иначе. Так что просто продолжаем работать с тем, что у вас есть:
Использование $ unwind
[110] Точки там начинаются с:
-
Initial [ 116] - не требуется. Только в очень ранних выпусках MongoDB 3.0 это когда-либо было требованием $unwind
массива значений перед использованием $lookup
для этих значений.
-
$unwind
после $lookup
- Всегда требуется, если вы ожидаете совпадения с «единичным» объектом, поскольку $lookup
всегда возвращает массив. [ 1178] [тысяча сто семьдесят два]
-
$match
после $unwind
- фактически «оптимизация» для конвейерной обработки и фактически требование для «фильтрации» [ 1160]. Без $unwind
это просто подтверждение того, что «что-то есть» , но элементы, которые не совпадают, не будут удалены.
-
$push
в $group
- Это фактическая часть, которая перестраивает массив "prices"
.
Ключевым моментом, который вы в основном упускали, было использование $first
для содержимого «всего документа». Вы действительно никогда не хотите этого, и даже если вы хотите больше, чем просто "name"
, вы всегда хотите $push
"prices"
.
На самом деле вам, вероятно, нужно больше полей, чем просто name
из исходного документа, но на самом деле вам следует использовать вместо этого следующую форму.
Expressive $ lookup
Альтернатива доступна с большинством современных выпусков MongoDB начиная с MongoDB 3.6, которые, честно говоря, вы должны использовать как минимум:
[111] Итак, первое, на что стоит обратить внимание Это «внешний» pipeline
на самом деле является просто одной $lookup
стадией, так как все, что ему действительно нужно сделать, это «присоединиться» к коллекции prices
. С точки зрения присоединения к вашей исходной коллекции это также верно, поскольку дополнительный $lookup
в приведенном выше примере фактически связан с prices
другой коллекцией.
Это именно то, что делает эта новая форма, поэтому вместо использования $unwind
в результирующем массиве, а затем после объединения, только соответствующие элементы для «цен». "затем" присоединяются "к коллекции" stores ", и перед они возвращаются в массив. Конечно, поскольку существует отношение «один к одному» с «магазином», это действительно $unwind
.
Короче говоря, вывод этого просто содержит исходный документ с массивом "prices"
внутри. Таким образом, нет необходимости перестраивать через $group
и не путать то, что вы используете $first
и что вы $push
. [ 1186]
ПРИМЕЧАНИЕ : я более чем подозреваю ваше утверждение «хранилища фильтров» и пытаюсь сопоставить поле store
, как представлено в коллекции "prices"
. Вопрос показывает ожидаемый результат из двух разных магазинов, даже если вы укажете совпадение равенства.
Если что-то подозреваю, вы могли бы означать «список магазинов» , который вместо этого был бы больше похож на:
[112] То, как вы бы это сделали работать с «списком строк» в обоих случаях, используя $in
для сопоставления со «списком» и Array.map()
для работы с предоставленным списком и возвращать каждый как ObjectId()
значения.
СОВЕТ : С mongoose вы используете «модель» вместо того, чтобы работать с именами коллекций, и фактические имена коллекций MongoDB обычно являются множественным числом от имени модели, которое вы зарегистрировали.
Таким образом, вам не нужно «жестко» кодировать фактические имена коллекций для $lookup
, просто используйте:
[113] blockquote> .collection.name
является доступным свойством во всех моделях, и может избавить вас от необходимости помнить, как на самом деле назвать коллекцию для $lookup
. Он также защищает вас, если вы когда-либо измените регистрацию вашего экземпляра mongoose.model()
таким образом, чтобы изменить имя хранимой коллекции с помощью MongoDB.
Полная демонстрация
Ниже приведен отдельный список, демонстрирующий оба подхода как работу и то, как они дают одинаковые результаты:
[114] , который выдает результат: [1194 ] [115]id', '$store' ] } } } ], as: 'store' } }, { '$unwind': '$store' } ], as: 'prices' } }, { '$match': { 'prices.0': { '$exists': true } } } ], {})
[
{
"_id": "5c7c79bcc78675135c09f554",
"prices": [
{
"_id": "5c7c79bcc78675135c09f54e",
"price": 1,
"store": {
"_id": "5c7c79bcc78675135c09f54b",
"name": "StoreA",
"__v": 0
},
"__v": 0
}
],
"name": "Milk",
"__v": 0
},
{
"_id": "5c7c79bcc78675135c09f555",
"prices": [
{
"_id": "5c7c79bcc78675135c09f550",
"price": 3,
"store": {
"_id": "5c7c79bcc78675135c09f54b",
"name": "StoreA",
"__v": 0
},
"__v": 0
}
],
"name": "Cheese",
"__v": 0
}
]
"Dual Store - expressive"
Mongoose: products.aggregate([ { '$lookup': { from: 'prices', let: { prices: '$prices' }, pipeline: [ { '$match': { store: { '$in': [ 5c7c79bcc78675135c09f54b, 5c7c79bcc78675135c09f54c ] }, '$expr': { '$in': [ '
Я не совсем уверен, что ваш существующий конвейер является наиболее оптимальным, но без примерных данных, которые можно использовать, трудно действительно сказать иначе. Так что просто продолжаем работать с тем, что у вас есть:
Использование $ unwind
[110] Точки там начинаются с:
-
Initial [ 116] - не требуется. Только в очень ранних выпусках MongoDB 3.0 это когда-либо было требованием $unwind
массива значений перед использованием $lookup
для этих значений.
-
$unwind
после $lookup
- Всегда требуется, если вы ожидаете совпадения с «единичным» объектом, поскольку $lookup
всегда возвращает массив. [ 1178] [тысяча сто семьдесят два]
-
$match
после $unwind
- фактически «оптимизация» для конвейерной обработки и фактически требование для «фильтрации» [ 1160]. Без $unwind
это просто подтверждение того, что «что-то есть» , но элементы, которые не совпадают, не будут удалены.
-
$push
в $group
- Это фактическая часть, которая перестраивает массив "prices"
.
Ключевым моментом, который вы в основном упускали, было использование $first
для содержимого «всего документа». Вы действительно никогда не хотите этого, и даже если вы хотите больше, чем просто "name"
, вы всегда хотите $push
"prices"
.
На самом деле вам, вероятно, нужно больше полей, чем просто name
из исходного документа, но на самом деле вам следует использовать вместо этого следующую форму.
Expressive $ lookup
Альтернатива доступна с большинством современных выпусков MongoDB начиная с MongoDB 3.6, которые, честно говоря, вы должны использовать как минимум:
[111] Итак, первое, на что стоит обратить внимание Это «внешний» pipeline
на самом деле является просто одной $lookup
стадией, так как все, что ему действительно нужно сделать, это «присоединиться» к коллекции prices
. С точки зрения присоединения к вашей исходной коллекции это также верно, поскольку дополнительный $lookup
в приведенном выше примере фактически связан с prices
другой коллекцией.
Это именно то, что делает эта новая форма, поэтому вместо использования $unwind
в результирующем массиве, а затем после объединения, только соответствующие элементы для «цен». "затем" присоединяются "к коллекции" stores ", и перед они возвращаются в массив. Конечно, поскольку существует отношение «один к одному» с «магазином», это действительно $unwind
.
Короче говоря, вывод этого просто содержит исходный документ с массивом "prices"
внутри. Таким образом, нет необходимости перестраивать через $group
и не путать то, что вы используете $first
и что вы $push
. [ 1186]
ПРИМЕЧАНИЕ : я более чем подозреваю ваше утверждение «хранилища фильтров» и пытаюсь сопоставить поле store
, как представлено в коллекции "prices"
. Вопрос показывает ожидаемый результат из двух разных магазинов, даже если вы укажете совпадение равенства.
Если что-то подозреваю, вы могли бы означать «список магазинов» , который вместо этого был бы больше похож на:
[112] То, как вы бы это сделали работать с «списком строк» в обоих случаях, используя $in
для сопоставления со «списком» и Array.map()
для работы с предоставленным списком и возвращать каждый как ObjectId()
значения.
СОВЕТ : С mongoose вы используете «модель» вместо того, чтобы работать с именами коллекций, и фактические имена коллекций MongoDB обычно являются множественным числом от имени модели, которое вы зарегистрировали.
Таким образом, вам не нужно «жестко» кодировать фактические имена коллекций для $lookup
, просто используйте:
[113] blockquote> .collection.name
является доступным свойством во всех моделях, и может избавить вас от необходимости помнить, как на самом деле назвать коллекцию для $lookup
. Он также защищает вас, если вы когда-либо измените регистрацию вашего экземпляра mongoose.model()
таким образом, чтобы изменить имя хранимой коллекции с помощью MongoDB.
Полная демонстрация
Ниже приведен отдельный список, демонстрирующий оба подхода как работу и то, как они дают одинаковые результаты:
[114] , который выдает результат: [1194 ] [115]id', '$prices' ] } } }, { '$lookup': { from: 'stores', let: { store: '$store' }, pipeline: [ { '$match': { '$expr': { '$eq': [ '
Я не совсем уверен, что ваш существующий конвейер является наиболее оптимальным, но без примерных данных, которые можно использовать, трудно действительно сказать иначе. Так что просто продолжаем работать с тем, что у вас есть:
Использование $ unwind
[110] Точки там начинаются с:
-
Initial [ 116] - не требуется. Только в очень ранних выпусках MongoDB 3.0 это когда-либо было требованием $unwind
массива значений перед использованием $lookup
для этих значений.
-
$unwind
после $lookup
- Всегда требуется, если вы ожидаете совпадения с «единичным» объектом, поскольку $lookup
всегда возвращает массив. [ 1178] [тысяча сто семьдесят два]
-
$match
после $unwind
- фактически «оптимизация» для конвейерной обработки и фактически требование для «фильтрации» [ 1160]. Без $unwind
это просто подтверждение того, что «что-то есть» , но элементы, которые не совпадают, не будут удалены.
-
$push
в $group
- Это фактическая часть, которая перестраивает массив "prices"
.
Ключевым моментом, который вы в основном упускали, было использование $first
для содержимого «всего документа». Вы действительно никогда не хотите этого, и даже если вы хотите больше, чем просто "name"
, вы всегда хотите $push
"prices"
.
На самом деле вам, вероятно, нужно больше полей, чем просто name
из исходного документа, но на самом деле вам следует использовать вместо этого следующую форму.
Expressive $ lookup
Альтернатива доступна с большинством современных выпусков MongoDB начиная с MongoDB 3.6, которые, честно говоря, вы должны использовать как минимум:
[111] Итак, первое, на что стоит обратить внимание Это «внешний» pipeline
на самом деле является просто одной $lookup
стадией, так как все, что ему действительно нужно сделать, это «присоединиться» к коллекции prices
. С точки зрения присоединения к вашей исходной коллекции это также верно, поскольку дополнительный $lookup
в приведенном выше примере фактически связан с prices
другой коллекцией.
Это именно то, что делает эта новая форма, поэтому вместо использования $unwind
в результирующем массиве, а затем после объединения, только соответствующие элементы для «цен». "затем" присоединяются "к коллекции" stores ", и перед они возвращаются в массив. Конечно, поскольку существует отношение «один к одному» с «магазином», это действительно $unwind
.
Короче говоря, вывод этого просто содержит исходный документ с массивом "prices"
внутри. Таким образом, нет необходимости перестраивать через $group
и не путать то, что вы используете $first
и что вы $push
. [ 1186]
ПРИМЕЧАНИЕ : я более чем подозреваю ваше утверждение «хранилища фильтров» и пытаюсь сопоставить поле store
, как представлено в коллекции "prices"
. Вопрос показывает ожидаемый результат из двух разных магазинов, даже если вы укажете совпадение равенства.
Если что-то подозреваю, вы могли бы означать «список магазинов» , который вместо этого был бы больше похож на:
[112] То, как вы бы это сделали работать с «списком строк» в обоих случаях, используя $in
для сопоставления со «списком» и Array.map()
для работы с предоставленным списком и возвращать каждый как ObjectId()
значения.
СОВЕТ : С mongoose вы используете «модель» вместо того, чтобы работать с именами коллекций, и фактические имена коллекций MongoDB обычно являются множественным числом от имени модели, которое вы зарегистрировали.
Таким образом, вам не нужно «жестко» кодировать фактические имена коллекций для $lookup
, просто используйте:
[113] blockquote> .collection.name
является доступным свойством во всех моделях, и может избавить вас от необходимости помнить, как на самом деле назвать коллекцию для $lookup
. Он также защищает вас, если вы когда-либо измените регистрацию вашего экземпляра mongoose.model()
таким образом, чтобы изменить имя хранимой коллекции с помощью MongoDB.
Полная демонстрация
Ниже приведен отдельный список, демонстрирующий оба подхода как работу и то, как они дают одинаковые результаты:
[114] , который выдает результат: [1194 ] [115]id', '$store' ] } } } ], as: 'store' } }, { '$unwind': '$store' } ], as: 'prices' } }, { '$match': { 'prices.0': { '$exists': true } } } ], {})
[
{
"_id": "5c7c79bcc78675135c09f554",
"prices": [
{
"_id": "5c7c79bcc78675135c09f54e",
"price": 1,
"store": {
"_id": "5c7c79bcc78675135c09f54b",
"name": "StoreA",
"__v": 0
},
"__v": 0
},
{
"_id": "5c7c79bcc78675135c09f54f",
"price": 2,
"store": {
"_id": "5c7c79bcc78675135c09f54c",
"name": "StoreB",
"__v": 0
},
"__v": 0
}
],
"name": "Milk",
"__v": 0
},
{
"_id": "5c7c79bcc78675135c09f555",
"prices": [
{
"_id": "5c7c79bcc78675135c09f550",
"price": 3,
"store": {
"_id": "5c7c79bcc78675135c09f54b",
"name": "StoreA",
"__v": 0
},
"__v": 0
}
],
"name": "Cheese",
"__v": 0
},
{
"_id": "5c7c79bcc78675135c09f556",
"prices": [
{
"_id": "5c7c79bcc7