Манипуляция данными агрегации MongoDB — Объекты в массивы

#json #mongodb #elasticsearch #aggregation-framework #algolia

Вопрос:

У меня есть следующий пример набора данных

 [{
  "_id": {
    "$oid": "60f83d3cd66842301905aa77"
  },
  "id": 527438,
  "name": "CryptoPunk #4050",
  "asset_contract": {
    "name": "CryptoPunks",
    "address": "0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb"
  },
  "traits": [
    {
      "trait_type": "type",
      "value": "Male",
      "display_type": null,
      "max_value": null,
      "trait_count": 6039,
      "order": null
    },
    {
      "trait_type": "accessory",
      "value": "Mohawk",
      "display_type": null,
      "max_value": null,
      "trait_count": 441,
      "order": null
    },
    {
      "trait_type": "accessory",
      "value": "Earring",
      "display_type": null,
      "max_value": null,
      "trait_count": 2459,
      "order": null
    },
    {
      "trait_type": "accessory",
      "value": "Frown",
      "display_type": null,
      "max_value": null,
      "trait_count": 261,
      "order": null
    }
  ],
  "token_id": "4050",
  "permalink": "https://opensea.io/assets/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/4050",
  "background_color": null,
  "image_url": "https://lh3.googleusercontent.com/sO18rDQYhC5yIcj12RVsv31pbbsZo_2muQQbTJMQHn47EKGhnirs8mxzohm58HAZ7taBoe4pU6x1qntlExk_TtJ-",
  "image_preview_url": "https://lh3.googleusercontent.com/sO18rDQYhC5yIcj12RVsv31pbbsZo_2muQQbTJMQHn47EKGhnirs8mxzohm58HAZ7taBoe4pU6x1qntlExk_TtJ-=s250",
  "animation_url": null,
  "vault_contract": "0x269616d549d7e8eaa82dfb17028d0b212d11232a"
},{
  "_id": { "$oid": "60f83d3cbc3f0161da2141f7" },
  "id": 17736625,
  "name": "OJ Simpson",
  "asset_contract": {
    "name": "Hashmasks",
    "address": "0xc2c747e0f7004f9e8817db2ca4997657a7746928"
  },
  "traits": [
    {
      "trait_type": "Character",
      "value": "Male",
      "display_type": null,
      "max_value": null,
      "trait_count": 8659,
      "order": null
    },
    {
      "trait_type": "Mask",
      "value": "Doodle",
      "display_type": null,
      "max_value": null,
      "trait_count": 2187,
      "order": null
    },
    {
      "trait_type": "Eye Color",
      "value": "Dark",
      "display_type": null,
      "max_value": null,
      "trait_count": 7419,
      "order": null
    },
    {
      "trait_type": "Item",
      "value": "No Item",
      "display_type": null,
      "max_value": null,
      "trait_count": 14533,
      "order": null
    },
    {
      "trait_type": "Skin Color",
      "value": "Dark",
      "display_type": null,
      "max_value": null,
      "trait_count": 3784,
      "order": null
    },
    {
      "trait_type": "Token ID",
      "value": 3535,
      "display_type": "number",
      "max_value": null,
      "trait_count": 0,
      "order": null
    },
    {
      "trait_type": "Background",
      "value": "Doodle",
      "display_type": null,
      "max_value": null,
      "trait_count": 5538,
      "order": null
    }
  ],
  "token_id": "3535",
  "permalink": "https://opensea.io/assets/0xc2c747e0f7004f9e8817db2ca4997657a7746928/3535",
  "background_color": null,
  "image_url": "https://lh3.googleusercontent.com/NZQu7CNjgJ_1uhbUVwEb-14rZPJmPCaqaXy0qnUpgm5Qll0BvmmF7tPMjBhFH6ZZp_qzOPxHi0NFmRkOjHoBQ0BODcWI8NlyBXLu",
  "image_preview_url": "https://lh3.googleusercontent.com/NZQu7CNjgJ_1uhbUVwEb-14rZPJmPCaqaXy0qnUpgm5Qll0BvmmF7tPMjBhFH6ZZp_qzOPxHi0NFmRkOjHoBQ0BODcWI8NlyBXLu=s250",
  "animation_url": null,
  "vault_contract": "0xc7a8b45e184138114e6085c82936a8db93dd156a"
}]
 

