【问题标题】:MongoDB Pivot / Crosstab / Transpose with arbitrarily named and numbered columnsMongoDB Pivot / Crosstab / Transpose 任意命名和编号的列
【发布时间】:2018-03-15 11:16:02
【问题描述】:

我有一个包含导入的平面文件 CSV 的 Mongo 数据库。在 SQL 中,这个文件无疑应该被规范化:该文件每个句点包含一行,句点包含重复的信息。我创建了一个查询,该查询使用“推送”运算符将(部分)重复信息聚合到行内的单个子对象中。这模拟了标准化。我想做的是重组输出对象,以便子对象字典在顶层使用键和值。这在 SQL 中称为 Pivot 查询或 Crosstab 查询。在 Excel 中,它被称为转置。不管名称如何,我正在寻找的是能够获取键值对并将它们用作 Mongo 中的“列”。

由于 Mongo 和其他 NoSQL 数据库针对的是非规范化实现,我很惊讶这如此困难。

我正在尝试将以下 JSON 对象放入 Mongo:

[{ "_id": {"Date": "1/1/2018", "Type": "Green", "client_id": 1},
    "Sub_data": [{"sub_id" : 1}, {"sub_value": 2}]  },
 { "_id": {"Date": "1/1/2018", "Type": "Green", "client_id": 1},
    "Sub_data": [{"sub_id" : 2}, {"sub_value": 5}]  },
 { "_id": {"Date": "1/2/2018", "Type": "Green", "client_id": 1},
    "Sub_data": [{"sub_id" : 2}, {"sub_value": 4}]  },
 { "_id": {"Date": "1/1/2018", "Type": "Orange", "client_id": 1},
    "Sub_data": [{"sub_id" : 6}, {"sub_value": 7}]  }]

然后得到以下内容:

[{ "_id": {"Date": "1/1/2018", "Type": "Green", "client_id": 1},
    "1" : 2, "2":5},
 { "_id": {"Date": "1/2/2018", "Type": "Green", "client_id": 1},
    "2" : 4},
 { "_id": {"Date": "1/2/2018", "Type": "Orange", "client_id": 1},
    "6" : 7}]

请注意,我希望此结果有任意数量的列。我已经研究了一些似乎可以解决问题的解决方案(Array to objectAddFieldsReplaceRootSomething like a pivot using static columns),并且我已经阅读了multiple versions of this 'do it afterwards' code. 后处理是解决此问题的唯一方法吗?

注意:这是为了模仿in this Stack Overflow questionthis TechNet article 中描述的 SQL 服务器(和 Excel 等)功能。

汇总起来,我使用第一个答案的第二个选项的总管道如下所示:

db.rate_cards.aggregate(
        {
            "$group": {
                "_id": {
                    "date": "$date",
                    "start_date": "$start_date",
                    "end_date": "$end_date"

                },
                "code_data": {
                    "$push": {
                        "code_str": {"$substr" : ["$code",0,-1]},
                        "cpm": "$cpm"
                    }
                }
            }
        },
        {
            "$group":{
                "_id":"$_id",
                "data":{
                    "$mergeObjects":{
                        "$arrayToObject":[[
                                {
                                    "k":{"$let":{"vars":{"sub_id_elem":{"$arrayElemAt":["$code_data",0]}},"in":"$$sub_id_elem.code_str"}},
                                    "v":{"$let":{"vars":{"sub_value_elem":{"$arrayElemAt":["$code_data",1]}},"in":"$$sub_value_elem.cpm"}}
                                }
                            ]]
                        }
                }
            }
        },
        {"$replaceRoot":{"newRoot":{"$mergeObjects":["$_id",{"$arrayToObject":"$data"}]}}}

 )

请注意,这比我希望的要复杂一些,性能也更密集。它似乎声明了一个局部变量,使用了一个子句,等等。在尝试运行两个答案的(工作)实现时,NoSQL 助推器窒息试图扩展第 600 行。

原始数据集的略微编辑版本如下。请注意,原始查询中没有使用一些额外的字段,它们已被省略:

{
    "_id" : ObjectId("5a578d5c57d33b197004beed"),
    "date" : ISODate("2017-09-25T03:00:00.000+03:00"),
    "start_date" : ISODate("2017-09-25T03:00:00.000+03:00"),
    "end_date" : ISODate("2017-10-01T03:00:00.000+03:00"),
    "dp" : "M-Su 12m-6a",
    "dsc" : "Daypart",
    "net" : "val1",
    "place" : "loc1",
    "code" : 12,
    "cost" : 16.8
},
{
    "_id" : ObjectId("5a578d5c57d33b197004beee"),
    "date" : ISODate("2017-09-25T03:00:00.000+03:00"),
    "start_date" : ISODate("2017-09-25T03:00:00.000+03:00"),
    "end_date" : ISODate("2017-10-01T03:00:00.000+03:00"),
    "dp" : "M-Su 12m-6a",
    "dsc" : "Daypart",
    "net" : "val1",
    "place" : "loc3",
    "code" : 24,
    "cost" : 55.6
},
{
    "_id" : ObjectId("5a578d5c57d33b197004beef"),
    "date" : ISODate("2017-09-25T03:00:00.000+03:00"),
    "start_date" : ISODate("2017-09-25T03:00:00.000+03:00"),
    "end_date" : ISODate("2017-10-01T03:00:00.000+03:00"),
    "dp" : "M-Su 12n-6p",
    "dsc" : "Daypart",
    "net" : "val2",
    "place" : "loc2",
    "code" : 23,
    "cost" : 65.5
},
{
    "_id" : ObjectId("5a578d5c57d33b197004bef0"),
    "date" : ISODate("2017-09-25T03:00:00.000+03:00"),
    "start_date" : ISODate("2017-09-25T03:00:00.000+03:00"),
    "end_date" : ISODate("2017-10-01T03:00:00.000+03:00"),
    "dp" : "M-Su 6p-12m",
    "dsc" : "Daypart",
    "net" : "val2",
    "place" : "loc2",
    "code" : 23,
    "cost" : 101
}

