美文网首页阿里云
MongoDB-第四章-查询

MongoDB-第四章-查询

作者: weipeng2k | 来源:发表于2019-12-15 17:34 被阅读0次

查询

        对MongoDB进行新增、修改和删除后,最主要的功能就是对数据(集合)进行查询,MongoDB支持丰富的查询功能。

查询简介

        使用find命令可以查询集合中的文档,并返回符合要求的文档子集。查询命令的第一个参数是查询的模式,第二个参数是指定返回的键。查询的模式有些类似JPA中的查询模式,只需要表述需要的模式就行,例如:如果要查询属性nametest的文档,模式就是:{"name": "test"}。返回的键可以通过声明指定的键即可,例如:如果要返回name,声明就是:{"name": 1}

        如果需要查询age20name,查询命令参数就是:{"age": 20}, {"name": 1}

> db.foo.find({"age": 20}, {"name": 1})
{ "_id" : ObjectId("5d9ab1e940eabd2b62ced66f"), "name" : "test" }
> db.foo.find({"age": 20}, {"name": 1, "age": 1})
{ "_id" : ObjectId("5d9ab1e940eabd2b62ced66f"), "name" : "test", "age" : 20 }
> db.foo.find({"age": 20}, {"name": 1, "_id": 0})
{ "name" : "test" }

默认会返回属性_id,如果不需要可以声明{"_id": 0}

        find是和MongoDB交互的基础命令,可以通过它来查询集合中的文档(数据)。

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.FindTest

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBObject query = new BasicDBObject();
query.put("age", 20);
DBObject project = new BasicDBObject();
project.put("name", 1);
project.put("_id", 0);
DBCursor dbObjects = collection.find(query, project);

dbObjects.forEach(System.out::println);

dbObjects = collection.find(query);
dbObjects.forEach(System.out::println);

        Collection.find()方法的文档描述是:Select documents in collection and get a cursor to the selected documents.,可以看到调用find方法,返回的是指向符合查询条件要求的文档游标。只要我们获取了游标,我们就可以遍历它,访问符合我们要求的文档了。

这里和JDBC有些区别,传统的JDBC通过CollectionStatement访问RDBMS后,得到的是数据集RowSet

根据条件查询

        通过使用find命令,可以查询集合中符合要求的文档,但是我们对于数据库的使用不是只限定在这种简单的操作,还有一些比较通用的查询要求,比如:范围查询等。

条件查询

        在SQL查询中,可以使用>或者<来进行范围的控制,也就是比较操作符。在MongoDB中,由于查询的语句都是JSON,所以需要用转移字符来替换掉我们常用的比较操作符。

        对应关系如下表:

类型 描述
$lt less than,也就是 <
$lte less than or equal,也就是 <=
$gt greater than,也就是 >
$gte greater than or equal,也就是 >=
$ne not equal, 也就是 <>

        根据age进行范围查询,先查询age小于等于21的。

> db.foo.find();
{ "_id" : ObjectId("5d9ab1e940eabd2b62ced66f"), "name" : "test", "age" : 20 }
{ "_id" : ObjectId("5dda6f29f75cb1b4beb2d95f"), "name" : "x", "age" : 21 }
> db.foo.find({"age": {"$lte" : 20}})
{ "_id" : ObjectId("5d9ab1e940eabd2b62ced66f"), "name" : "test", "age" : 20 }
> db.foo.find({"age": {"$gt" : 20}})
{ "_id" : ObjectId("5dda6f29f75cb1b4beb2d95f"), "name" : "x", "age" : 21 }

        可以看到查询语句中,先给出了限定的文档字段age,随后跟着对字段的限定内容。、

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.ConditionFindTest

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBObject query = new BasicDBObject();

DBObject condition = new BasicDBObject();
condition.put("$gt", 20);

query.put("age", condition);

DBCursor dbObjects = collection.find(query);

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

$IN

        在关系数据库中,可以使用in查询来进行单key的多值查询,比如:

select * from user where age in (18, 19, 20);

        上述SQL可以查询age为18、19或者20的所有user,而这种查询方式,MongoDB也有提供,方式和SQL很类似。

