【问题标题】:MongoDB - Calculate time difference between documents based on the existence of a value inside an array?MongoDB - 根据数组中是否存在值计算文档之间的时间差?
【发布时间】:2021-06-27 08:23:17
【问题描述】:

我正在尝试计算两个文档之间的时间差,但我不确定如何根据数组中是否存在值来执行此操作。

让我更详细地解释一下。假设我有五个文档:A, B, C, D, E 在一个集合中。

每个文档都有referenceKeytimestamppersons 字段。
persons 数组中的每个元素都有 personType 字段以及其他字段:

A: { referenceKey: 1, timestamp: ISODate, persons: [ { personType: "ALICE", ... }, { personType: "BOB", ... } ] }
B: { referenceKey: 1, timestamp: ISODate, persons: [ { personType: "ALICE", ... }, { personType: "BOB", ... } ] }
C: { referenceKey: 1, timestamp: ISODate, persons: [ { personType: "BOB", ... } ] }
D: { referenceKey: 1, timestamp: ISODate, persons: [ { personType: "ALICE", ... }, { personType: "BOB", ... } ] }
E: { referenceKey: 1, timestamp: ISODate, persons: [ { personType: "BOB", ... } ] }

我想要实现的是计算类型为ALICE 的人每次访问花费了多少时间。
换句话说,这应该计算并返回一个时间差数组:

[{ timeSpent: C.timestamp - A.timestamp }, { timeSpent: E.timestamp - D.timestamp }]

这是一个要测试的示例集合:

[
  {
    timestamp: ISODate("2019-04-12T20:00:00.000Z"),
    referenceKey: 1,
    persons: [
      {
        personType: "BOB"
      },
      {
        personType: "ALICE"
      }
    ]
  },
  {
    timestamp: ISODate("2019-04-12T20:10:00.000Z"),
    referenceKey: 1,
    persons: [
      {
        personType: "BOB"
      }
    ]
  },
  {
    timestamp: ISODate("2019-04-12T21:00:00.000Z"),
    referenceKey: 1,
    persons: [
      {
        personType: "BOB"
      },
      {
        personType: "ALICE"
      }
    ]
  },
  {
    timestamp: ISODate("2019-04-12T21:15:00.000Z"),
    referenceKey: 1,
    persons: [
      {
        personType: "BOB"
      }
    ]
  },
  {
    timestamp: ISODate("2019-04-12T21:20:00.000Z"),
    referenceKey: 1,
    persons: [
      {
        personType: "BOB"
      }
    ]
  },
  {
    timestamp: ISODate("2019-04-12T21:45:00.000Z"),
    referenceKey: 1,
    persons: [
      {
        personType: "BOB"
      },
      {
        personType: "ALICE"
      }
    ]
  },
  {
    timestamp: ISODate("2019-04-12T22:05:00.000Z"),
    referenceKey: 1,
    persons: [
      {
        personType: "BOB"
      },
      {
        personType: "ALICE"
      }
    ]
  },
  {
    timestamp: ISODate("2019-04-12T23:00:00.000Z"),
    referenceKey: 1,
    persons: [
      {
        personType: "BOB"
      }
    ]
  },
  {
    timestamp: ISODate("2019-04-12T18:30:00.000Z"),
    referenceKey: 2,
    persons: [
      {
        personType: "BOB"
      },
      {
        personType: "JOHN"
      }
    ]
  }
]

我想我可以基于人员类型ALICE 的存在使用$in 添加一个新的布尔字段hasAlice。但问题是应该为每次访问计算花费的时间,所以我不能只使用$reduce 来计算总时间。我可以通过hasAlice字段更改以某种方式使用$group,然后使用$reduce吗?

到目前为止我已经尝试过(但失败了):

db.collection.aggregate([
  {
    "$match": { // I also filter by timestamp and referenceKey but it is not relevant to the problem
      timestamp: {
        "$gte": ISODate("2019-04-12T00:00:00.000Z"),
        "$lt": ISODate("2019-04-12T23:59:00.000Z")
      },
      referenceKey: 1
    }
  },
  {
    "$project": {
      _id: 0,
      timestamp: 1,
      hasAlice: {
        "$in": [
          "ALICE",
          "$persons.personType"
        ]
      }
    }
  },
  {
    "$sort": {
      timestamp: 1
    }
  }
])

