MongoDB find () с точечной нотацией не работает [дубликат]

matches() вернет true только в том случае, если полная строка соответствует. find() попытается найти следующее вхождение в подстроке, которое соответствует регулярному выражению. Обратите внимание на акцент на «следующий». Это означает, что результат вызова find() несколько раз может быть не таким. Кроме того, с помощью find() вы можете вызвать start(), чтобы вернуть позицию, подстроенную подстрокой.

final Matcher subMatcher = Pattern.compile("\\d+").matcher("skrf35kesruytfkwu4ty7sdfs");
System.out.println("Found: " + subMatcher.matches());
System.out.println("Found: " + subMatcher.find() + " - position " + subMatcher.start());
System.out.println("Found: " + subMatcher.find() + " - position " + subMatcher.start());
System.out.println("Found: " + subMatcher.find() + " - position " + subMatcher.start());
System.out.println("Found: " + subMatcher.find());
System.out.println("Found: " + subMatcher.find());
System.out.println("Matched: " + subMatcher.matches());

System.out.println("-----------");
final Matcher fullMatcher = Pattern.compile("^\\w+$").matcher("skrf35kesruytfkwu4ty7sdfs");
System.out.println("Found: " + fullMatcher.find() + " - position " + fullMatcher.start());
System.out.println("Found: " + fullMatcher.find());
System.out.println("Found: " + fullMatcher.find());
System.out.println("Matched: " + fullMatcher.matches());
System.out.println("Matched: " + fullMatcher.matches());
System.out.println("Matched: " + fullMatcher.matches());
System.out.println("Matched: " + fullMatcher.matches());

Будет выводиться:

Found: false
Found: true - position 4
Found: true - position 17
Found: true - position 20
Found: false
Found: false
Matched: false
-----------
Found: true - position 0
Found: false
Found: false
Matched: true
Matched: true
Matched: true
Matched: true

Итак, будьте осторожны при многократном вызове find(), если объект Matcher не был сброшен, даже когда регулярное выражение окружено с ^ и $, чтобы соответствовать полной строке.

50
задан jschr 3 July 2012 в 23:45
поделиться

6 ответов

В современном MongoDB больше 3.2 вы можете использовать $lookup в качестве альтернативы .populate() в большинстве случаев. Это также имеет преимущество, заключающееся в том, что на самом деле выполняется соединение «на сервере» в отличие от того, что .populate() делает на самом деле «несколькими запросами» для «эмулирования» соединения.

Таким образом, .populate() на самом деле не является «объединением» в смысле того, как это делает реляционная база данных. Оператор $lookup , с другой стороны, фактически выполняет работу на сервере и более или менее аналогичен «LEFT JOIN» :