> db.foo.find()
{ "_id" : ObjectId("5d9ab1e940eabd2b62ced66f"), "name" : "test", "age" : 20 }
{ "_id" : ObjectId("5dda6f29f75cb1b4beb2d95f"), "name" : "x", "age" : 21 }
> db.foo.find({"age": {"$in": [20,21]}})
{ "_id" : ObjectId("5d9ab1e940eabd2b62ced66f"), "name" : "test", "age" : 20 }
{ "_id" : ObjectId("5dda6f29f75cb1b4beb2d95f"), "name" : "x", "age" : 21 }

        可以看到查询语句中,对于文档属性name进行了限定,限定描述是$in,同时指定值是[20, 21]

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.InFindTest

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBObject query = new BasicDBObject();

DBObject condition = new BasicDBObject();
condition.put("$in", new int[]{20, 21});

query.put("age", condition);

DBCursor dbObjects = collection.find(query);

        $in操作符还是用来限定文档的一个属性,而通过$or可以连接对于多个属性的限定。

$OR

        在关系数据库中,可以通过or来限定查询条件,MongoDB也提供了类似的解决方案,也就是使用$or操作符。

> db.foo.find({"$or": [{"name": "test"}, {"age": 21}]})
{ "_id" : ObjectId("5d9ab1e940eabd2b62ced66f"), "name" : "test", "age" : 20 }
{ "_id" : ObjectId("5dda6f29f75cb1b4beb2d95f"), "name" : "x", "age" : 21 }

        上述查询语句会查询集合foo中,name属性为testage属性为21的文档。

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.OrFindTest

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBObject query = new BasicDBObject();

DBObject nameQuery = new BasicDBObject();
nameQuery.put("name", "test");

DBObject ageQuery = new BasicDBObject();
ageQuery.put("age", 21);

query.put("$or", new DBObject[] {nameQuery, ageQuery});

DBCursor dbObjects = collection.find(query);

        $or操作符可以连接多个对于不同(或者相同)属性的查询限定,在一定程度上要比$in灵活。同理也可以用$and操作符来连接多个查询条件。

高级查询

        前文介绍了基本的查询功能,能够指定集合中文档的键进行精确或者范围查询,上述基本的查询功能也覆盖了大部分关系数据库中的查询方法。这些基本的查询方法对应的是基础数据类型,由于MongoDB提供了数组和子文档,这就使得查询这些特有的数据类型需要更为强大的查询方法,而这些也是MongoDB优于传统关系数据库查询功能的关键。

数组查询

        数组类型是一组相同类型的集合,比如:

{ "_id" : ObjectId("5de3abd5c28644d98389fadd"), "name" : "haha", "value" : [ 1, 2, 3 ] }

        其中value属性存储的内容就是数组,数组元素的类型是INT。

        如果查询时,查询条件是数组中的一个元素,则一旦文档中的数组元素包含了这个元素,就会返回。

> db.foo.find()
{ "_id" : ObjectId("5d9ab1e940eabd2b62ced66f"), "name" : "test", "age" : 20 }
{ "_id" : ObjectId("5dda6f29f75cb1b4beb2d95f"), "name" : "x", "age" : 21 }
{ "_id" : ObjectId("5de3abd5c28644d98389fadd"), "name" : "haha", "value" : [ 1, 2, 3 ] }
{ "_id" : ObjectId("5de3abffc28644d98389fade"), "name" : "hehe", "value" : [ 3, 4, 5 ] }
> db.foo.find({"value": 3})
{ "_id" : ObjectId("5de3abd5c28644d98389fadd"), "name" : "haha", "value" : [ 1, 2, 3 ] }
{ "_id" : ObjectId("5de3abffc28644d98389fade"), "name" : "hehe", "value" : [ 3, 4, 5 ] }

        可以看到,db.foo.find({"value": 3})可以查询出所有value数组属性包含了3的文档。

精确匹配$all

        按照db.foo.find({"value": 3})是一种单值的匹配方式,有时我们需要一种使用数组的匹配方式,比如:找出value包含了[1, 2]两个元素的文档,这就需要使用$all操作符了。

> db.foo.find({"value": {"$all": [1,4]}})
> db.foo.find({"value": {"$all": [1,2]}})
{ "_id" : ObjectId("5de3abd5c28644d98389fadd"), "name" : "haha", "value" : [ 1, 2, 3 ] }

        可以看到当输入的数组元素是[1, 2]时,就可以查询到对应的文档。

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.ArrayFindTest#all

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBObject query = new BasicDBObject();

DBObject condition = new BasicDBObject();
condition.put("$all", new int[]{1, 2});

query.put("value", condition);

DBCursor dbObjects = collection.find(query);

        可以看到针对value这个属性,使用的查询限定是{"$all": [1, 2]},表示查询value中全部包含了12元素的文档。