который я хотел бы обновить до

 [{
    "_id": {
      "$oid": "60f83d3cd66842301905aa77"
    },
    "id": 527438,
    "name": "CryptoPunk #4050",
    "asset_contract": {
      "name": "CryptoPunks",
      "address": "0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb"
    },
    "traits":
      {
        "type": "Male",
        "accessory": ["Mohawk", "Earing", "Frown"], 
      },
    "token_id": "4050",
    "permalink": "https://opensea.io/assets/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/4050",
    "background_color": null,
    "image_url": "https://lh3.googleusercontent.com/sO18rDQYhC5yIcj12RVsv31pbbsZo_2muQQbTJMQHn47EKGhnirs8mxzohm58HAZ7taBoe4pU6x1qntlExk_TtJ-",
    "image_preview_url": "https://lh3.googleusercontent.com/sO18rDQYhC5yIcj12RVsv31pbbsZo_2muQQbTJMQHn47EKGhnirs8mxzohm58HAZ7taBoe4pU6x1qntlExk_TtJ-=s250",
    "animation_url": null,
    "vault_contract": "0x269616d549d7e8eaa82dfb17028d0b212d11232a"
  },{
  "_id": { "$oid": "60f83d3cbc3f0161da2141f7" },
  "id": 17736625,
  "name": "OJ Simpson",
  "asset_contract": {
    "name": "Hashmasks",
    "address": "0xc2c747e0f7004f9e8817db2ca4997657a7746928"
  },
  "traits": {
      "character": "Male",
      "mask": "Doodle",
      "eye_color": "Dark",
      "item": "No Item",
      "skin_color": "Dark",
      "token_id": 3535,
      "background": "Doodle",
    },
  "token_id": "3535",
  "permalink": "https://opensea.io/assets/0xc2c747e0f7004f9e8817db2ca4997657a7746928/3535",
  "background_color": null,
  "image_url": "https://lh3.googleusercontent.com/NZQu7CNjgJ_1uhbUVwEb-14rZPJmPCaqaXy0qnUpgm5Qll0BvmmF7tPMjBhFH6ZZp_qzOPxHi0NFmRkOjHoBQ0BODcWI8NlyBXLu",
  "image_preview_url": "https://lh3.googleusercontent.com/NZQu7CNjgJ_1uhbUVwEb-14rZPJmPCaqaXy0qnUpgm5Qll0BvmmF7tPMjBhFH6ZZp_qzOPxHi0NFmRkOjHoBQ0BODcWI8NlyBXLu=s250",
  "animation_url": null,
  "vault_contract": "0xc7a8b45e184138114e6085c82936a8db93dd156a"
}]
 

Логика, лежащая в основе этого, была бы

  • Посмотрите на объекты массива признаков
  • получите значение типа признака и создайте новый ключ, используя имя в нижнем регистре (подчеркивания для пробелов).
  • Установите значение нового ключа равным значению "value"

Так,

       "trait_type": "type",
      "value": "Male",

//becomes

            "type": "Male"
 
  • Если существует несколько экземпляров одного и того же типа признака, создайте массив значений.

Так,

 {
      "trait_type": "accessory",
      "value": "Mohawk",
      "display_type": null,
      "max_value": null,
      "trait_count": 441,
      "order": null
    },
    {
      "trait_type": "accessory",
      "value": "Earring",
      "display_type": null,
      "max_value": null,
      "trait_count": 2459,
      "order": null
    },

// becomes

            "accessory": ["Mohawk", "Earring"]
 

Ответ №1:

Запрос

  • это обновление агрегации, даже если конвейер не позволяет нам использовать такие этапы, как поиск групп и т. Д., Которые здесь используются. (вы можете использовать $out и заменять коллекцию после или $merge для замены документов(аналогично обновлению))
  • первая карта
    • для каждого признака(элемента документа признаков) он преобразует его в массив
      [["trait_type": "type"] ["value": "Male"] ["display_type": null] ...]
    • уменьшите этот массив, чтобы создать из них только 1 документ
      {"type" "type","value" :"Male"} (делает также это в нижнем регистре и «_»)
  • Теперь черт ее побери
     "traits": [
      {
        "type": "type",
        "value": "Male"
      },
      {
        "type": "accessory",
        "value": "Mohawk"
      },
      {
        "type": "accessory",
        "value": "Earring"
      },
      {
        "type": "accessory",
        "value": "Frown"
      }
    ]
     
  • поиск с помощью фиктивной коллекции [{}] (мы делаем это, чтобы создать группу внутри этого массива) похож на трюк, который позволяет нам использовать операторы этапа внутри 1 документа
    • конвейер поиска разматывается и группируется по типу
     "traits": [
      {
        "values": [
          "Mohawk",
          "Earring",
          "Frown"
        ],
        "type": "accessory"
      },
      {
        "values": [
          "Male"
        ],
        "type": "type"
      }
    ]
     
    • затем его корень замены должен принять значение типа, сделать его именем поля и значениями в качестве значения (if size=1 removes the array)
  • После поиска у нас есть
     "traits": [
      {
        "accessory": [
          "Mohawk",
          "Earring",
          "Frown"
        ]
      },
      {
        "type": "Male"
      }
    ]
     
  • поэтому все, что нам нужно сделать, это уменьшить эти черты и объединить объекты
    (ключи в любом случае уникальны, потому что мы сгруппированы по ним)
  • и мы получаем ожидаемый результат (по крайней мере, я думаю, что все в порядке).

