获取ID数组中所有具有匹配ID的元素

编程入门 行业动态 更新时间:2024-10-19 08:55:37
本文介绍了获取ID数组中所有具有匹配ID的元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我在Express/React Web应用程序中使用Mongoose,并将数据存储在Mongo数据库中.

I am using Mongoose in my Express / React web application and I'm storing data in a Mongo Database.

我将歌曲"存储在歌曲集中,例如,用户有一个数组,其中包含他听过的歌曲的ID.

I store 'songs' in a songs collection and the user has an array containing the ids of the songs he listened to for example.

然后要渲染他一直在听的内容,我必须将带有歌曲ID的数组与来自歌曲集合的歌曲ID关联起来.

Then to render what he's been listening to, I have to link the array with song ids with song ids from the songs collection.

我当前正在使用

song.find({_id: {$in: ids}}).exec(callback)

提取所有与"ids"数组中的id相匹配的歌曲.如果用户一次又一次地听这首歌,"ids"数组可能会多次包含相同的id.

to fetch all the songs matching the ids in the 'ids' array. The 'ids' array may contain the same id several times if the user listened the song again and again.

问题是猫鼬只返回一次对应于id的歌曲,因此该歌曲不会多次显示.有没有一种方法,我不能告诉猫鼬在重复ID的情况下将尽可能多的对象传递给回调?

The thing is that mongoose returns only once the song corresponding to the id and thus the song is not displayed multiple times. Is there a way I cant tell mongoose to pass to the callback as many object as the id is repeated ?

总结:

ids: ['a', 'a', 'a', 'b', 'c'] song.find({_id: {$in: ids}}).exec(callback) dataPassedToCallback: [songA, songB, songC]

期待

dataPassedToCallback: [songA, songA, songA, songB, songC]

推荐答案

对于您可能要问的问题,这里似乎有几种可能的情况.

There seem to be a couple of possible cases here about what you could be asking.

从 $in 的角度来看,MongoDB确实视为 $or 条件的简写"因此,这两个语句实际上是相同的:

From the perspective of $in, MongoDB really looks at this as "shorthand" for an $or condition so effectively these two statements are the same:

"field": { "$in": ["a", "a", "a", "b", "c"] }

"$or": [ { "field": "a" }, { "field": "a" }, { "field": "a" }, { "field": "b" }, { "field": "c" } ]

至少在他们选择的文档"方面,这仅仅是数据库实际包含的个人"文档. $in 实际上是最佳选择,因为查询引擎可以看到OR在相同的键"上,这节省了执行查询计划的成本.

At least in terms of the "documents they select" which is merely the "individual" documents the database actually contains. The $in is actually a little more optimal here because the query engine can see that the OR is on the "same key", and this saves some cost in the query plan execution.

此外,只是要注意实际的查询计划执行",可以使用 explain() 实际上将显示重复"条目已被删除:

Also, just to note the actual "query plan execution" which can be viewed with explain() will actually show the "duplicate" entries are removed anyway:

"filter" : { "_id" : { "$in" : [ "a", "b", "c" ] } },

值得注意的是,尽管 $or 并不会真正删除条件,这实际上就是为什么 $in 作为此处的查询效率更高,甚至 $or 仍然不会多次获得相同的匹配文档.

Notably though the $or would not actually remove the conditions, which is really just another point of why $in is more efficient as a query here, and even so the $or still is not going to get the same matching document more than once.

但是,从选择"的角度来看,然后要求相同的条件多次"不会导致检索多次". 参数顺序也是如此,因为它们对如何从数据库本身返回订单.从数据库"的角度实际检索多份副本"也没有任何意义,因为这基本上是多余的.

But from the perspective of "selection", then asking for the same criteria "multiple times" does not result in retrieving "multiple times". The same is true of the order of arguments in that they have no effect on how the order is returned from the database itself. Nor would it really make any sense to actually retrieve "multiple copies" from a "database" perspective since this is basically redundant.

您真正要问的是我有一个列表,现在我想用数据库中的文档替换这些值" .这实际上是一个合理的要求,而且相对容易实现.实际的实现实际上仅取决于您从何处获取数据.

Instead what you are really asking is "I have a list, now I want to substitute those values with documents from the database". That is actually a reasonable ask, and relatively easy to achieve. Your actual implementation really just depends on where you get the data from.

如果您有一个来自外部来源的列表"并且想要数据库对象,那么逻辑上的事情就是返回匹配的文档,然后用返回的文档替换到您的有序列表中.