长度匹配$size

        我们需要查询数组长度为3的文档,可以使用如下方式:

> db.foo.find({"value": {"$size": 3}})
{ "_id" : ObjectId("5de3abd5c28644d98389fadd"), "name" : "haha", "value" : [ 1, 2, 3 ] }
{ "_id" : ObjectId("5de3abffc28644d98389fade"), "name" : "hehe", "value" : [ 3, 4, 5 ] }
> db.foo.find({"value": {"$size": 2}})
> 

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.ArrayFindTest#all

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBObject query = new BasicDBObject();

DBObject condition = new BasicDBObject();
condition.put("$size", 3);

query.put("value", condition);

DBCursor dbObjects = collection.find(query);

        可以看到针对value这个属性,使用的查询限定是{"$size": 3},表示查询value数组的长度为3的文档。

子文档查询

        子文档是在文档的一个属性中存储另一个文档,好比自定义数据结构中包括了另外的自定义数据结构。子文档的模式如下所示,其中map属性存储的内容就是一个文档,也就是当前文档的子文档。

{ 
    "_id" : ObjectId("5deccfaf65cd643b1a7f1b0f"), 
    "name" : "m", "age" : 20, 
    "map" : 
    { 
        "math" : 90, 
        "physics" : 91, 
        "english" : 92 
    } 
}

        子文档的查询分为两种方式,一种是当做属性来看,也就是全匹配,另外一种是根据子文档的部分属性进行匹配查询。

        先看一下全匹配,也就是在查询的时候,限定的内容是对子文档的完整描述。

> db.foo.find({"map":  {"math": 90, "physics": 91, "english": 92}})
{ "_id" : ObjectId("5deccfaf65cd643b1a7f1b0f"), "name" : "m", "age" : 20, "map" : { "math" : 90, "physics" : 91, "english" : 92 } }

        这种查询方式没有什么意义,因为给出子文档的全部内容,成本太高了,所以根据子文档的部分属性进行查询是一种常用的方式。而描述子文档的部分内容(属性)就需要利用.操作符,比如:要查询math为90的文档,可以这样描述map.math=90

> db.foo.find({"map.math": 90})
{ "_id" : ObjectId("5deccfaf65cd643b1a7f1b0f"), "name" : "m", "age" : 20, "map" : { "math" : 90, "physics" : 91, "english" : 92 } }

        还可以这样查询,查询math属性高于80的全部文档。

> db.foo.find({"map.math": {"$gte": 80}})
{ "_id" : ObjectId("5deccfaf65cd643b1a7f1b0f"), "name" : "m", "age" : 20, "map" : { "math" : 90, "physics" : 91, "english" : 92 } }
{ "_id" : ObjectId("5decd04865cd643b1a7f1b10"), "name" : "n", "age" : 19, "map" : { "math" : 80, "physics" : 81, "english" : 82 } }

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.InnerFindTest#inner_doc_property

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBObject query = new BasicDBObject();

DBObject condition = new BasicDBObject();
condition.put("$gte", 80);

query.put("map.math", condition);

DBCursor dbObjects = collection.find(query);

        可以看到针对map.math这个属性,也就是子文档map中的math属性,使用的查询限定是{"$gte": 80},表示查询map.math中大于等于80的文档。

        在看到数组和子文档后,如果数据结构再复杂一些,也就是数组中的元素不再是基本类型,而是文档类型,那么如何进行查询呢?类似如下文档:

{ 
    "_id" : ObjectId("5dece6af65cd643b1a7f1b11"), 
    "name" : "x", 
    "age" : 27, 
    "maps" : 
    [ 
        { 
            "math" : 90, 
            "physics" : 91, 
            "english" : 92 
        }, 
        { 
            "math" : 80, 
            "physics" : 81, 
            "english" : 82 
        } 
    ] 
}

        对于这种复杂的文档进行查询时,首先可以看到maps是一个数组,对于数组查询可以使用完全给出数组中的元素或者一些特定的操作符,比如:

> db.foo.find({"maps": {"$size": 2}})
{ "_id" : ObjectId("5dece6af65cd643b1a7f1b11"), "name" : "x", "age" : 27, "maps" : [ { "math" : 90, "physics" : 91, "english" : 92 }, { "math" : 80, "physics" : 81, "english" : 82 } ] }

        可以通过$size操作符,来查询maps属性(数组)包含了2个元素的文档,又或者给出数组元素中的完整内容进行查询。

