「MongoDB数据库」初见

|-----------------------|
| 你好,我是安然无虞。 |
| 和我一起,为高质量人生而不懈奋斗。 |

文章目录

MongoDB简介

MongoDB是一个基于分布式文件存储的数据库. 由C++语言编写. 旨在为WEB应用提供可扩展的高性能数据存储解决方案.

MongoDB是一个介于 关系数据库 和 非关系数据库 之间的产品, 是非关系数据库当中功能最丰富、最像关系数据库的.

它支持的数据结构非常松散, 类似 json的bson格式, 因此可以存储比较复杂的数据类型.

MongoDB最大的特点是它支持的查询语言非常强大, 其语法有点类似于面向对象的查询语言, 几乎可以实现类似关系数据库单表查询的绝大部分功能, 而且还支持对数据建立 索引.

并且无模式, 灵活的文档类型, 无需固定的结构.

命名规则:

  1. 不能是空字符串.
  2. 不能包含空字符.
  3. 不能使用system前缀, 因为是系统保留的.
  4. 建议不包含$.

MongoDB 对比 MySQL

mongo概念 MySQL概念 说明
database database 数据库
collection table 集合 - 表
document row 文档 - 数据行
field column 字段 - 属性
index index 索引
嵌入文档 join 使用嵌入式的文档来代替关联查询
primary key primary key mongodb自动生成主键 _id

基本指令