In the case you have a "list" from an external source and want the database objects, then the logical thing to do is return the matching documents and then substitute into your ordered list with the returned documents.

在现代NodeJS环境中,这很简单:

In modern NodeJS environments this is as simple as:

let list = ["a", "a", "a", "b", "c"]; let songs = await Song.find({ "_id": { "$in": list } }); songs = list.map(e => songs.find(s => s._id === e));

现在songs列表中list中的每个项目都有一个条目,其顺序相同,但实际上是返回的真实数据库文档.

And now the songs list has an entry for each item in list in the same order but actually with the real database document as returned.

如果要处理_id中的实际ObjectId值,则最好投射"列表中的值并使用 ObjectId.equals() 函数来比较对象":

If you are dealing with actual ObjectId values within _id, then it's better to "cast" the values in the list and use the ObjectId.equals() function to compare the "objects":

// of course not "valid" ObjectId here; but let list = ["a", "a", "a", "b", "c"].map(e => ObjectId(e)); // casting let songs = await Song.find({ "_id": { "$in": list } }); songs = list.map(e => songs.find(s => s._id.equals(e))); // compare

没有在NodeJS 8.x发行版中默认启用async/await关键字或在早期版本中显式启用的情况下,则标准的Promise解析将起作用:

Without the async/await keywords enabled by default from NodeJS 8.x releases or enabling explicitly in earlier versions, then standard Promise resolution will do:

// of course not "valid" ObjectId here; but let list = ["a", "a", "a", "b", "c"].map(e => ObjectId(e)); // casting Song.find({ "_id": { "$in": list } }).then(songs => list.map(e => songs.find(s => s._id.equals(e))) // compare ).then(songs => { // do something })

或带有回调

let list = ["a", "a", "a", "b", "c"].map(e => ObjectId(e)); // casting Song.find({ "_id": { "$in": list } },(err,songs) => { songs = list.map(e => songs.find(s => s._id.equals(e))); // compare })

请注意,这与问题注释中提到的映射功能"有很大不同.当您已经有一个" 请求返回的结果时,多次请求数据库" 毫无意义.因此,请执行以下操作:

Note that this is significantly different to "mapping the function" as was mentioned in comment on the question. There really is no point in "asking the database multiple times" when you already have the results returned from "one" request. Therefore doing something like:

let songs = await Promise.all(list.map(_id => Song.findById(_id)));

这是非常可怕的冗余,仅为了执行请求而创建其他请求和开销.因此,您不会这样做,而是执行一个"请求并重新映射"到列表,因为这样做最有意义.

That's quite horribly redundant and creating additional requests and overhead just for the sake of doing requests. So you would not do that and instead do the "one" request and "re-map" onto the list as that simply makes the most sense.

在实际实现中,最重要的是,在此API级别上,这种重新映射"仍然没有任何位置.真正应该发生的是,理想地"实际上是您的前端"使用唯一" _id列表""仅" 发出请求.然后,请求通过允许数据库进行响应并简单地返回匹配的文档.作为工作流程:

More to the point of the actual implementation you have though is that this "re-mapping" still really has no place at this level of the API. What should really be happening is "ideally" your "front end" actually makes the request with the "unique" _id list "only". Then the request is passed through allowing the database to respond and simply return the matching documents. As a workflow:

Front End Back End Front End --------- ------------ ------- List -> Unique List -> Endpoint => Database => Endpoint -> Doc List -> Remap List

因此,从服务器端点"和数据库"的角度来看,返回的文档"应该完全由它们处理.通过删除所有重复项,这减少了请求中网络流量的有效负载.只有在接收到样本中的三个"文档的响应的前端"处理时,您才实际重新映射"到包含重复副本的最终列表.

So really from the server "Endpoint" and "Database" perpective the "documents" as returned should be all they handle. This decreases the payload of network traffic in the request by removing all duplicates. Only when processing at the "Front End" when receiving the response of those "three" documents in the sample would you actually "re-map" to the final list containing the duplicate copies.

另一方面,如果您实际上正在使用文档中已包含的数据,则Mongoose已经支持此功能,其中您的列表"已经是文档中的数组.例如,作为SongList模型的文档:

On the other hand if you are actually using data already contained in a document, then Mongoose already supports this where your "list" is already an array within a document. For example as a document for a SongList model:

{ "list": ["a", "a", "a", "b", "c"] }

调用填充该列表"实际上是对Song模型项目的引用的列表的位置时,将返回每个副本",并按以下顺序存储文档中的列表:

