Эффективные запросы MongoDB для получения отчетов из одной коллекции и вставки в другую - PullRequest
0 голосов
/ 09 марта 2020

Я хотел бы проконсультироваться о том, как решить конкретную задачу c, используя MongoDB. Я сделаю все возможное, чтобы объяснить всю картину, чтобы не было проблемы XY. Это будет немного долго, поэтому я ценю всех, кто доберется до конца топи c. У меня есть коллекция (давайте назовем это Cars), которая содержит отчеты. Все отчеты содержат три основных поля:

  • name.
  • color.
  • timestamp.

Эти отчеты содержат другие поля также, но они неуважительны для моего вопроса. Есть только одно поле, которое я хотел бы объяснить - new_start. Если в отчете находится new_start (имеется в виду new_start: 1), тогда я игнорирую все отчеты, которые имеют одинаковое имя и цвет, но они являются более старыми, чем отчет, содержащий new_start (что означает, что отметка времени меньше, чем хотел отчеты). Я постараюсь объяснить на примере. Пожалуйста, рассмотрите следующие отчеты:

report1 - name: ABC, color: black, timestamp: 1581946973
report2 - name: ABC, color: black, timestamp: 1581946963
report3 - name: ABC, color: black, timestamp: 1581946953, new_start: 1
report4 - name: ABC, color: black, timestamp: 1581946943
report5 - name: ABC, color: black, timestamp: 1581946933, new_start: 1
report6 - name: ABC, color: black, timestamp: 1581946923

Эти отчеты отсортированы по меткам времени (от самых новых до самых старых) и все имеют одинаковые имя и цвет. Итак, отчеты, которые меня интересуют:

report1 - name: ABC, color: black, timestamp: 1581946973
report2 - name: ABC, color: black, timestamp: 1581946963
report3 - name: ABC, color: black, timestamp: 1581946953, new_start: 1

Обратите внимание, что если бы не было отчетов с new_start, я бы обработал все из них.

Я попытался написать запрос / код это делает для меня следующую логику c: Для всех отчетов, которые содержат с тем же именем и цветом , получают все отчеты. Если один из отчетов содержит new_start, то он должен возвращать отчеты из самого нового до этого отчета.

То, что я пробовал (используя python и pymon go):

  1. Получить все отчеты:

    records = db.query(collection_name="cars", query={})
    
  2. Выполнить итерацию всех отчетов и каждого из них, внести изменения.

    for record in records:
        other_line_records = db.query(collection_name="cars", query={'name': record['name'], 'color': record['color'], '_id': {'$ne': record['_id']}})
        # changes
    

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

Здесь указывается цель всей этой операции - я хотел бы объединить эти отчеты в один основной отчет и вставить его в другую коллекцию merged_cars. Логотип слияния c Я сделаю сам после того, как получу необходимые отчеты, но я буду рад получить помощь по другим вопросам:

  1. В моем предложенном способе это объединит эти отчеты в бесконечное число. Это означает, что merged_cars будет иметь одни и те же отчеты снова и снова. Мне нужно как-то отслеживать объединенные отчеты. Я хотел создать поле merged_ids, содержащее массив всех объединенных идентификаторов. Таким образом, я буду знать, если есть новый отчет, который я должен добавить к слиянию. Но как мне эффективно проверить, объединен ли отчет? Кроме того, это правильное решение этой проблемы? Кажется немного странным сохранять эти идентификаторы.

  2. В настоящее время я просто перебираю все отчеты, фактически не используя возможности агрегирования MongoDB. Я уверен, что есть более разумный и эффективный способ, поэтому мне не придется перебирать все объединенные отчеты снова и снова. Но я не могу понять, как это сделать.

  3. Как мне принять во внимание new_start?

Подводя итог, из-за отсутствия у меня опыта агрегирования MongoDB я не могу найти эффективный способ решения этой проблемы. Я буду рад увидеть некоторые предложения (просьба привести примеры, чтобы было легче понять), как решить эту проблему. Как видите, моя главная проблема - выяснить, как должны выглядеть эти запросы.

1 Ответ

1 голос
/ 09 марта 2020

С помощью агрегации MongoDb мы можем добиться этого.

Объяснение

  1. Мы $group все записи с одинаковыми name и color и храним root документов во временное поле с именем data
  2. Начиная с data, мы находим все документы с new_start + с $reduce, возвращаем Greates timestamp.
  3. С $filter мы сопоставить все записи с max_result <= <code>item i timestamp
  4. С $unwind мы сглаживаем отфильтрованные data
  5. $replaceRoot помогает использовать изменения root структура с data i поддокументом

db.Cars.aggregate([
  {
    $group: {
      _id: {
        name: "$name",
        color: "$color"
      },
      data: {
        $push: "$$ROOT"
      }
    }
  },
  {
    $addFields: {
      max_timestamp: {
        $reduce: {
          input: "$data",
          initialValue: 0,
          in: {
            $cond: [
              {
                $and: [
                  {
                    $eq: [
                      "$$this.new_start",
                      1
                    ]
                  },
                  {
                    $gt: [
                      "$$this.timestamp",
                      "$$value"
                    ]
                  }
                ]
              },
              "$$this.timestamp",
              "$$value"
            ]
          }
        }
      }
    }
  },
  {
    $addFields: {
      data: {
        $filter: {
          input: "$data",
          cond: {
            $lte: [
              "$max_timestamp",
              "$$this.timestamp"
            ]
          }
        }
      }
    }
  },
  {
    $unwind: "$data"
  },
  {
    $replaceRoot: {
      newRoot: "$data"
    }
  }
])

MongoPlayground

Если добавить $merge оператор как последний шаг, отчеты будут вставлены в merged_cars коллекцию

{
  $merge: {
    into: "merged_cars",
    on: "_id",
    whenMatched: "replace",
    whenNotMatched: "insert"
  }
}

Pymon go

from pymongo import MongoClient

db = MongoClient('mongodb://localhost:27017').test

pipeline = [
 {
    '$group': {
      '_id': {
       'name': "$name",
        'color': "$color"
      },
      'data': {
        '$push': "$$ROOT"
      }
    }
  },
  {
    '$addFields': {
      'max_timestamp': {
        '$reduce': {
          'input': "$data",
          'initialValue': 0,
          'in': {
            '$cond': [
              {
                '$and': [
                  {
                    '$eq': [
                      "$$this.new_start",
                      1
                    ]
                  },
                  {
                    '$gt': [
                      "$$this.timestamp",
                      "$$value"
                    ]
                  }
                ]
              },
              "$$this.timestamp",
              "$$value"
            ]
          }
        }
      }
    }
  },
  {
    '$addFields': {
      'data': {
        '$filter': {
          'input': "$data",
          'cond': {
            '$lte': [
              "$max_timestamp",
              "$$this.timestamp"
            ]
          }
        }
      }
    }
  },
  {
    '$unwind': "$data"
  },
  {
    '$replaceRoot': {
      'newRoot': "$data"
    }
  }
]


print(list(db.cars.aggregate(pipeline)))

[{'_ id': ObjectId ('5e658bb6fd9da8cfcc2f5a08'), 'name': 'AB C', 'color': 'black', 'timestamp': 1581946973}, {'_id': ObjectId ('5e658bb6fd9da8cfcc2f5a09'), 'name C ',' color ':' black ',' timestamp ': 1581946963}, {' _i d ': ObjectId (' 5e658bb6fd9da8cfcc2f5a0a '),' name ':' AB C ',' color ':' black ',' timestamp ': 1581946953,' new_start ': 1}]

...