> db.foo.find({"maps": {"$all": [{ "math" : 90, "physics" : 91, "english" : 92 }]}})
{ "_id" : ObjectId("5dece6af65cd643b1a7f1b11"), "name" : "x", "age" : 27, "maps" : [ { "math" : 90, "physics" : 91, "english" : 92 }, { "math" : 80, "physics" : 81, "english" : 82 } ] }

        如果是完全给出匹配内容的查询方式,意义不大,我们需要的是给出部分属性的查询,如果要查询english大于80的数据,按照如下方式:

> db.foo.find({"maps": {"$all": [{ "english" : {"$gt": 80} }]}})
> 

        可以发现,并不会生效,原因就是在数组查询中,需要给出数组元素的全部内容。这种查询也无法使用maps.english来进行匹配,因为用这个key无法确定性的指定到子文档中的属性,因此需要使用$elemMatch来进行部分指定匹配数组中单个子文档的限定条件,仅当数组中是有多个键的子文档时,可以使用。

> db.foo.find({"maps": {"$elemMatch": {"math": {"$gte": 90}}}})
{ "_id" : ObjectId("5dece6af65cd643b1a7f1b11"), "name" : "x", "age" : 27, "maps" : [ { "math" : 90, "physics" : 91, "english" : 92 }, { "math" : 80, "physics" : 81, "english" : 82 } ] }

        上述查询指定了maps数组属性中,所有的子文档中math属性大于等于90。

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.InnerFindTest#inner_doc_elem_match

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBObject query = new BasicDBObject();

DBObject condition = new BasicDBObject();
condition.put("$gte", 90);

query.put("math", condition);

DBObject elementMath = new BasicDBObject();

elementMath.put("$elemMatch", query);

DBObject arrayQuery = new BasicDBObject();

arrayQuery.put("maps", elementMath);

DBCursor dbObjects = collection.find(arrayQuery);

        可以看到,查询的构筑过程已经非常复杂,首先是对于子文档的限定{"math": {"$gte", 90}},随后将限定放置在$elemMatch的限定中,最后用elemMatch限定去匹配maps这子文档数组属性。

查询过程应该从最里层开始思考,逐步外推到父文档的属性。

游标

        在使用find命令进行查询时,MongoDB并不会将结果直接返回给客户端,而是返回了一个游标,你可以认为是一个指针,指向远端的数据。可以通过遍历的方式获取数据,或者执行一些诸如:排序的操作。

DBObject query = new BasicDBObject();
query.put("age", 20);
DBObject project = new BasicDBObject();
project.put("name", 1);
project.put("_id", 0);
DBCursor dbObjects = collection.find(query, project);

dbObjects.forEach(System.out::println);

        上述代码查询集合后,返回的就是游标,而这个DBCursor的定义如下:

@NotThreadSafe
public class DBCursor implements Cursor, Iterable<DBObject> {
}

        可以看到其实现了Iterable接口,能够支持for循环遍历,而对于真实数据的获取是在需要真正获取结果时才会去请求远端的文档数据。当客户端使用Iterator#next()方法获取数据时,客户端会批量的获取一部分数据或者4MB以内的数据(二者取其小者),这样客户端在调用next()方法时,就不会总去服务端上请求数据了。

这种获取方式优化了客户端获取数据时的时延,并且降低了网络开销对于查询带来的影响

        游标不仅仅是客户端的概念,在服务端也有会有游标。当客户端开始对服务端发起查询请求时,服务端也会生成对应的游标,这样客户端和服务端就能够对齐,这好比是一个会话,因此游标不是线程安全的。当然,在服务端的游标也会占用一定资源,当客户端完成遍历或者操作后,就会发起销毁请求给服务端,服务端会销毁对应的游标(会话)。

limit

        在查询返回的数据上,限定返回的结果数量。比如:下面的查询,返回数据的结果被限定为2个。

> db.foo.find().limit(2)
{ "_id" : ObjectId("5d9ab1e940eabd2b62ced66f"), "name" : "test", "age" : 20 }
{ "_id" : ObjectId("5dda6f29f75cb1b4beb2d95f"), "name" : "x", "age" : 21 }

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.CursorTest#limit

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBCollection collection = mongoTemplate.getCollection("foo");

DBCursor dbObjects = collection.find().limit(2);
dbObjects.forEach(System.out::println);

skip

        在查询返回的数据上,过滤掉前几个匹配的文档。比如:下面的查询,返回数据的结果会过滤掉前2个。