Item.aggregate(
  [
    { "$lookup": {
      "from": ItemTags.collection.name,
      "localField": "tags",
      "foreignField": "_id",
      "as": "tags"
    }},
    { "$unwind": "$tags" },
    { "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },
    { "$group": {
      "_id": "$_id",
      "dateCreated": { "$first": "$dateCreated" },
      "title": { "$first": "$title" },
      "description": { "$first": "$description" },
      "tags": { "$push": "$tags" }
    }}
  ],
  function(err, result) {
    // "tags" is now filtered by condition and "joined"
  }
)

NB Здесь .collection.name фактически оценивает «строку», которая является фактическим именем коллекции MongoDB, присвоенной модели. Поскольку mongoose «плюрализует» имена коллекций по умолчанию, а $lookup нуждается в фактическом имени коллекции MongoDB в качестве аргумента (поскольку это операция с сервером), то это удобный трюк для использования в коде Mongoose, поскольку (g34)

Хотя мы также могли использовать $filter на массивах для удаления нежелательных элементов, это на самом деле самый эффективная форма из-за Оптимизация трубопровода агрегации для специального условия как $lookup , за которым следует как $unwind , так и $match .

Это на самом деле приводит к тому, что три этапа конвейера перекатываются в один:

   { "$lookup" : {
     "from" : "itemtags",
     "as" : "tags",
     "localField" : "tags",
     "foreignField" : "_id",
     "unwinding" : {
       "preserveNullAndEmptyArrays" : false
     },
     "matching" : {
       "tagName" : {
         "$in" : [
           "funny",
           "politics"
         ]
       }
     }
   }}

Это очень оптимально, поскольку фактическая операция «фильтрует коллекцию сначала присоединиться », затем возвращает результаты и« раскручивает »массив. Оба метода используются, поэтому результаты не нарушают предел BSON в 16 МБ, что является ограничением, которое клиент не имеет.

Единственная проблема заключается в том, что она кажется «противоинтуитивной» в некотором роде, особенно если вы хотите получить результаты в массиве, но это то, что здесь $group здесь, так как он восстанавливает исходную форму документа.

Также очень жаль, что мы просто не может в настоящий момент написать $lookup в том же конечном синтаксисе, который использует сервер. ИМХО, это надзор, который нужно исправить. Но на данный момент простое использование последовательности будет работать и является наиболее жизнеспособным вариантом с максимальной производительностью и масштабируемостью.

Приложение - MongoDB 3.6 и выше

Хотя приведенный здесь шаблон довольно оптимизирован из-за того, что другие этапы катятся в $lookup , у него есть одна неудача в том, что «LEFT JOIN», который обычно присущ как $lookup , а действия populate() отрицаются «оптимальным» использованием $unwind здесь, который не сохраняет пустые массивы. Вы можете добавить опцию preserveNullAndEmptyArrays, но это исключает описанную выше последовательность «оптимизированная» и по существу оставляет все три стадии неповрежденными, которые обычно объединяются в оптимизации.

MongoDB 3.6 расширяется с помощью «более выразительной» формы $lookup , позволяющей выражать «субподрядку». Это не только позволяет сохранить «LEFT JOIN», но и позволяет получить оптимальный запрос для уменьшения возвращаемых результатов и с упрощенным синтаксисом:

Item.aggregate([
  { "$lookup": {
    "from": ItemTags.collection.name,
    "let": { "tags": "$tags" },
    "pipeline": [
      { "$match": {
        "tags": { "$in": [ "politics", "funny" ] },
        "$expr": { "$in": [ "$_id", "$$tags" ] }
      }}
    ]
  }}
])

$expr , используемый для соответствия объявленному «локальному» значению с «чужим» значением, фактически является тем, что MongoDB делает «внутренне» теперь с оригинальным синтаксисом $lookup . Выражая в этой форме, мы можем адаптировать исходное выражение $match в самой «подтрубке».

На самом деле, как истинный «конвейер агрегации», вы можете сделать почти все, что вы можете сделать с конвейером агрегации в этом выражении «под-конвейер», включая «вложение» уровней $lookup в другие связанные коллекции.

Дальнейшее использование немного выходит за рамки того, что здесь задается здесь, но в отношении даже «вложенной популяции» тогда новый шаблон использования $lookup позволяет сделать это примерно одинаковым, а "lot" более мощный в своем полном использовании.


Рабочий пример

Ниже приведен пример использования статического метода в модели. Как только этот статический метод будет реализован, вызов просто станет:

  Item.lookup(
    {
      path: 'tags',
      query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
    },
    callback
  )

Или усиление, чтобы быть немного более современным, становится:

  let results = await Item.lookup({
    path: 'tags',
    query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
  })

. Очень похоже на .populate() в структуре, но на самом деле он делает соединение на сервере. Для полноты, использование здесь возвращает возвращаемые данные обратно в экземпляры экземпляров mongoose в соответствии с родительским и дочерним случаями.

Это довольно тривиально и легко адаптируется или просто используется как для большинства распространенных случаев.

NB. Использование async здесь просто для краткости запуска прилагаемого примера. Фактическая реализация не зависит от этой зависимости.

const async = require('async'),
      mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.connect('mongodb://localhost/looktest');

const itemTagSchema = new Schema({
  tagName: String
});

const itemSchema = new Schema({
  dateCreated: { type: Date, default: Date.now },
  title: String,
  description: String,
  tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
});

itemSchema.statics.lookup = function(opt,callback) {
  let rel =
    mongoose.model(this.schema.path(opt.path).caster.options.ref);

  let group = { "$group": { } };
  this.schema.eachPath(p =>
    group.$group[p] = (p === "_id") ? "$_id" :
      (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

  let pipeline = [
    { "$lookup": {
      "from": rel.collection.name,
      "as": opt.path,
      "localField": opt.path,
      "foreignField": "_id"
    }},
    { "$unwind": `$${opt.path}` },
    { "$match": opt.query },
    group
  ];

  this.aggregate(pipeline,(err,result) => {
    if (err) callback(err);
    result = result.map(m => {
      m[opt.path] = m[opt.path].map(r => rel(r));
      return this(m);
    });
    callback(err,result);
  });
}

const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);

function log(body) {
  console.log(JSON.stringify(body, undefined, 2))
}
async.series(
  [
    // Clean data
    (callback) => async.each(mongoose.models,(model,callback) =>
      model.remove({},callback),callback),

    // Create tags and items
    (callback) =>
      async.waterfall(
        [
          (callback) =>
            ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
              callback),

          (tags, callback) =>
            Item.create({ "title": "Something","description": "An item",
              "tags": tags },callback)
        ],
        callback
      ),

    // Query with our static
    (callback) =>
      Item.lookup(
        {
          path: 'tags',
          query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
        },
        callback
      )
  ],
  (err,results) => {
    if (err) throw err;
    let result = results.pop();
    log(result);
    mongoose.disconnect();
  }
)

Или немного более современный для узла 8.x и выше с async/await и без дополнительных зависимостей:

const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/looktest';

mongoose.Promise = global.Promise;
mongoose.set('debug', true);

const itemTagSchema = new Schema({
  tagName: String
});

const itemSchema = new Schema({
  dateCreated: { type: Date, default: Date.now },
  title: String,
  description: String,
  tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
});

itemSchema.statics.lookup = function(opt) {
  let rel =
    mongoose.model(this.schema.path(opt.path).caster.options.ref);

  let group = { "$group": { } };
  this.schema.eachPath(p =>
    group.$group[p] = (p === "_id") ? "$_id" :
      (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

  let pipeline = [
    { "$lookup": {
      "from": rel.collection.name,
      "as": opt.path,
      "localField": opt.path,
      "foreignField": "_id"
    }},
    { "$unwind": `$${opt.path}` },
    { "$match": opt.query },
    group
  ];

  return this.aggregate(pipeline).exec().then(r => r.map(m => 
    this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })
  ));
}

const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);

