Mongodb投射中的$slice,正向反向跳过要搞清楚

在投射中,使用操作符和elemMatch返回数组中第一个符合查询条件的元素。而在投射中使用$slice, 能够返回指定数量的数组元素。

定义

投射中使用$slice命令,指定查询结果中返回数组元素的数量。

语法

复制代码
db.collection.find(
  <query>,
  {<arrayField: {$slice: <number to return}
)

db.collection.find(
  <query>,
  { <arrayField: {$slice: [ <number to skip>, <number to return>]}>}
)

其中<number to return>,指定了返回数组元素的数量,当定义为正值时,返回数组的前面几个元素,而定义负值时,返回数组后面的元素。如果定义的值大于数组长度,则返回数组中所有的元素。

<number to skip>,定义了自哪个元素开始,返回数组元素。与<number to return>定义一致,当定义正值时,从数组开头跳过指定数量的数组元素。当定义负值时,从数组第一个元素开始向后跳过指定数量的数组元素。这里<number to skip>的正数和负数,只是指定起点自第一个元素开始跳转的方向和跳动的次数。无论正数还是负数,使用<number to return>获取数组元素时,都是从数组的正向开始获取。

行为

在投射定义查询结果包含字段时,在内嵌文档的数组中使用$slice,内嵌文档只会返回slice定义的字段。

构建包含下面数据的inventory集合,其中details字段是文档类型,details.colors和details.sizes是文档details中的两个数组元素。

复制代码
db.inventory.insertOne(
{ 
  item: "socks", 
  qty: 100, 
  details: 
  	{ 
      colors: [ "blue", "red" ], 
      sizes: [ "S", "M", "L"] 
    } 
	}
)

使用$slice返回colors字段中的第一个数组元素

复制代码
db.inventory.find(
  {},
  {qty: 1, "details.colors": { $slice: 1}}
)

返回结果

复制代码
{
	"_id" : ObjectId("65b82e5b5943471b2e552d50"),
	"qty" : 100,
	"details" : {
		"colors" : [ "blue" ]
	}
}

在投射定义查询结果不包含字段时,在内嵌文档的数组中使用$slice, 内嵌文档会返回其他字段。

复制代码
db.inventory.find(
  {},
  {_id: 0, "details.colors": { $slice: 1}}
)

返回结果

复制代码
{
	"item" : "socks",
	"qty" : 100,
	"details" : {
		"colors" : [ "blue" ],
		"sizes" : [ "S", "M", "L" ]
	}
}

4.4版本前的数据库行为有差异,无论是定义包含字段还是不包含字段,投射中都会包含其他字段。

对视图的查询不支持$slice操作符。

4.4版本开始,使用find, findAndModify定义查询语句的投射中,不能将slice应用到操作符号中。下面的查询语句是错误的。

复制代码
db.inventory.find(
  {"instock.qty": {$gt:25}}, 
  {"instock.$": {$slice:1}}
)

{
	"message" : "positional projection cannot be used with an expression or sub object",
	"ok" : 0,
	"code" : 31271,
	"codeName" : "Location31271"
}

在文档数组字段使用$slice时,需要注意路径冲突

向集合inventory插入文档。其中instock是文档数组类型的数据。

复制代码
db.inventory.insertOne(
    { 
      item: "books", 
      qty: 100, 
      details: 
      	{ 
          colors: [ "blue", "red" ], 
          sizes: [ "S", "M", "L"] 
        },
      instock: [{
          warehouse: "A",
          qty:35
      },{
          warehouse: "B",
          qty:15
      },{
          warehouse: "C",
          qty:35
      }]
    }
)

构建针对instock的投射查询语句

复制代码
db.inventory.find(
    {},
    { "instock": {$slice:1}, "instock.warehouse": 0}
    )

因为instock和instock.warehouse定义的路径冲突,所以此语句无效。

复制代码
{
	"message" : "Path collision at instock.warehouce remaining portion warehouce",
	"ok" : 0,
	"code" : 31249,
	"codeName" : "Location31249"
}

应用

构建测试集合。向posts集合中插入两条数据,数据包含文档数组类型的字段comments.

复制代码
db.posts.insertMany([
   {
     _id: 1,
     title: "Bagels are not croissants.",
     comments: [ 
         { comment: "0. true" }, 
         { comment: "1. croissants aren't bagels."} 
        ]
   },
   {
     _id: 2,
     title: "Coffee please.",
     comments: [ 
         { comment: "0. fooey" }, 
         { comment: "1. tea please" }, 
         { comment: "2. iced coffee" }, 
         { comment: "3. cappuccino" }, 
         { comment: "4. whatever" } 
        ]
   }
])

构建查询语句,返回数组comments中前三个元素。

复制代码
db.posts.find({}, {comments: {$slice:  3}})

返回数组comments中后面三个元素。

复制代码
db.posts.find({}, {comments: {$slice:  -3}})

正向跳过1个元素,获取后面的三个元素

复制代码
db.posts.find({}, {comments: {$slice: [1, 3]}})

反向跳过1个元素,获取后面的三个元素

复制代码
db.posts.find({}, {comments: {$slice: [-1, 3]}})

//返回结果
/* 1 */
{
	"_id" : 1,
	"title" : "Bagels are not croissants.",
	"comments" : [
		{
			"comment" : "1. croissants aren't bagels."
		}
	]
},

/* 2 */
{
	"_id" : 2,
	"title" : "Coffee please.",
	"comments" : [
		{
			"comment" : "4. whatever"
		}
	]
}

这个语句的返回结果,就值得探讨了。正常理解可能会认为是跳过数组最后一个元素,然后获取最后一个元素前面的三个元素,即{ comment: "1. tea please" }, { comment: "2. iced coffee" }, { comment: "3. cappuccino" }。但这样获取的方向就是-3,而不是3了。在slice中指定跳过几个元素时,指定获取元素的数量,只能是正数。也就表示获取元素的方向只能是正向。使用{slice: [-1, 3]}时,表示从数组第一个元素开始,向后跳转一步,到达第四个元素,再以第四个元素作为起点,获得后面的3个元素。因为第四个元素后面没有数据了,所以只返回一个元素。

相关推荐
前端一课1 分钟前
【前端每天一题】🔥 第 8 题:什么是事件委托?它的原理是什么?有哪些优点和常见坑? - 前端高频面试题
前端·面试
gAlAxy...3 分钟前
SpringMVC 响应数据和结果视图:从环境搭建到实战全解析
大数据·数据库·mysql
前端一课7 分钟前
【前端每天一题】🔥第7题 事件冒泡与事件捕获 - 前端高频面试题
前端·面试
前端一课7 分钟前
【前端每天一题】 第 5 题:Promise.then 执行顺序深入题(微任务队列机制)
前端·面试
前端一课12 分钟前
【前端每天一题】🔥 事件循环第 6 题:setTimeout(fn, 0) 执行时机详解
前端·面试
前端一课13 分钟前
【前端每天一题】🔥 第3题 事件循环 20 道经典面试题(附详细答案)
前端·面试
前端一课15 分钟前
【前端每天一题】第 2 题:var、let、const 的区别?(绝对高频)
前端·面试
前端一课19 分钟前
【前端每天一题】🔥第四题 原型与原型链 - 前端面试必考题
前端·面试
初见00122 分钟前
告别无限循环:深入理解并征服 React Hooks 的依赖数组
前端
一颗不甘坠落的流星24 分钟前
【HTML】iframe 标签 allow 权限汇总(例如添加复制粘贴权限)
前端·javascript·html