MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

MongoDB查询条件深度解析

2021-12-243.8k 阅读

MongoDB查询基础

在MongoDB中,查询操作是最为核心的功能之一,用于从集合(collection)中检索符合特定条件的文档(document)。最基本的查询语法使用find()方法,其最简单的形式不包含任何条件,会返回集合中的所有文档。例如,假设我们有一个名为users的集合,存储用户信息:

// 连接到MongoDB
const { MongoClient } = require('mongodb');
const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);

async function findAllUsers() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({});
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findAllUsers();

上述代码通过find({})查询users集合中的所有文档,并将结果打印出来。

简单条件查询

比较运算符

  1. 等于($eq):默认情况下,find()方法中的条件如果是简单的键值对,就表示$eq操作。例如,要查找年龄为25岁的用户:
async function findUserByAge() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ age: 25 });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserByAge();

这与使用显式的$eq运算符效果相同:

async function findUserByAgeWithEq() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ age: { $eq: 25 } });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserByAgeWithEq();
  1. 大于($gt)、大于等于($gte)、小于($lt)、小于等于($lte):这些运算符用于数值比较。比如,查找年龄大于30岁的用户:
async function findUserOlderThan30() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ age: { $gt: 30 } });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserOlderThan30();

要查找年龄大于等于20岁且小于等于30岁的用户,可以组合使用$gte$lte

async function findUserInAgeRange() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ age: { $gte: 20, $lte: 30 } });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserInAgeRange();
  1. 不等于($ne):用于查找与指定值不相等的文档。例如,查找年龄不等于25岁的用户:
async function findUserNot25() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ age: { $ne: 25 } });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserNot25();

逻辑运算符

  1. 与($and)$and运算符用于连接多个条件,只有当所有条件都满足时,文档才会被返回。假设users集合中的文档还包含city字段,要查找年龄大于25岁且居住在“Beijing”的用户:
async function findUserInBeijingAndOlder() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({
            $and: [
                { age: { $gt: 25 } },
                { city: 'Beijing' }
            ]
        });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserInBeijingAndOlder();
  1. 或($or)$or运算符连接多个条件,只要有一个条件满足,文档就会被返回。比如,查找年龄大于30岁或者居住在“Shanghai”的用户:
async function findUserOlderOrInShanghai() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({
            $or: [
                { age: { $gt: 30 } },
                { city: 'Shanghai' }
            ]
        });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserOlderOrInShanghai();
  1. 非($not)$not运算符用于对单个条件取反。例如,查找年龄不大于25岁的用户(等价于年龄小于等于25岁):
async function findUserNotOlderThan25() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ age: { $not: { $gt: 25 } } });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserNotOlderThan25();
  1. 与非($nor)$nor运算符连接多个条件,只有当所有条件都不满足时,文档才会被返回。比如,查找年龄不大于25岁且不住在“Beijing”的用户:
async function findUserNotInBeijingAndNotOlder() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({
            $nor: [
                { age: { $gt: 25 } },
                { city: 'Beijing' }
            ]
        });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserNotInBeijingAndNotOlder();

字段存在性查询

$exists运算符

$exists运算符用于判断文档中是否存在某个字段。例如,要查找users集合中存在email字段的用户:

async function findUserWithEmail() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ email: { $exists: true } });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserWithEmail();

要查找不存在phone字段的用户,可以将$exists的值设为false

async function findUserWithoutPhone() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ phone: { $exists: false } });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserWithoutPhone();

数组相关查询

数组包含元素查询

  1. 简单包含(直接匹配):如果集合中的文档包含数组字段,例如users集合中的hobbies字段是一个数组,要查找爱好中有“reading”的用户:
async function findUserWithReadingHobby() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ hobbies: 'reading' });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserWithReadingHobby();
  1. $in运算符$in运算符用于查询数组字段中包含多个值中的任意一个的文档。比如,查找爱好中有“reading”或者“swimming”的用户:
async function findUserWithCertainHobbies() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ hobbies: { $in: ['reading', 'swimming'] } });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserWithCertainHobbies();

数组元素个数查询

$size运算符用于查询数组字段具有特定元素个数的文档。例如,查找hobbies数组中有3个爱好的用户:

async function findUserWith3Hobbies() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ hobbies: { $size: 3 } });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserWith3Hobbies();

数组中对象的查询

假设users集合中的文档addresses字段是一个包含对象的数组,每个对象包含citystreet字段。要查找住在“Beijing”的用户的地址:

async function findUserInBeijingAddresses() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ "addresses.city": 'Beijing' });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserInBeijingAddresses();

如果要更精确地查询数组中对象的多个条件,比如住在“Beijing”且街道是“Wangfujing Street”的地址:

async function findUserInBeijingWangfujing() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({
            "addresses": {
                $elemMatch: {
                    city: 'Beijing',
                    street: 'Wangfujing Street'
                }
            }
        });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserInBeijingWangfujing();

这里的$elemMatch用于匹配数组中满足所有指定条件的元素。

正则表达式查询

MongoDB支持使用正则表达式进行文本查询。例如,要查找name字段以“J”开头的用户:

async function findUserWithNameStartingWithJ() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ name: /^J/ });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserWithNameStartingWithJ();

如果要查找name字段包含“on”的用户:

async function findUserWithNameContainingOn() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ name: /on/ });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserWithNameContainingOn();

正则表达式在MongoDB查询中非常强大,可以进行复杂的文本匹配,结合i标志可以进行不区分大小写的查询。例如,查找name字段包含“jo”不区分大小写的用户:

async function findUserWithNameContainingJoIgnoreCase() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({ name: /jo/i });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserWithNameContainingJoIgnoreCase();

地理空间查询

MongoDB对地理空间数据的查询提供了很好的支持,特别是对于存储位置信息的应用场景。假设我们有一个restaurants集合,每个文档包含一个location字段,存储餐厅的经纬度坐标(以GeoJSON格式)。

基于距离的查询

  1. $near运算符$near用于查找距离某个点一定范围内的文档。首先,需要确保location字段建立了2dsphere索引:
async function create2dsphereIndex() {
    try {
        await client.connect();
        const database = client.db('test');
        const restaurants = database.collection('restaurants');
        await restaurants.createIndex({ location: "2dsphere" });
        console.log('Index created successfully');
    } finally {
        await client.close();
    }
}

create2dsphereIndex();

然后,假设我们要查找距离坐标[longitude, latitude]10公里内的餐厅:

async function findNearbyRestaurants() {
    try {
        await client.connect();
        const database = client.db('test');
        const restaurants = database.collection('restaurants');
        const center = [-73.9857, 40.7588];
        const cursor = restaurants.find({
            location: {
                $near: {
                    $geometry: {
                        type: "Point",
                        coordinates: center
                    },
                    $maxDistance: 10000 // 10公里,单位为米
                }
            }
        });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findNearbyRestaurants();
  1. $geoWithin运算符$geoWithin可以用于查询在一个给定几何形状内的文档。例如,查询在一个多边形内的餐厅:
async function findRestaurantsInPolygon() {
    try {
        await client.connect();
        const database = client.db('test');
        const restaurants = database.collection('restaurants');
        const polygon = {
            type: "Polygon",
            coordinates: [
                [
                    [-73.99, 40.74],
                    [-73.97, 40.74],
                    [-73.97, 40.76],
                    [-73.99, 40.76],
                    [-73.99, 40.74]
                ]
            ]
        };
        const cursor = restaurants.find({
            location: {
                $geoWithin: {
                    $geometry: polygon
                }
            }
        });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findRestaurantsInPolygon();

文本搜索

MongoDB支持文本搜索,这对于处理大量文本数据非常有用。首先,需要在要搜索的字段上创建文本索引。假设articles集合包含titlecontent字段,我们要对这两个字段进行文本搜索:

async function createTextIndex() {
    try {
        await client.connect();
        const database = client.db('test');
        const articles = database.collection('articles');
        await articles.createIndex({ title: "text", content: "text" });
        console.log('Text index created successfully');
    } finally {
        await client.close();
    }
}

createTextIndex();

然后,可以使用$text运算符进行文本搜索。例如,搜索标题或内容中包含“mongodb”的文章:

async function searchArticles() {
    try {
        await client.connect();
        const database = client.db('test');
        const articles = database.collection('articles');
        const cursor = articles.find({
            $text: {
                $search: "mongodb"
            }
        });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

searchArticles();

文本搜索还支持一些选项,比如$language用于指定语言,$caseSensitive用于指定是否区分大小写等。例如,进行不区分大小写的法语文章搜索:

async function searchFrenchArticles() {
    try {
        await client.connect();
        const database = client.db('test');
        const articles = database.collection('articles');
        const cursor = articles.find({
            $text: {
                $search: "mongodb",
                $language: "fr",
                $caseSensitive: false
            }
        });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

searchFrenchArticles();

聚合查询中的查询条件

聚合查询(aggregation)在MongoDB中用于对数据进行复杂的处理和分析。在聚合管道(aggregation pipeline)中,$match阶段可以用于筛选文档,其语法与find()方法中的查询条件类似。例如,假设orders集合存储订单信息,每个订单包含amount(金额)和status(状态)字段。要统计金额大于100且状态为“completed”的订单数量:

async function countCompletedOrders() {
    try {
        await client.connect();
        const database = client.db('test');
        const orders = database.collection('orders');
        const pipeline = [
            {
                $match: {
                    amount: { $gt: 100 },
                    status: 'completed'
                }
            },
            {
                $count: 'total'
            }
        ];
        const result = await orders.aggregate(pipeline).toArray();
        console.log(result);
    } finally {
        await client.close();
    }
}

countCompletedOrders();

在聚合中,$match阶段可以放在管道的不同位置,合理安排其位置可以提高查询效率。例如,如果数据量很大,先使用$match筛选出一部分数据,再进行后续的聚合操作,会减少处理的数据量,从而提升性能。

查询优化

  1. 索引的使用:合理创建索引是优化查询的关键。通过explain()方法可以查看查询的执行计划,了解索引的使用情况。例如,对于前面查找年龄大于30岁的用户的查询:
async function explainQuery() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const result = await users.find({ age: { $gt: 30 } }).explain('executionStats');
        console.log(result);
    } finally {
        await client.close();
    }
}

explainQuery();

如果查询没有使用索引,可以考虑在age字段上创建索引:

async function createAgeIndex() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        await users.createIndex({ age: 1 });
        console.log('Age index created successfully');
    } finally {
        await client.close();
    }
}

createAgeIndex();
  1. 减少返回字段:只返回需要的字段可以减少网络传输和处理的数据量。例如,只返回users集合中用户的nameage字段:
async function findUserLimitedFields() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const cursor = users.find({}, { name: 1, age: 1, _id: 0 });
        const results = await cursor.toArray();
        console.log(results);
    } finally {
        await client.close();
    }
}

findUserLimitedFields();

这里_id字段默认会返回,如果不需要可以显式设置为0。

  1. 批量操作:对于多次查询操作,可以考虑合并为批量操作,减少与数据库的交互次数。例如,使用bulkWrite()方法批量插入和更新文档。假设我们有多个用户数据要插入users集合:
async function bulkInsertUsers() {
    try {
        await client.connect();
        const database = client.db('test');
        const users = database.collection('users');
        const operations = [
            { insertOne: { document: { name: 'User1', age: 28 } } },
            { insertOne: { document: { name: 'User2', age: 30 } } }
        ];
        const result = await users.bulkWrite(operations);
        console.log(result);
    } finally {
        await client.close();
    }
}

bulkInsertUsers();

通过深入理解和灵活运用上述MongoDB查询条件,开发人员可以高效地从数据库中检索和处理所需的数据,满足各种复杂的业务需求。在实际应用中,还需要根据数据量、查询频率等因素进行优化,以确保系统的高性能和稳定性。