macOS中已通过 brew services start mongodb-community@8.2 运行`本地 MongoDB 服务.

接着使用 mongosh 指令启动并连接 MongoDB.

  1. 显示当前的所有数据库

    shell 复制代码
    show dbs
    show databases
    
    # 比如看系统中默认的数据库
    test> show dbs
    admin    40.00 KiB
    config  100.00 KiB
    local    64.00 KiB
  2. 进入到指定的数据库中

    shell 复制代码
    use 数据库名
    
    # 比如:
    test> use local
    switched to db local
    local>
  3. 当前所处的数据库

    shell 复制代码
    db
    
    local> db
    local
  4. 显示数据库中所有的集合 (表)

    shell 复制代码
    show collections
    
    local> show collections
    startup_log

增删查改

增加

向数据库中插入一个文档

shell 复制代码
db.{collection}.insert(document)

# 例如:
# 新建一个数据库yrx
test> use yrx
switched to db yrx
yrx> db
yrx
# 因为此时新数据库yrx还没有数据,所以暂时看不到yrx
yrx> show dbs
admin    40.00 KiB
config  108.00 KiB
local    40.00 KiB

# 向yrx中插入一个集合(表)company
yrx> db.company.insert({name: 'zhangsan', age: 18, gender: 'male'})
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('69756bcaffa7a096b3e7e146') }
}

yrx> show collections
company
yrx>

# 新数据库yrx中有集合(表)数据了,现在可以看到yrx了
yrx> show dbs
admin   40.00 KiB
config  96.00 KiB
local   64.00 KiB
yrx     40.00 KiB

上面是向数据库中插入一个文档, 当插入多个时, 可以这样:

shell 复制代码
db.company.insert([{name: 'lisi', age: 20, gender: 'male'}, {name: 'lulu', age: 18, gender: 'female'}])

db.company.insert([{name: 'zhangwen', age: 23, gender: 'male'}, {name: 'liwen', age: 25, gender: 'female'}, {name: 'zhou', gender: 'female'}])

看插入结果:

shell 复制代码
yrx> db.company.insert([{name: 'zhangwen', age: 23, gender: 'male'}, {name: 'liwen', age: 25, gender: 'female'}, {name: 'zhou', gender: 'female'}])
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('69793ef52377fa2311816ff1'),
    '1': ObjectId('69793ef52377fa2311816ff2'),
    '2': ObjectId('69793ef52377fa2311816ff3')
  }
}

这里我们能看出 MongoDB 会自动生成主键 _id.

我们来查看一下可视化程序里面的数据:

json 复制代码
{
    "_id" : ObjectId("69756bcaffa7a096b3e7e146"),
    "name" : "zhangsan",
    "age" : NumberInt(18),
    "gender" : "male"
}
{
    "_id" : ObjectId("69793e7d2377fa2311816fef"),
    "name" : "lisi",
    "age" : NumberInt(20),
    "gender" : "male"
}
{
    "_id" : ObjectId("69793e7d2377fa2311816ff0"),
    "name" : "lulu",
    "age" : NumberInt(18),
    "gender" : "female"
}
......

这里的 主键_id 是可以自己指定的, 但是自己指定的话, 要确保其唯一性.

比如:

shell 复制代码
db.company.insert({_id: 123, name: 'zhang', age: 18, gender: 'male'})

除了 insert() 方法之外, 还有其他的方法, 比如:

  • insertOne(): 插入一个文档
  • insertMany(): 插入多个文档

上面的方法语义明确、易懂.

查询

查询文档.

shell 复制代码
db.{collection}.find()

用于查询集合中的文档, 可以接收一个对象作为查询条件.

比如我们想查询前面集合中所有的文档, 可以:

shell 复制代码
db.company.find({})

如果我们想查询集合中指定的字段, 比如年龄是18岁的人:

shell 复制代码
db.company.find({age: 18})

我们也可以使用 findOne()来返回查询到的第一条数据.

countDocuments()方法 返回查询结果的数量.

比如:

shell 复制代码
yrx> db.company.countDocuments()
7
yrx> db.company.countDocuments({age: 18})
3

查询指定字段, 在查询条件后再加一个对象, 指明你想要的字段, 不想要查询出来的字段赋值为0:

shell 复制代码
yrx> db.company.find({age: 18.0}, {name: 1, _id: 0})
[ { name: 'zhangsan' }, { name: 'lulu' }, { name: 'zhang' } ]

# 如果没有指明不想要的字段, 默认会有主键------id
yrx> db.company.find({age: 18.0}, {name: 1})
[
  { _id: ObjectId('69756bcaffa7a096b3e7e146'), name: 'zhangsan' },
  { _id: ObjectId('69793e7d2377fa2311816ff0'), name: 'lulu' },
  { _id: 123, name: 'zhang' }
]

常用的比较运算符:

  • $gt: 大于
  • $lt: 小于
  • $gte: 大于等于
  • $lte: 小于等于
  • $ne: 不等于
shell 复制代码
db.company.find({age: {$ne: 18}})

yrx> db.company.countDocuments({age: {$ne: 18}})
4

更新

shell 复制代码
db.{collection}.update(查询条件, 新对象)

旧写法: update() 默认只会更新第一个数据, 如果要更新多个数据, 需要加上 multi 参数, 并设置为true.

新写法对比:

  • updateOne()
  • updateMany()
旧写法(已弃用) 新写法(推荐) 说明
update(filter, update) updateOne(filter, update) 更新第一个匹配项
update(filter, update, { multi: true }) updateMany(filter, update) 更新所有匹配项

使用更新操作符: $set 修改文档中指定字段的值.

例如: 将 liwen 的名字改成 liwenwen.

shell 复制代码
db.company.updateOne({name: 'liwen'}, {$set: {name: 'liwenwen'}})

使用 $unset删除文档中指定的字段.

shell 复制代码
db.company.updateOne({_id: 123}, {$unset: {age: 1}})

删除

删除操作了解即可. 真实工作中尽量别用.

一般都是做逻辑删除, 比如: 新增一个字段 is_delete, 并且设置is_delete=true.

shell 复制代码
db.{collection}.remove()

删除符合条件的所有数据.

删除一个justOne 传true, 则只删除一个.

必须传递参数, 不传参会报错, 如果传递一个{}, 则删除所有.

db.{collection}.deleteOne(): 删除一个.

db.{collection}.deleteMany(): 删除多个.

db.{collection}.drop(): 删除集合.

db.{collection}.dropDatabase(): 删除数据库.

排序 sort

sort()

需要传递一个参数来指定排序规则:

  • 1为升序
  • -1为降序

limit skip

  • limit: 限制返回的结果数
  • skip: 跳过结果的数量

常用的操作符

$regex

$regex: 正则表达式, 可用于模糊查询.

shell 复制代码
db.company.find({name: {$regex: /.*?wen/}})

运行结果是:

shell 复制代码
yrx> db.company.find({name: {$regex: /.*?wen/}})
[
  {
    _id: ObjectId('69793ef52377fa2311816ff1'),
    name: 'zhangwen',
    age: 23,
    gender: 'male'
  },
  {
    _id: ObjectId('69793ef52377fa2311816ff2'),
    name: 'liwenwen',
    age: 25,
    gender: 'female'
  }
]

$exists

$exists: 某字段是否存在.

shell 复制代码
db.company.find({is_deleted: {$exists: false}})

$in

$in: 包含.

shell 复制代码
db.company.find({age: {$in: [20, 25]}})

and or

  • $and: 与.

  • $or: 或.

查找年龄是23岁, 并且名字是zhangwen, 或者年龄是25岁的.

shell 复制代码
 db.company.find({$or: [{$and: [{age: 23}, {name: 'zhangwen'}]}, {age: 25}]})

$inc

$inc: 值增加或者减少.

shell 复制代码
db.company.update({name: 'zhangwen'}, {$inc: {age: -5}})

aggregate 聚合操作

新建一个集合: sales.

shell 复制代码
db.sales.insertMany([
	{ "_id" : 1, "item" : "铅笔", "price" : 5, "quantity" : 2, tags: ["blank", "red"], size:[10, 2, 3]},
	{ "_id" : 2, "item" : "钢笔", "price" : 20, "quantity" : 2, tags: ["red", "blank"], size:[10, 3, 3]},
	{ "_id" : 3, "item" : "作业本", "price" : 5, "quantity" : 10, tags: ["red", "blank", "plain"], size:[10, 12, 3]},
	{ "_id" : 4, "item" : "铅笔", "price" : 8, "quantity" : 20, tags: ["blank", "red"], size:[10, 2, 4]},
	{ "_id" : 5, "item" : "作业本", "price" : 10, "quantity" : 10, tags: ["blue"], size:[20, 10, 3]}
])
shell 复制代码
$group == MySQL group by
_id: 对应想要分组的字段 # 注意和集合中主键进行区分
  1. 按照 item 字段进行分组,统计每个 item 下面的文档个数:

    shell 复制代码
    db.sales.aggregate([{$group: {_id: '$item', count: {$sum: 1}}}])

    运行结果是:

    shell 复制代码
    test> db.sales.aggregate([{$group: {_id: '$item', count: {$sum: 1}}}])
    
    [
      { _id: '钢笔', count: 1 },
      { _id: '铅笔', count: 2 },
      { _id: '作业本', count: 2 }
    ]
  2. 按照 item 字段进行分组,统计每个 item 下面 quantity 的总数,price的平均数

    shell 复制代码
    test> db.sales.aggregate([{$group: {_id: 'item', count: {$sum: '$quantity'}, price_avg: {$avg: '$price'}}}])
    
    [ { _id: 'item', count: 44, price_avg: 9.6 } ]
  3. 按照 item 字段进行分组,统计每个 item 下面 price的最大值与最小值.

    shell 复制代码
    test> db.sales.aggregate([{$group: {_id: '$item', max_price: {$max: '$price'}, min_price: {$min: '$price'}}}])
    
    [
      { _id: '钢笔', max_price: 20, min_price: 20 },
      { _id: '铅笔', max_price: 8, min_price: 5 },
      { _id: '作业本', max_price: 10, min_price: 5 }
    ]

数组查询

  1. tags值是只包含两个元素"red","blank"并且有指定顺序.

    shell 复制代码
    test> db.sales.find({tags: ["red", "blank"]})
    
    [
      {
        _id: 2,
        item: '钢笔',
        price: 20,
        quantity: 2,
        tags: [ 'red', 'blank' ],
        size: [ 10, 3, 3 ]
      }
    ]
  2. tags值是只包含两个元素"red","blank"不指定顺序, 只要有这2个即可, 不关心数组中是否有其它元素.

    shell 复制代码
    test> db.sales.find({tags: {$all: ["red", "blank"]}})
    
    [
      {
        _id: 1,
        item: '铅笔',
        price: 5,
        quantity: 2,
        tags: [ 'blank', 'red' ],
        size: [ 10, 2, 3 ]
      },
      {
        _id: 2,
        item: '钢笔',
        price: 20,
        quantity: 2,
        tags: [ 'red', 'blank' ],
        size: [ 10, 3, 3 ]
      },
      {
        _id: 3,
        item: '作业本',
        price: 5,
        quantity: 10,
        tags: [ 'red', 'blank', 'plain' ],
        size: [ 10, 12, 3 ]
      },
      {
        _id: 4,
        item: '铅笔',
        price: 8,
        quantity: 20,
        tags: [ 'blank', 'red' ],
        size: [ 10, 2, 4 ]
      }
    ]
  3. tags中有一个元素的值是"red"的所有文档.

    shell 复制代码
    test> db.sales.find({tags: 'red'})
    
    [
      {
        _id: 1,
        item: '铅笔',
        price: 5,
        quantity: 2,
        tags: [ 'blank', 'red' ],
        size: [ 10, 2, 3 ]
      },
      {
        _id: 2,
        item: '钢笔',
        price: 20,
        quantity: 2,
        tags: [ 'red', 'blank' ],
        size: [ 10, 3, 3 ]
      },
      {
        _id: 3,
        item: '作业本',
        price: 5,
        quantity: 10,
        tags: [ 'red', 'blank', 'plain' ],
        size: [ 10, 12, 3 ]
      },
      {
        _id: 4,
        item: '铅笔',
        price: 8,
        quantity: 20,
        tags: [ 'blank', 'red' ],
        size: [ 10, 2, 4 ]
      }
    ]
  4. size中至少有一个大于10的.

    shell 复制代码
    test> db.sales.find({size: {$gt: 10}})
    [
      {
        _id: 3,
        item: '作业本',
        price: 5,
        quantity: 10,
        tags: [ 'red', 'blank', 'plain' ],
        size: [ 10, 12, 3 ]
      },
      {
        _id: 5,
        item: '作业本',
        price: 10,
        quantity: 10,
        tags: [ 'blue' ],
        size: [ 20, 10, 3 ]
      }
    ]
  5. size中的第二个必须大于10的.

    shell 复制代码
    test> db.sales.find({'size.1': {$gt: 10}})
    [
      {
        _id: 3,
        item: '作业本',
        price: 5,
        quantity: 10,
        tags: [ 'red', 'blank', 'plain' ],
        size: [ 10, 12, 3 ]
      }
    ]

Python操作MongoDB

Python 操作MongoDB很简单, 语法和前面差不多:

python 复制代码
import pymongo

"""
稍微封装一下.
"""

class Config:

    mongo_params_local = {
        'host': '127.0.0.1',
        'port': 27017
    }


class MongoBase:

    def __init__(self, collection_name):
        # 连接MongoDB
        self.mongo_local = pymongo.MongoClient(**Config.mongo_params_local)
        self.collection_name = collection_name
        self.collection = None

    def get_collection(self):
        db = self.mongo_local.yrx
        collection = db[self.collection_name]
        self.collection = collection

    def insert_data(self, data):
        """
        插入数据
        :param data: 需要插入的数据.
        :return: none
        """
        self.collection.insert_one(data)

    def find_data(self, _id):
        res = self.collection.find_one({'_id': _id})
        return res

    def update_data(self, _id, update_field):
        """根据主键id更新数据"""
        self.collection.update_one({'_id': _id}, {'$set': update_field})

    def __del__(self):
        self.mongo_local.close()
        print("mongo连接已关闭")


if __name__ == '__main__':
    mongo_base = MongoBase('company')

继承上面实现的 MongoBase类:

python 复制代码
import re
import time
import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests
from loguru import logger

from _mongo_db import MongoBase


class YrxSpider(MongoBase):

    def __init__(self, collection_name):
        super(YrxSpider, self).__init__(collection_name)
        self.start_url = "http://exam.python-spider.com/spider/search"
        self.page_url = "http://exam.python-spider.com/spider/list"
        self.detail_url = "http://exam.python-spider.com/spider/postDetail"
        self.detail = 'http://exam.python-spider.com/spider/detail?href={}'
        self.executor = ThreadPoolExecutor(8)
        self.get_collection()

    def get_list_data(self, page):
        """获取列表页的数据"""
        response = requests.post(self.page_url, data={"page": page})
        results = response.json()
        return results

    def get_detail_data(self, item):
        """获取详情页的数据"""
        assert isinstance(item, dict), 'item必须为字典.'
        detail_id = item.get('_id')
        response = requests.post(self.detail_url, data={"href": detail_id})
        item['detail_data'] = response.content.decode("unicode_escape")
        item['url'] = self.detail.format(detail_id)
        return item

    def diff_field(self, item, res):
        """
        比对字段.
        :param item: 是我们刚抓到的, 是即将要插入数据库的数据.
        :param res: 是从mongo里获取到的.
        :return: 需要更新的字段.
        """
        update_field = {}
        for key in item.keys():
            if key not in res.keys() or item[key] != res[key]:
                # 如果进入到这个判断, 是不是就代表这个key是需要更新的.
                update_field[key] = item[key]
        return update_field

    def main(self):
        response = requests.get(self.start_url)
        pages = re.search(r'pageNum: (\d+)', response.text).group(1)
        all_tasks = [self.executor.submit(self.get_list_data, page + 1) for page in range(int(pages))]
        for list_result in as_completed(all_tasks):
            message = list_result.result().get('message')
            detail_task_list = []
            for msg in message:
                item = dict()
                item['title'] = msg.get('title')
                item['_id'] = msg.get('href')
                task = self.executor.submit(self.get_detail_data, item)
                detail_task_list.append(task)
            for detail_result in as_completed(detail_task_list):
                data = detail_result.result()
                _id = data.get('_id')
                res = self.find_data(_id)
                if res:
                    # 如果数据进了这个判断, 就代表要么数据有更新, 要么就是重复数据.
                    # 要处理数据有更新的情况和重复数据的情况.
                    # data: 是我们刚抓到的, 是即将要插入数据库的数据.
                    # res: 是从mongo里获取到的.
                    update_field = self.diff_field(data, res)
                    if update_field:
                        # 如果有就更新.
                        update_field['update_time'] = datetime.datetime.now()
                        self.update_data(_id, update_field)
                        logger.success(f'{_id}: 数据更新成功.')
                    else:
                        logger.warning(f'{_id}: 是重复数据.')
                else:
                    data['create_time'] = datetime.datetime.now()
                    data['update_time'] = datetime.datetime.now()
                    self.insert_data(data)
                    logger.success(f'正在插入{data.get("_id")}')


if __name__ == '__main__':
    start = time.perf_counter()
    YrxSpider('project').main() # 新建一张表并将获取的数据插入其中
    print(time.perf_counter() - start)

|----------------------|
| 遇见安然遇见你,不负代码不负卿。 |
| 谢谢老铁的时间,咱们下篇再见~ |

相关推荐
一起养小猫2 小时前
Flutter for OpenHarmony 实战:番茄钟应用完整开发指南
开发语言·jvm·数据库·flutter·信息可视化·harmonyos
Mr_Xuhhh2 小时前
MySQL视图详解:虚拟表的创建、使用与实战
数据库·mysql
AI_56782 小时前
MySQL索引优化全景指南:从慢查询诊断到智能调优
数据库·mysql
老虎06272 小时前
Redis入门,配置,常见面试题总结
数据库·redis·缓存
一起养小猫2 小时前
Flutter for OpenHarmony 实战:数据持久化方案深度解析
网络·jvm·数据库·flutter·游戏·harmonyos
codeRichLife2 小时前
TimescaleDB保存100万条设备采集数据的两种存储方案对比分析
数据库
J&Lu2 小时前
[DDD大营销-Redis]
数据库·redis·缓存
咚咚?2 小时前
麒麟操作系统达梦数据集群安装(一主多从)
数据库
u0109272712 小时前
使用XGBoost赢得Kaggle比赛
jvm·数据库·python