const log = body => console.log(JSON.stringify(body, undefined, 2));

(async function() {
  try {

    const conn = await mongoose.connect(uri);

    // Clean data
    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

    // Create tags and items
    const tags = await ItemTag.create(
      ["movies", "funny"].map(tagName =>({ tagName }))
    );
    const item = await Item.create({ 
      "title": "Something",
      "description": "An item",
      tags 
    });

    // Query with our static
    const result = (await Item.lookup({
      path: 'tags',
      query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
    })).pop();
    log(result);

    mongoose.disconnect();

  } catch (e) {
    console.error(e);
  } finally {
    process.exit()
  }
})()

И от MongoDB 3.6 и выше, даже без создания $unwind и $group :

const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/looktest';

mongoose.Promise = global.Promise;
mongoose.set('debug', true);

const itemTagSchema = new Schema({
  tagName: String
});

const itemSchema = new Schema({
  title: String,
  description: String,
  tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
},{ timestamps: true });

itemSchema.statics.lookup = function({ path, query }) {
  let rel =
    mongoose.model(this.schema.path(path).caster.options.ref);

  // MongoDB 3.6 and up $lookup with sub-pipeline
  let pipeline = [
    { "$lookup": {
      "from": rel.collection.name,
      "as": path,
      "let": { [path]: `$${path}` },
      "pipeline": [
        { "$match": {
          ...query,
          "$expr": { "$in": [ "$_id", `$$${path}` ] }
        }}
      ]
    }}
  ];

  return this.aggregate(pipeline).exec().then(r => r.map(m =>
    this({ ...m, [path]: m[path].map(r => rel(r)) })
  ));
};