我想得到什么:

[
 { timeSpent: 10 }, // in minutes
 { timeSpent: 15 },
 { timeSpent: 75 },
]

我在运行聚合时实际得到的结果:

[
  {
    "hasAlice": true, // 1. visit starts
    "timestamp": ISODate("2019-04-12T20:00:00Z")
  },
  {
    "hasAlice": false, // 1. visit ends
    "timestamp": ISODate("2019-04-12T20:10:00Z")
  },
  {
    "hasAlice": true, // 2. visit starts
    "timestamp": ISODate("2019-04-12T21:00:00Z")
  },
  {
    "hasAlice": false, // 2. visit ends
    "timestamp": ISODate("2019-04-12T21:15:00Z")
  },
  {
    "hasAlice": false,
    "timestamp": ISODate("2019-04-12T21:20:00Z")
  },
  {
    "hasAlice": true, // 3. visit starts
    "timestamp": ISODate("2019-04-12T21:45:00Z")
  },
  {
    "hasAlice": true, // NOTE: there are some misleading documents such as these (e.g. document B)
    "timestamp": ISODate("2019-04-12T22:05:00Z")
  },
  {
    "hasAlice": false, // 3. visit ends
    "timestamp": ISODate("2019-04-12T23:00:00Z")
  }
]

我不知道我的逻辑是否正确,或者我能否以某种方式减少这些文档来计算每次访问所花费的时间。但任何帮助表示赞赏。 提前致谢。

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    我终于弄明白了,感谢this somewhat similar post

    诀窍是使用$lookup自身 左加入集合,然后从加入的集合中获取第一个不包含人类型ALICE 的元素。加入集合中的这个元素为我们提供了每次访问的结束(即leaveTimestamp)。

    从那里,我们可以在每次访问结束时进一步$group,并仅选择匹配文档中的第一个timestamp,以便我们可以消除任何误导性文档(例如文档B)。

    这是完整的aggregate 管道:

    db.collection.aggregate([
      {
        "$match": {
          timestamp: {
            "$gte": ISODate("2019-04-12T00:00:00.000Z"),
            "$lt": ISODate("2019-04-12T23:59:00.000Z")
          },
          referenceKey: 1,
          persons: {
            "$elemMatch": {
              "personType": "ALICE"
            }
          }
        }
      },
      {
        "$project": {
          timestamp: 1,
          hasAlice: {
            "$in": [
              "ALICE",
              "$persons.personType"
            ]
          }
        }
      },
      {
        $lookup: {
          from: "collection",
          let: {
            root_id: "$_id"
          },
          pipeline: [
            {
              "$match": {
                timestamp: {
                  "$gte": ISODate("2019-04-12T00:00:00.000Z"),
                  "$lt": ISODate("2019-04-12T23:59:00.000Z")
                },
                referenceKey: 1
              }
            },
            {
              "$project": {
                timestamp: 1,
                hasAlice: {
                  "$in": [
                    "ALICE",
                    "$persons.personType"
                  ]
                }
              }
            },
            {
              $match: {
                $expr: {
                  $gt: [
                    "$_id",
                    "$$root_id"
                  ]
                }
              }
            }
          ],
          as: "tmp"
        }
      },
      {
        "$project": {
          _id: 1,
          timestamp: 1,
          tmp: {
            $filter: {
              input: "$tmp",
              as: "item",
              cond: {
                $eq: [
                  "$$item.hasAlice",
                  false
                ]
              }
            }
          }
        }
      },
      {
        "$project": {
          timestamp: 1,
          leaveTimestamp: {
            $first: "$tmp.timestamp"
          }
        }
      },
      {
        "$group": {
          "_id": "$leaveTimestamp",
          "timestamp": {
            "$min": "$timestamp"
          },
          leaveTimestamp: {
            $first: "$leaveTimestamp"
          }
        }
      },
      {
        $addFields: {
          "visitingTime": {
            $dateToString: {
              date: {
                $toDate: {
                  $subtract: [
                    "$leaveTimestamp",
                    "$timestamp"
                  ]
                }
              },
              format: "%H-%M-%S"
            }
          }
        }
      },
      {
        "$sort": {
          "timestamp": 1
        }
      }
    ])
    

    Mongoplayground

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多