> db.foo.find().skip(2)
{ "_id" : ObjectId("5de3abd5c28644d98389fadd"), "name" : "haha", "value" : [ 1, 2, 3 ] }
{ "_id" : ObjectId("5de3abffc28644d98389fade"), "name" : "hehe", "value" : [ 3, 4, 5 ] }
{ "_id" : ObjectId("5deccfaf65cd643b1a7f1b0f"), "name" : "m", "age" : 20, "map" : { "math" : 90, "physics" : 91, "english" : 92 } }
{ "_id" : ObjectId("5decd04865cd643b1a7f1b10"), "name" : "n", "age" : 19, "map" : { "math" : 80, "physics" : 81, "english" : 82 } }
{ "_id" : ObjectId("5dece6af65cd643b1a7f1b11"), "name" : "x", "age" : 27, "maps" : [ { "math" : 90, "physics" : 91, "english" : 92 }, { "math" : 80, "physics" : 81, "english" : 82 } ] }
> 

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.CursorTest#skip

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBCollection collection = mongoTemplate.getCollection("foo");

DBCursor dbObjects = collection.find().skip(2);
dbObjects.forEach(System.out::println);

使用skip过滤少量数据是可以的,但是如果用来过滤大量数据(比如:上万),就会出现性能瓶颈,因为它都是要查询出符合约束的数据,然后再加以过滤,这样的开销会随着过滤数据量级的提升而变得越来越差。

        过滤大量的数据,可以在功能侧考虑一些支持,比如:增加一个查询条件,时间;又或者将上一次查询的结果的id作为下一次查询的条件传入。

sort

        在查询返回的数据上,可以通过sort来对集合的文档进行排序,其中1表示升序,-1表示降序。

> db.foo.find().sort({"name":1}).limit(3)
{ "_id" : ObjectId("5de3abd5c28644d98389fadd"), "name" : "haha", "value" : [ 1, 2, 3 ] }
{ "_id" : ObjectId("5de3abffc28644d98389fade"), "name" : "hehe", "value" : [ 3, 4, 5 ] }
{ "_id" : ObjectId("5deccfaf65cd643b1a7f1b0f"), "name" : "m", "age" : 20, "map" : { "math" : 90, "physics" : 91, "english" : 92 } }

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter4.CursorTest#sort

        使用MongoDB的Java客户端,进行查询文档的关键逻辑如下:

DBCollection collection = mongoTemplate.getCollection("foo");

DBObject dbObject = new BasicDBObject();
dbObject.put("name", 1);
DBCursor dbObjects = collection.find().limit(3).sort(dbObject);
dbObjects.forEach(System.out::println);

相关文章

  • MongoDB-查询

    任何数据库中查询都是最麻烦的,在MongoDB中对于查询的支持非常到位,有关系运算,逻辑运算,数组运算等等首先对于...

  • MongoDB-第四章-查询

    查询 对MongoDB进行新增、修改和删除后,最主要的功能就是对数据(集合)进行查询,MongoDB支持丰富的查询...

  • mongodb 数据库 安装步骤

    SQL- 结构化查询语言- 关系数据库全都同SQL来操作 1.安装MongoDB- 安装- 配置环境变量C:\Pr...

  • 2022-10-23

    SQL- 结构化查询语言- 关系数据库全都同SQL来操作 1.安装MongoDB- 安装- 配置环境变量C:\Pr...

  • mongodb-模糊查询+聚合函数+连表查询+排序

    今天我们来聊一下nosql mongodb的模糊查询: 在mysql中需要做模糊查询的时候一般都是用的关键字lik...

  • MongoDB-基础使用(二)

    前置文章:MongoDB-基础使用(一)[https://www.jianshu.com/p/7bca353127...

  • MongoDB-基础使用(三)

    前置文章:MongoDB-基础使用(一)[https://www.jianshu.com/p/7bca353127...

  • mongoDB(二)

    第四章 插入命令 查看指令 查看数据库命令 插入单条 插入多条 第五章 查询命令 1.查询一条 2.查询所有 3....

  • Mongodb-主从

    docker inspect --format '{{ .NetworkSettings.IPAddress }}...

  • MongoDB-序

    NOSql :not only sql 显示开发中一直存在一个问题: 数据表 — JDBC读取 — POJO(V...

网友评论

    本文标题:MongoDB-第四章-查询

    本文链接:https://www.haomeiwen.com/subject/phclnctx.html