const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);

const log = body => console.log(JSON.stringify(body, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri);

    // Clean data
    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

    // Create tags and items
    const tags = await ItemTag.insertMany(
      ["movies", "funny"].map(tagName => ({ tagName }))
    );

    const item = await Item.create({
      "title": "Something",
      "description": "An item",
      tags
    });

    // Query with our static
    let result = (await Item.lookup({
      path: 'tags',
      query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }
    })).pop();
    log(result);


    await mongoose.disconnect();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }

})()
17
ответ дан Neil Lunn 20 August 2018 в 07:41
поделиться
  • 1
    Я больше не использую Mongo / Mongoose, но я принял ваш ответ, потому что это популярный вопрос, и похоже, что это было полезно для других. Рад видеть, что эта проблема имеет более масштабируемое решение. Спасибо за предоставление обновленного ответа. – jschr 12 May 2018 в 02:27
  • 2

то, о чем вы просите, напрямую не поддерживается, но может быть достигнуто путем добавления другого шага фильтра после возвращения запроса.

во-первых, .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } ) определенно то, что вам нужно сделать, чтобы отфильтровать теги документы. то после возвращения запроса вам нужно будет вручную отфильтровать документы, у которых нет документов tags, которые соответствуют критериям заполнения. что-то вроде:

query....
.exec(function(err, docs){
   docs = docs.filter(function(doc){
     return doc.tags.length;
   })
   // do stuff with docs
});
35
ответ дан aaronheckmann 20 August 2018 в 07:41
поделиться
  • 1
    Привет, Аарон, спасибо за ответ. Возможно, я ошибаюсь, но не будет ли $ in on populate () заполнять только совпадающие теги? Таким образом, любые дополнительные теги на элементе будут отфильтрованы. Похоже, мне нужно будет заполнить все элементы, и второй шаг фильтра уменьшит его на основе имени тега. – jschr 10 July 2012 в 19:35
  • 2
    Мне тоже любопытно .... кажется, фильтр бесполезен, если я возвращаю нулевые теги. – chovy 15 March 2015 в 01:23
  • 3
    @aaronheckmann Я внедрил ваше предлагаемое решение, вы правы, чтобы делать фильтр после .exec, потому что, хотя заполняющий запрос заполняет только требуемые объекты, но все же возвращает его полный набор данных. Как вы думаете, в новой версии Mongoose есть некоторая опция для возврата только заполненного набора данных, поэтому нам не нужно идти на другую фильтрацию? – Aqib Mumtaz 11 November 2015 в 09:58
  • 4
    Мне также интересно узнать о производительности. Если запрос возвращает весь набор данных в конце, то нет никакой цели для фильтрации популяции? Что ты говоришь? Im адаптирует демографический запрос для оптимизации производительности, но таким образом производительность не улучшится для большого набора данных? – Aqib Mumtaz 11 November 2015 в 10:04
  • 5
    mongoosejs.com/docs/api.html#query_Query-populate имеет все подробности, если кто-либо еще заинтересован – samazi 7 March 2016 в 04:51

Обновление: Пожалуйста, взгляните на комментарии - этот ответ не соответствует вашему вопросу, но, возможно, он отвечает на другие вопросы пользователей, которые натолкнулись (я думаю, что из-за upvotes), поэтому я не буду удалять это " answer ":

