北川广海の梦

北川广海の梦

【笔记】MongoDB 一些较为复杂的操作

2020-06-08

MongoDB

Mukai Music的用户自建歌单(可以整合其他平台)打算用MongoDB来存储。
使用单表设计,每一个用户为一个Document,包含用户Id int64类型,三个数组分别是:

  1. 用户自建歌单playlists,包含歌单封面,名字,是否公开,以及内部还包含一个tracks数组,记录所有歌单中的歌曲
  2. 用户收藏的歌单,记录目标歌单Id 和来源,如果为用户自建歌单,Id则为ObjectId,否则为网易云歌单Id
  3. 用户喜欢的歌曲,记录所有用户喜欢的单曲。以DataSource_Id形式存储

可以看出,主要遇到的问题,还是在于子数组中,要想办法实现对子数组的各种操作。

创建一个歌单(向sub-array插入元素)

这个遇到的问题在于,用户数据和用户的音乐数据是分开存储的,用户数据完全属于另一个模块。所以如果一个用户刚刚注册完成,MongoDB中还没有包含这个用户的Id的Document,那么他的playlists肯定也无从谈起。
这个可以用MongoDB的setOnInsert进行操作,即用户Id存在的话,就往对应的playlists插入一个歌单,插入用到的是$addToSet操作符,用于向数组插入元素,不然的话,就创建一个用户Document:
用C# 可以这样实现
image.png
在Shell中可以如此:

db.user_musics.update(
  { "_id": 100 },
  {
     $addToSet: { "playlists": playlist0 },
     $setOnInsert: { "_id": 100 }
  },
  { upsert: true } //upsert是必要的
)

更新歌单信息(更改sub-array中的元素)

例如想要修改歌单的名字,是否公开等等.查阅mongoDB官方文档可以得知,可以用$美刀符号来实现。它的作用:

MongoDB Manual
The positional $ operator identifies an element in an array to update without explicitly specifying the position of the element in the array.
大概就是它可以标识要更新哪个元素,而不需要知道元素在数组哪个位置。
C# :
image.png

用Shell:

db.user_musics.update(
{"playlists":
$elemMatch:{"_id": "5edd033ed261111ef49ec6a6" }
},
{
 $set:{ "playlists.$.picUrl":"http://pic.mypic.png" }
}
)

查询用户歌单数量(聚合查询sub-array元素个数)

这个稍显复杂,要用到MongoDB的AggregatePipeline,聚合管道,我的理解是这个管道就是类似一个漏斗的东西,从上下一层一层筛选,在最后根据聚合操作符,输出对应的结果。
C# 操作:
image.png
采用的是Json的方式来聚会,整个管道有两个部分,第一部分作用是筛选出对应的用户Id。第二部分是通过 $size操作符,得到playlists数组的长度,并且赋值给“count”,同时还排除了“_id”字段,让它不返回,那么最后的查询得到的一个Document,其中就会包含一个Count字段,里面的值就是playlists的长度。

这两个stage0,stage1的两个泛型,就分别是输入输出类型,第一个管道,输入UserMusic,输出UserMusic,因为这里做的是筛选操作。第二个管道,输入的UserMusic,输出一个包含Count字段的内部类(自定义的),因为这里使用了聚合操作。最后我们得到的结构就是一个CountInfo这个内部类对象实例。

Shell写法:

db.user_musics.aggregate(
[
{$match:{"_id": 1}},
{$project :{"count": {$size:"$playlists" }, "_id":0 } }
])

删除歌单 (删除sub-array元素)

主要是用到$Pull操作符,作用是删除array中的元素,这个操作符的介绍在MongoDB Manual里也有,这里不详细说了,直接上代码:
C#
image.png

Shell:

db.user_musics.update(
{"_id":1},
{$pull:{"playlists":{ "_id": "5edd033ed261111ef49ec6a6" } }}
)

向歌单中添加歌曲 (向sub-array的sub-array中添加元素)

这个比直接创建歌单麻烦更多。好在,也成功解决了。用到的是也是$addToSet
C#:
image.png

和更新一样,需要用$. C#的AddToSetEach可以插入多个元素

Shell:

db.user_musics.update(
{	
	"_id":1,
	"playlists":{$elemMatch:{"_id":"5edd033ed261111ef49ec6a6"}}
},
{
	$addToset:{"playlists.$.tracks":{$each:[music1,music2]}}
}
)

从歌单中移除一些歌曲(从sub-array的sub-array中移除元素)

妈的,这个困扰我好久。我一直尝试用“playlists.$.tracks”,但是没有卵用。
我跑去Stack overflow问了下外国小哥才解决。
C#:
image.png
可以看到,它用了一个“playlists.0.tracks”.这个0其实是playlists对应的在数组中的位置,第一个歌单,就是0。而不是要删除的元素对应的位置。
那么我们不知道位置咋办,没有关系,我么可以通过聚合进行一次查询。
image.png
这个聚合的结果也是一个内部类,包含了index字段。然后将上面的"playlists.0.tracks"中的0换成查询到的index结果就行了。
聚合查询的Shell:

db.user_musics.aggregate(
[
    {$match :{"_id":46}},
    {$project :{
        "index":{ $indexOfArray:["$playlists._id",ObjectId("5edb5734e55ff4378857d2f4")]},
        "_id":0
    }}
])

删除歌曲的Shell

db.user_musics.update(
   { "_id" : 2 },
   { $pull : { "playlists.0.tracks" : { "sid": "NetEase_1998" } } }
);