Calling populate where that "list" is actually a list of references to the Song model items will return each "copy" and in order that the list in the document is stored with:

SongList.find().populate('list')

其原因是.populate()基本上会使用文档的"list"字段中的参数来发出相同的$in查询.然后,这些查询结果实际上使用与上面演示的完全相同的代码映射"到该数组.

The reason for this is .populate() basically issues that same $in query anyway, using the arguments found in the "list" field for the document. Then those query results are actually "mapped" onto that array using what is essentially exactly the same code as demonstrated above.

因此,如果这是您的实际用例,那么它已经是内置"的,无需亲自去查询:

So if that is your actual use case, this is already "built in" and there is no need to go and do the query yourself:

以下显示了添加三首"歌曲并使用相同的映射"技术的示例清单,并显示了populate()自动执行的操作

The following shows an example listing of adding "three" songs and using the same "mapping" techniques as well as showing what populate() just does automatically

const { Schema, Types: { ObjectId } } = mongoose = require('mongoose'); const { uniq } = require('lodash'); const uri = 'mongodb://localhost/songs'; mongoose.set('debug', true); mongoose.Promise = global.Promise; const songSchema = new Schema({ name: String }); const songListSchema = new Schema({ list: [{ type: Schema.Types.ObjectId, ref: 'Song' }] }); const Song = mongoose.model('Song', songSchema); const SongList = mongoose.model('SongList', songListSchema); const log = data => console.log(JSON.stringify(data, undefined, 2)); (async function() { try { const conn = await mongoose.connect(uri); const db = conn.connections[0].db; let { version } = await dbmand({ "buildInfo": 1 }); version = parseFloat(version.match(new RegExp(/(?:(?!-).)*/))[0]); await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove())); let [a,b,c] = await Song.insertMany(['a','b','c'].map(name => ({ name }))); await SongList.create({ list: [ a, a, b, a, c ] }); // populate is basically mapping the list let popresult = await SongList.find().populate('list'); log({ popresult }); // Using an id list let list = [a, a, b, a, c].map(e => e._id); // Use a unique copy for the $in to save bandwidth let unique = uniq(list); // Map the result let songs = await Song.find({ _id: { $in: unique } }); songs = list.map(e => songs.find(s => s._id.equals(e))); log({ songs }) if ( version >= 3.4 ) { // Force the server to return copies let stupid = await Song.aggregate([ { "$match": { "_id": { "$in": unique } } }, { "$addFields": { "copies": { "$filter": { "input": { "$map": { "input": { "$zip": { "inputs": [ { "$literal": list }, { "$range": [0, { "$size": { "$literal": list } } ] } ] } }, "in": { "_id": { "$arrayElemAt": [ "$$this", 0 ] }, "idx": { "$arrayElemAt": [ "$$this", 1 ] } } } }, "cond": { "$eq": ["$$this._id", "$_id"] } } } }}, { "$unwind": "$copies" }, { "$sort": { "copies.idx": 1 } }, { "$project": { "copies": 0 } } ]); log({ stupid }) } } catch(e) { console.error(e) } finally { process.exit() } })()

这将为您提供如下输出:

And this gives you output as follows:

Mongoose: songs.remove({}, {}) Mongoose: songlists.remove({}, {}) Mongoose: songs.insertMany([ { _id: 5b06c2ff373eb00d9610aa6e, name: 'a', __v: 0 }, { _id: 5b06c2ff373eb00d9610aa6f, name: 'b', __v: 0 }, { _id: 5b06c2ff373eb00d9610aa70, name: 'c', __v: 0 } ], {}) Mongoose: songlists.insertOne({ list: [ ObjectId("5b06c2ff373eb00d9610aa6e"), ObjectId("5b06c2ff373eb00d9610aa6e"), ObjectId("5b06c2ff373eb00d9610aa6f"), ObjectId("5b06c2ff373eb00d9610aa6e"), ObjectId("5b06c2ff373eb00d9610aa70") ], _id: ObjectId("5b06c2ff373eb00d9610aa71"), __v: 0 }) Mongoose: songlists.find({}, { fields: {} }) Mongoose: songs.find({ _id: { '$in': [ ObjectId("5b06c2ff373eb00d9610aa6e"), ObjectId("5b06c2ff373eb00d9610aa6f"), ObjectId("5b06c2ff373eb00d9610aa70") ] } }, { fields: {} }) { "popresult": [ { "list": [ { "_id": "5b06c2ff373eb00d9610aa6e", "name": "a", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa6e", "name": "a", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa6f", "name": "b", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa6e", "name": "a", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa70", "name": "c", "__v": 0 } ], "_id": "5b06c2ff373eb00d9610aa71", "__v": 0 } ] } Mongoose: songs.find({ _id: { '$in': [ ObjectId("5b06c2ff373eb00d9610aa6e"), ObjectId("5b06c2ff373eb00d9610aa6f"), ObjectId("5b06c2ff373eb00d9610aa70") ] } }, { fields: {} }) { "songs": [ { "_id": "5b06c2ff373eb00d9610aa6e", "name": "a", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa6e", "name": "a", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa6f", "name": "b", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa6e", "name": "a", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa70", "name": "c", "__v": 0 } ] } Mongoose: songs.aggregate([ { '$match': { _id: { '$in': [ 5b06c2ff373eb00d9610aa6e, 5b06c2ff373eb00d9610aa6f, 5b06c2ff373eb00d9610aa70 ] } } }, { '$addFields': { copies: { '$filter': { input: { '$map': { input: { '$zip': { inputs: [ { '$literal': [Array] }, { '$range': [Array] } ] } }, in: { _id: { '$arrayElemAt': [ '$$this', 0 ] }, idx: { '$arrayElemAt': [ '$$this', 1 ] } } } }, cond: { '$eq': [ '$$this._id', '$_id' ] } } } } }, { '$unwind': '$copies' }, { '$sort': { 'copies.idx': 1 } }, { '$project': { copies: 0 } } ], {}) { "stupid": [ { "_id": "5b06c2ff373eb00d9610aa6e", "name": "a", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa6e", "name": "a", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa6f", "name": "b", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa6e", "name": "a", "__v": 0 }, { "_id": "5b06c2ff373eb00d9610aa70", "name": "c", "__v": 0 } ] }

愚蠢"的聚合技巧

这实际上不是解决方案,而是在其他人提到它或类似内容之前,更多地是关于该主题的帖子.

"Stupid" Aggregation Tricks

This really is not a solution but really more of a post on the subject before somebody else mentions it or something similar.

愚蠢的把戏"类别下的失败实际上迫使服务器返回文档的副本".

Falling more under the category of "stupid tricks" is actually forcing the server to return the "copies" of the documents.

let stupid = await Song.aggregate([ { "$match": { "_id": { "$in": list } } }, { "$addFields": { "copies": { "$filter": { "input": { "$map": { "input": { "$zip": { "inputs": [ list, { "$range": [0, { "$size": { "$literal": list } } ] } ] } }, "in": { "_id": { "$arrayElemAt": [ "$$this", 0 ] }, "idx": { "$arrayElemAt": [ "$$this", 1 ] } } } }, "cond": { "$eq": ["$$this._id", "$_id"] } } } }}, { "$unwind": "$copies" }, { "$sort": { "copies.idx": 1 } }, { "$project": { "copies": 0 } } ]);

这实际上将从服务器返回所有文档副本".通过处理的列表输出上的 $unwind 做到这一点与 $filter 一起仅保留那些与当前值匹配的值文档_id.使用 $unwind 有效地为每个数组条目生成文档的副本".

That actually will return all the document "copies" from the server. It does so via the $unwind on the list output processed with $filter to keep only those values which match the current document _id. Multiples will be retained in that array which when processed with $unwind effectively produces a "copy" of the document for each array entry.

作为奖励,我们通过"> $zip 和 $range 以下 $sort

As a bonus we keep the "idx" of the items in the list via mapping an "index" position into the array via $zip and $range The following $sort will then place the documents in order of how they appear in the input list, just to mimic the Array.map() which is being done in the code you should be using.

然后我们可以简单地 $project 来排除该字段只是临时措施.

We can then simply $project to "exclude" that field which was only there as a temporary measure.

所有这些都说,做这样的事情并不是一个好主意.如前所述,这样做实际上是在增加有效负载,而在客户端中构造映射"实际上要合乎逻辑得多.理想情况下,正如已经提到的,最终"客户端.

All of that said, it's not really a great idea to do such a thing. As already mentioned you are essentially increasing the payload by doing so, when it's really far more logical to construct the "mapping" in the client. And ideally the "end" client as already mentioned.

更多推荐

获取ID数组中所有具有匹配ID的元素

本文发布于:2023-10-24 20:01:24,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1524866.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:组中   元素   ID

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!