Сначала: я знаю, что этот вопрос действительно устарел, но я искал именно эту проблему, и эта публикация была опубликована в Google # 1. Поэтому я реализовал версию docs.filter (принятый ответ), но, как я читал в mongoose v4.6.0 docs , мы теперь можем просто использовать:

Item.find({}).populate({
    path: 'tags',
    match: { tagName: { $in: ['funny', 'politics'] }}
}).exec((err, items) => {
  console.log(items.tags) 
  // contains only tags where tagName is 'funny' or 'politics'
})

Надеемся, что это поможет будущему пользователей поисковой системы.

13
ответ дан Fabian 20 August 2018 в 07:41
поделиться
  • 1
    Но это только фильтрует массив items.tags? Элементы будут возвращены независимо от имени тега ... – OllyBarca 1 December 2016 в 01:10
  • 2
    Это правильно, @OllyBarca. Согласно документам, соответствие влияет только на запрос населения. – andreimarinescu 7 December 2016 в 12:52
  • 3
    Я думаю, что это не отвечает на вопрос – Z.Alpha 16 December 2016 в 08:27
  • 4
    @Fabian, это не ошибка. Отфильтровывается только запрос населения (в данном случае fans). Возвращаемый фактический документ (который является Story, содержит fans как свойство) не затрагивается и не фильтруется. – EnKrypt 8 May 2018 в 15:15
  • 5
    Этот ответ, таким образом, неверен по причинам, указанным в комментариях. Любой, кто смотрит на это в будущем, должен быть осторожным. – EnKrypt 8 May 2018 в 15:17

Ответ @aaronheckmann работал для меня, но мне пришлось заменить return doc.tags.length; на return doc.tags != null;, потому что это поле содержит null, если оно не совпадает с условиями, записанными внутри заполнения. Итак, окончательный код:

query....
.exec(function(err, docs){
   docs = docs.filter(function(doc){
     return doc.tags != null;
   })
   // do stuff with docs
});
1
ответ дан HernanFila 20 August 2018 в 07:41
поделиться

После того, как я недавно столкнулся с той же проблемой, я придумал следующее решение:

Сначала найдите все ItemTags, где tagName либо «смешно», либо «политика» и возвращает массив ItemTag _ids.

Затем найдите элементы, которые содержат все ItemTag _ids в массиве тегов

ItemTag
  .find({ tagName : { $in : ['funny','politics'] } })
  .lean()
  .distinct('_id')
  .exec((err, itemTagIds) => {
     if (err) { console.error(err); }
     Item.find({ tag: { $all: itemTagIds} }, (err, items) => {
        console.log(items); // Items filtered by tagName
     });
  });
0
ответ дан OllyBarca 20 August 2018 в 07:41
поделиться

Попробуйте заменить

.populate('tags').where('tags.tagName').in(['funny', 'politics']) 

на

.populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )
15
ответ дан Sergio Tulentsev 20 August 2018 в 07:41
поделиться
  • 1
    Спасибо за ответ. Я считаю, что это означает только заполнение каждого элемента смешным или политическим, что не уменьшит родительский список. То, что мне действительно нравится, это только те вещи, которые имеют забавный или политический характер в их теге. – jschr 3 July 2012 в 14:27
  • 2
    Можете ли вы показать, как выглядит ваш документ? Coz a 'where' внутри массива тегов кажется мне действительной операцией. Мы просто не получаем синтаксис. Вы пытались полностью удалить это предложение «где» и проверить, что что-то возвращается? В качестве альтернативы, просто чтобы проверить, является ли запись «tags.tagName» синтаксически нормально, вы можете немного забыть об ошибке и попробовать свой запрос со встроенным массивом внутри документа «Item». – Aafreen Sheikh 3 July 2012 в 15:13
  • 3
Другие вопросы по тегам:

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