Тестовый код здесь

 db.collection.aggregate([
  {
    "$set": {
      "traits": {
        "$map": {
          "input": "$traits",
          "as": "t",
          "in": {
            "$reduce": {
              "input": {
                "$map": {
                  "input": {
                    "$objectToArray": "$t"
                  },
                  "as": "m",
                  "in": [
                    "$m.k",
                    "$m.v"
                  ]
                }
              },
              "initialValue": {},
              "in": {
                "$let": {
                  "vars": {
                    "type_value": "$value",
                    "ta": "$this"
                  },
                  "in": {
                    "$let": {
                      "vars": {
                        "key": {
                          "$arrayElemAt": [
                            "$ta",
                            0
                          ]
                        },
                        "value": {
                          "$arrayElemAt": [
                            "$ta",
                            1
                          ]
                        }
                      },
                      "in": {
                        "$switch": {
                          "branches": [
                            {
                              "case": {
                                "$eq": [
                                  "$key",
                                  "value"
                                ]
                              },
                              "then": {
                                "$mergeObjects": [
                                  "$type_value",
                                  {
                                    "value": "$value"
                                  }
                                ]
                              }
                            },
                            {
                              "case": {
                                "$eq": [
                                  "$key",
                                  "trait_type"
                                ]
                              },
                              "then": {
                                "$mergeObjects": [
                                  "$type_value",
                                  {
                                    "type": {
                                      "$replaceAll": {
                                        "input": {
                                          "$toLower": "$value"
                                        },
                                        "find": " ",
                                        "replacement": "_"
                                      }
                                    }
                                  }
                                ]
                              }
                            }
                          ],
                          "default": "$type_value"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  {
    "$lookup": {
      "from": "dummy",
      "let": {
        "traits": "$traits"
      },
      "pipeline": [
        {
          "$set": {
            "traits": "$traits"
          }
        },
        {
          "$unwind": {
            "path": "$traits"
          }
        },
        {
          "$replaceRoot": {
            "newRoot": "$traits"
          }
        },
        {
          "$group": {
            "_id": "$type",
            "values": {
              "$push": "$value"
            }
          }
        },
        {
          "$set": {
            "type": "$_id"
          }
        },
        {
          "$project": {
            "_id": 0
          }
        },
        {
          "$replaceRoot": {
            "newRoot": {
              "$cond": [
                {
                  "$eq": [
                    {
                      "$size": "$values"
                    },
                    1
                  ]
                },
                {
                  "$arrayToObject": {
                    "$let": {
                      "vars": {
                        "pair": [
                          [
                            "$type",
                            {
                              "$arrayElemAt": [
                                "$values",
                                0
                              ]
                            }
                          ]
                        ]
                      },
                      "in": "$pair"
                    }
                  }
                },
                {
                  "$arrayToObject": {
                    "$let": {
                      "vars": {
                        "pair": [
                          [
                            "$type",
                            "$values"
                          ]
                        ]
                      },
                      "in": "$pair"
                    }
                  }
                }
              ]
            }
          }
        }
      ],
      "as": "traits"
    }
  },
  {
    "$set": {
      "traits": {
        "$mergeObjects": "$traits"
      }
    }
  }
])
 

Комментарии:

1. Большое спасибо @Takis_. Коллекция, в которой я запускаю это, содержит 57 тысяч записей, и у меня нет возможности добавить dummy их в конец набора записей. Я также получаю Invalid $set :: caused by :: Unrecognized expression '$setField' , когда пытаюсь запустить его с оболочкой Mongo (и на агрегации компаса)

2. В реальном мире это будет использоваться для выполнения агрегирования одной коллекции и переноса выходных данных в другую коллекцию, db.rawCollection.aggregation() а также для вывода всех результатов для обновления вторичной коллекции db.cleanCollection . Это будет выполняться с помощью скрипта каждые 5 минут.

3. Для этого вам понадобится еще 1 коллекция в базу данных с именем dummy , в ней будет 1 пустой документ collection=[{}] (или любое имя, если вы также измените его по запросу). Эта $setField ошибка вызвана тем, что вы не запускаете MongoDB 5, все в порядке, есть другой способ, я обновлю запрос. В вашем случае вам необходимо $out сохранить в другой коллекции.

4. я обновил запрос, удалил $setField , заменил его на $mergeObjects его штраф, пустышка-это новая коллекция в вашей базе данных, в которой всего 1 документ, пустой документ dummy=[{}] , вам просто нужны права для создания этой коллекции. Вы можете дать ему любое имя, какое захотите, но вы также должны заменить это имя $lookup . Чтобы $out узнать, как использовать его в вашем драйвере, он выводит результат в новой коллекции(без обновления) новая коллекция станет результатом агрегирования.

5. Спасибо, Такис, я отменил это еще на 50 очков и передал вам, большое спасибо за вашу постоянную поддержку.