Фильтровать по присоединенному поддокументу

Лучший способ, с которым я столкнулся, -

  1. Использовать lineage для хранения \ sort \ trace trees. Этого более чем достаточно и работает в тысячу раз быстрее, чем любой другой подход. Он также позволяет оставаться на этом шаблоне, даже если DB изменится (поскольку ANY db позволит использовать этот шаблон)
  2. Использовать функцию, определяющую линию для конкретного идентификатора.
  3. Использовать как вы хотите (в выборе или в CUD-операциях или даже по заданию).

Линейный подход 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)

Надеюсь, это поможет кому-то:)

0
задан Diego Gallegos 3 March 2019 в 16:50
поделиться

1 ответ

Я не совсем уверен, что ваш существующий конвейер является наиболее оптимальным, но без примерных данных, которые можно использовать, трудно действительно сказать иначе. Так что просто продолжаем работать с тем, что у вас есть:

Использование $ unwind

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

.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]

.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]

.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]

.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]

.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]

.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

0
ответ дан Neil Lunn 3 March 2019 в 16:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: