MongoDB查询总结

MongoDB查询总结


介绍

前面写过一篇关于Mongo�db的例子——浅谈MongoDB数据库,当时使用的只是简单的查询,然后后面业务变的有点复杂,原先没有仔细研究过Mongodb的查询,以为就是简单调用下find就可以了,乃衣服。

所以今天特地举例说明一下Mongo中查询问题。

Mongo查询可以�分为2种:

  • 普通查询,类似于Sql中的 select where

  • 聚合查询,类似于Sql中的 group by

普通查询

首先放一下官方文档,普通查询主要用到db.collection.find()函数。

定义下示例数据库,下面是是初始化数据,可以在Mongo中的控制台�执行。

db.inventory.insertMany([
   { item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
   { item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
   { item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
   { item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
   { item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }
]);
  • 查询所有
db.inventory.find( {} )

映射Sql语句

SELECT * FROM inventory
  • 条件查询

语法格式

{ <field1>: <value1>, ... }

比如查询statusD记录。

db.inventory.find( { status: "D" } )

映射Sql语句

SELECT * FROM inventory WHERE status = "D"
  • 使用操作符进行条件查询

语法格式

{ <field1>: { <operator1>: <value1> }, ... }

比如查询满足status是�数组[A,D]中的记录

db.inventory.find( { status: { $in: [ "A", "D" ] } } )

映射Sql语句

SELECT * FROM inventory WHERE status in ("A", "D")
  • AND 条件查询

直接在find函数指定多个字段满足即可,这样就是 and 条件。

比如下面语句就是 statusAqty 小于 30

db.inventory.find( { status: "A", qty: { $lt: 30 } } )

映射Sql语句

SELECT * FROM inventory WHERE status = "A" AND qty < 30
  • OR 条件查询

�OR 和 AND 就不一样了,需要用到操作符 $or,如下所示。

db.inventory.find( { $or: [ { status: "A" }, { qty: { $lt: 30 } } ] } )

类似于SQL中的

SELECT * FROM inventory WHERE status = "A" OR qty < 30
  • OR 和 AND 集合一起
db.inventory.find( {
     status: "A",
     $or: [ { qty: { $lt: 30 } }, { item: /^p/ } ]
} )

表示这样的意思。

SELECT * FROM inventory WHERE status = "A" AND ( qty < 30 OR item LIKE "p%")

查询举例

  • 查询全部
SELECT *
FROM people
db.people.find()
  • 指定字段
SELECT id,
       user_id,
       status
FROM people
db.people.find(
    { },
    { user_id: 1, status: 1 }
)
SELECT user_id, status
FROM people
  • 指定字段,不显示_id
db.people.find(
    { },
    { user_id: 1, status: 1, _id: 0 }
)
  • 条件查询全部
SELECT *
FROM people
WHERE status = "A"
db.people.find(
    { status: "A" }
)
  • 条件查询�指定字段
SELECT user_id, status
FROM people
WHERE status = "A"
db.people.find(
    { status: "A" },
    { user_id: 1, status: 1, _id: 0 }
)
  • 条件查询不等于
SELECT *
FROM people
WHERE status != "A"
db.people.find(
    { status: { $ne: "A" } }
)
  • 条件查询 AND
SELECT *
FROM people
WHERE status = "A"
AND age = 50
db.people.find(
    { status: "A",
      age: 50 }
)
  • 条件查询 OR
SELECT *
FROM people
WHERE status = "A"
OR age = 50
db.people.find(
    { $or: [ { status: "A" } ,
             { age: 50 } ] }
)
  • 条件查询 �>
SELECT *
FROM people
WHERE age > 25
db.people.find(
    { age: { $gt: 25 } }
)
  • 条件查询 �<
SELECT *
FROM people
WHERE age < 25
db.people.find(
   { age: { $lt: 25 } }
)
  • �复杂的条件查询
SELECT *
FROM people
WHERE age > 25
AND   age <= 50
db.people.find(
   { age: { $gt: 25, $lte: 50 } }
)
  • 条件查询 �LIKE
SELECT *
FROM people
WHERE user_id like "%bc%"
db.people.find( { user_id: /bc/ } )

// OR

db.people.find( { user_id: { $regex: /bc/ } } )
SELECT *
FROM people
WHERE user_id like "bc%"
db.people.find( { user_id: /^bc/ } )

// OR

db.people.find( { user_id: { $regex: /^bc/ } } )
  • 排序
SELECT *
FROM people
WHERE status = "A"
ORDER BY user_id ASC
db.people.find( { status: "A" } ).sort( { user_id: 1 } )
SELECT *
FROM people
WHERE status = "A"
ORDER BY user_id DESC
db.people.find( { status: "A" } ).sort( { user_id: -1 } )
  • 统计数量
SELECT COUNT(*)
FROM people
db.people.count()

// or

db.people.find().count()
SELECT COUNT(user_id)
FROM people
db.people.count( { user_id: { $exists: true } } )
or
db.people.find( { user_id: { $exists: true } } ).count()
SELECT COUNT(*)
FROM people
WHERE age > 30
db.people.count( { age: { $gt: 30 } } )

// or

db.people.find( { age: { $gt: 30 } } ).count()
  • 去除重复distinct
SELECT DISTINCT(status)
FROM people
db.people.distinct( "status" )
SELECT *
FROM people
LIMIT 1
  • 限制数量
db.people.findOne()

// or

db.people.find().limit(1)
SELECT *
FROM people
LIMIT 5
SKIP 10
db.people.find().limit(5).skip(10)
  • EXPLAIN
EXPLAIN SELECT *
FROM people
WHERE status = "A"
db.people.find( { status: "A" } ).explain()

聚合查询

上面�普通查询使用find函数即可,但是聚合查询使用另外一个函数aggregate,这里是官方文档

初始化数据如下,有2个表 ordersorder_lineitem �,外键关联order_lineitem.order_id and the orders.id

{
  cust_id: "abc123",
  ord_date: ISODate("2012-11-02T17:04:11.102Z"),
  status: 'A',
  price: 50,
  items: [ { sku: "xxx", qty: 25, price: 1 },
           { sku: "yyy", qty: 25, price: 1 } ]
}
  • 统计数量
db.orders.aggregate( [
   {
     $group: {
        _id: null,
        count: { $sum: 1 }
     }
   }
] )

映射Sql语句

SELECT COUNT(*) AS count
FROM orders
  • 计算总和
db.orders.aggregate( [
   {
     $group: {
        _id: null,
        total: { $sum: "$price" }
     }
   }
] )

映射Sql语句

SELECT SUM(price) AS total
FROM orders
  • 分组计算总和
db.orders.aggregate( [
   {
     $group: {
        _id: "$cust_id",
        total: { $sum: "$price" }
     }
   }
] )

映射Sql语句

SELECT cust_id,
       SUM(price) AS total
FROM orders
GROUP BY cust_id
  • 分组计算总和并排序
db.orders.aggregate( [
   {
     $group: {
        _id: "$cust_id",
        total: { $sum: "$price" }
     }
   },
   { $sort: { total: 1 } }
] )

映射Sql语句

SELECT cust_id,
       SUM(price) AS total
FROM orders
GROUP BY cust_id
ORDER BY tota
  • 多个字段分组
db.orders.aggregate( [
   {
     $group: {
        _id: {
           cust_id: "$cust_id",
           ord_date: {
               month: { $month: "$ord_date" },
               day: { $dayOfMonth: "$ord_date" },
               year: { $year: "$ord_date"}
           }
        },
        total: { $sum: "$price" }
     }
   }
] )

映射Sql语句

SELECT cust_id,
       ord_date,
       SUM(price) AS total
FROM orders
GROUP BY cust_id,
         ord_date
  • 条件分组——HAVING
db.orders.aggregate( [
   {
     $group: {
        _id: "$cust_id",
        count: { $sum: 1 }
     }
   },
   { $match: { count: { $gt: 1 } } }
] )

映射Sql语句

SELECT cust_id,
       count(*)
FROM orders
GROUP BY cust_id
HAVING count(*) > 1
  • 复杂条件分组统计
db.orders.aggregate( [
   {
     $group: {
        _id: {
           cust_id: "$cust_id",
           ord_date: {
               month: { $month: "$ord_date" },
               day: { $dayOfMonth: "$ord_date" },
               year: { $year: "$ord_date"}
           }
        },
        total: { $sum: "$price" }
     }
   },
   { $match: { total: { $gt: 250 } } }
] )

映射Sql语句

SELECT cust_id,
       ord_date,
       SUM(price) AS total
FROM orders
GROUP BY cust_id,
         ord_date
HAVING total > 250
  • 复杂条件分组统计示例1
db.orders.aggregate( [
   { $match: { status: 'A' } },
   {
     $group: {
        _id: "$cust_id",
        total: { $sum: "$price" }
     }
   }
] )

映射Sql语句

SELECT cust_id,
       SUM(price) as total
FROM orders
WHERE status = 'A'
GROUP BY cust_id
  • 复杂条件分组统计示例2
db.orders.aggregate( [
   { $match: { status: 'A' } },
   {
     $group: {
        _id: "$cust_id",
        total: { $sum: "$price" }
     }
   },
   { $match: { total: { $gt: 250 } } }
] )

映射Sql语句

SELECT cust_id,
       SUM(price) as total
FROM orders
WHERE status = 'A'
GROUP BY cust_id
HAVING total > 250
  • 表关联
db.orders.aggregate( [
   { $unwind: "$items" },
   {
     $group: {
        _id: "$cust_id",
        qty: { $sum: "$items.qty" }
     }
   }
] )

映射Sql语句

SELECT cust_id,
       SUM(li.qty) as qty
FROM orders o,
     order_lineitem li
WHERE li.order_id = o.id
GROUP BY cust_id
  • 嵌套查询
db.orders.aggregate( [
   {
     $group: {
        _id: {
           cust_id: "$cust_id",
           ord_date: {
               month: { $month: "$ord_date" },
               day: { $dayOfMonth: "$ord_date" },
               year: { $year: "$ord_date"}
           }
        }
     }
   },
   {
     $group: {
        _id: null,
        count: { $sum: 1 }
     }
   }
] )

映射Sql语句

SELECT COUNT(*)
FROM (SELECT cust_id,
             ord_date
      FROM orders
      GROUP BY cust_id,
               ord_date)
      as DerivedTable

Map-Reduce

Mongo中聚合查询还有一种叫Map-Reduce,官方文档在这里,在思想上它跟Hadoop一样,从一个单一集合中输入数据,然后将结果输出到一个集合中。通常在使用类似SQL中Group By操作时,Map/Reduce会是一个好的工具。

Map-Reduce
Map-Reduce

接口方法定义

db.collection.mapReduce(
    <map>,
    <reduce>,
    {
        out: <collection>,
        query: <document>,
        sort: <document>,
        limit: <number>,
        finalize: <function>,
        scope: <document>,
        jsMode: <boolean>,
        verbose: <boolean>,
        bypassDocumentValidation: <boolean>
    }
)

参数说明

  • mapReduce: 要执行Map/Reduce集合的名字

  • map: map 函数 (下面会详细介绍)

  • reduce: reduce函数(下面会详细介绍)

  • out: 存放结果的集合 (下面会详细介绍)

  • query: 设置查询条件 <可选>

  • sort: 按某个键来排序 <可选>

  • limit: 指明从集合检索文档个数的最大值 <可选>

  • finalize: 对reduce结果做进一步处理 <可选>

  • scope: 指明通过map/reduce/finalize可以访问到的变量 <可选>

  • jsMode: 指明Map/Reduce执行过程中文档保持JSON状态 <可选>

  • verbose: 提供关于任务执行的统计数据 <可选>

示例说明

�举例说明Map-Reduce的用途,�虽然代码比较多,也行用上面的聚合查询,一下子就搞定了,但是这里只是举例。

比如有个订单表,如下所示,我们需要计算每个人的订单总价。

{
     _id: ObjectId("50a8240b927d5d8b5891743c"),
     cust_id: "abc123",
     ord_date: new Date("Oct 04, 2012"),
     status: 'A',
     price: 25,
     items: [ { sku: "mmm", qty: 5, price: 2.5 },
              { sku: "nnn", qty: 5, price: 2.5 } ]
}

首先定义Map方法,就说我们后面的聚合计算需要哪些字段,�由于需要计算每个人的订单总结,那么个人信息和加个肯定是我们需要的。

var mapFunction1 = function() {
    emit(this.cust_id, this.price);
};

然后定义reduce方法,计算每个人的订单价格。

var reduceFunction1 = function(keyCustId, valuesPrices) {
    return Array.sum(valuesPrices);
};

然后存储最后的计算结果。

db.orders.mapReduce(
    mapFunction1,
    reduceFunction1,
    { out: "map_reduce_example" }
)

这样一个简单的Map-Reduce实例就完成了,结果放在map_reduce_example中。

上面示例比较简单,那么我们来一个复杂一点的例子。

一条订单记录中,有sdk的名称、数量、价格,那么要查询出日期大于01/01/2012,所有订单的总数,以及�平均sdk价格。

首先还是定义个map函数。

var mapFunction2 = function() {
    for (var idx = 0; idx < this.items.length; idx++) {
        var key = this.items[idx].sku;
        var value = {
                        count: 1,
                        qty: this.items[idx].qty
                    };
        emit(key, value);
    }
};

然后算出sku的数量,和总价格。

var reduceFunction2 = function(keySKU, countObjVals) {
    reducedVal = { count: 0, qty: 0 };

    for (var idx = 0; idx < countObjVals.length; idx++) {
        reducedVal.count += countObjVals[idx].count;
        reducedVal.qty += countObjVals[idx].qty;
    }

    return reducedVal;
};

总价格出来后,还要计算出平均价格。

var finalizeFunction2 = function (key, reducedVal) {
    reducedVal.avg = reducedVal.qty / reducedVal.count;
    return reducedVal;
};

还有日期的条件过滤,最后得出完整的map-reduce。

db.orders.mapReduce(
    mapFunction2,
    reduceFunction2,
    {
        out: { merge: "map_reduce_example" },
        query: {
            ord_date:{ $gt: new Date('01/01/2012') }
        },
        finalize: finalizeFunction2
    }
)

总结

以上就是我对MongoDB的示例总结,本人是一个初学者,也有很多地方不懂,如果有错误的地方,欢迎指出。

相关资料

浅谈MongoDB数据库

普通查询官方文档

Sql和Mongo隐射表

聚合官方文档

Map-Reduce官方文档

Map-Reduce API

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容