#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 очков и передал вам, большое спасибо за вашу постоянную поддержку.