【问题讨论】:

  • 您的输入文档不是有效的 json。你打算{ _id: {Date: "1/1/2018", Type: "Green", client_id: 1}, sub_id : 1, sub_value: 1 } 吗?
  • 好的。所以,是的,如果我有答案,我想我可以用它来解决我的特殊情况下更复杂的问题。但是,由于我正在为此树立声誉,因此我很想得到更彻底的答案。
  • 谢谢。 Sub_data 是否总是包含两个字段?一个是关键,另一个是价值?如果是,键确实需要是字符串 {"sub_id" : "1"}
  • 也刚刚意识到您的 _id 不是唯一的。可能你的意思是{ Date: "1/1/2018", Type: "Green", client_id: 1, "Sub_data": [{"sub_id" : "1"}, {"sub_value": 2}] }
  • Id 不是唯一的。 ID 是任意的。

标签: mongodb pivot aggregation-framework transpose


【解决方案1】:

好的,根据帖子和 cmets 中提供的信息,我创建了以下数据集。

注意:我做了一些更改。也都记录在 cmets 中。

将 _id 更改为在数据库中读取 my_id,因为 _id 字段名称是保留的并且是唯一索引的。

更改“sub_id”以将值存储为字符串类型。

db.test.insert(
[
 { "my_id": {"Date": "1/1/2018", "Type": "Green", "client_id": 1},
    "Sub_data": [{"sub_id" : "1"}, {"sub_value": 2}]  },
 { "my_id": {"Date": "1/1/2018", "Type": "Green", "client_id": 1},
    "Sub_data": [{"sub_id" : "2"}, {"sub_value": 5}]  },
 { "my_id": {"Date": "1/2/2018", "Type": "Green", "client_id": 1},
    "Sub_data": [{"sub_id" : "2"}, {"sub_value": 4}]  },
 { "my_id": {"Date": "1/1/2018", "Type": "Orange", "client_id": 1},
    "Sub_data": [{"sub_id" : "6"}, {"sub_value": 7}]  }
])

您需要使用$group$arrayToObject 来输出预期的格式。

$group$push 将所有值从子数据中推送,并将第一个元素映射到键,将第二个元素映射到值,然后将 $arrayToObject 格式化为指定的键值。

$mergeObjects 将 _id 与其余值合并。 $replaceRoot 将合并的文档提升到顶层。

db.test.aggregate([
  {"$group":{
    "_id":"$my_id",
    "data":{
      "$push":{
        "k":{"$let":{"vars":{"sub_id_elem":{"$arrayElemAt":["$Sub_data",0]}},"in":"$$sub_id_elem.sub_id"}},
        "v":{"$let":{"vars":{"sub_value_elem":{"$arrayElemAt":["$Sub_data",1]}},"in":"$$sub_value_elem.sub_value"}}
      }
    }
  }},
  {"$replaceRoot":{"newRoot":{"$mergeObjects":["$_id",{"$arrayToObject":"$data"}]}}}
])

输出:

{Date:"1/2/2018", "Type":"Orange", "client_id": 1", "6":7}
{Date:"1/1/2018", "Type":"Green", "client_id": 1", "2":4}
{Date:"1/2/2018", "Type":"Green", "client_id": 1", "1":2, "2":5}

或者,您可以使用$mergeObjects 作为累加器,在分组时合并对象。

db.test.aggregate([
  {"$group":{
    "_id":"$my_id","data":{
      "$mergeObjects":{
        "$arrayToObject":[[
          {
            "k":{"$let":{"vars":{"sub_id_elem":{"$arrayElemAt":["$Sub_data",0]}},"in":"$$sub_id_elem.sub_id"}},
            "v":{"$let":{"vars":{"sub_value_elem":{"$arrayElemAt":["$Sub_data",1]}},"in":"$$sub_value_elem.sub_value"}}
          }
        ]]
      }
    }
  }},
  {"$replaceRoot":{"newRoot":{"$mergeObjects":["$_id","$data"]}}}
])

【讨论】:

  • 谢谢。我没有时间立即测试它,但是看着它我认为它会起作用,我会尽快接受答案。
  • Np。这里不急。慢慢来。我只是有一些时间,所以我添加了一个答案。如果您有任何更改或疑虑,请随时发表评论,我很乐意为您提供帮助。
  • 好的,我已经对此进行了一些研究,但我对我的结果感到有些困惑。当我对测试数据库运行查询时,它可以工作,但是当我用 $_id 字段替换 $my_id 字段时(这样这可以是管​​道中的第二步和第三步,而不是第一步和第二步),我最终会出错使用这两种方法。这是预期的行为吗?
  • 您遇到什么错误?我可以看看你的查询吗?第一步会发生什么?
  • 问题已更新。我得到“$arrayToObject 需要一个数组输入,找到:对象”作为提供的第二个答案的错误,其中包含已编辑的文本。
猜你喜欢
  • 1970-01-01
  • 2021-08-25
  • 2021-01-15
  • 1970-01-01
  • 2018-05-23
  • 2018-10-11
  • 2019-01-19
  • 1970-01-01
  • 2019-02-26
相关资源
最近更新 更多