查询Mongodb中的文档ID的格局,基础杂萃

数据库 8
数据库

在IBM我的一份新工作是一名开发的后勤人员。那意味着我的大部分时间是在和数据库打交道。在我的工作流程中,我花了一些时间在MongoDB上面——这是一个文档数据库。但是在通过ID来检索记录这个操作上面我碰到了一些问题。下面的代码是最终版本,以后碰到类似的问题我可以直接引用它。如果大家也需要,希望下面对大家有所帮助。

数据库 1

图片来自Google搜索

MongoDB 和 IDs

一、配置和安装相应的环境

0
由于工作需要,最近开始接触MongoDB,对于一个已经习惯了传统SQL的程序员来说,进入NoSQL可以说是需要莫大的勇气的。最大的不适应,是NoSQL和SQL的存储原理与思维方式的不一致。当然,笔者也是NoSQL中的菜鸟,在这里分享一下自己的一些学习总结与心得。

当我向一个集合中插入数据的时候,我并没有设置_id字段;如果这个字段是空的话,那么MongoDB将要自动生成一个ID来使用,这对我来说是非常不错的。然而,当我使用MongoDB生成的标识符的时候,这样就会出现问题。

主要针对python脚本操作mongdb数据库,所以安装pymongo和mongodb库。

1、数组

在MongoDB中,文档(Document)即表示数据库中的一个集合中的一条记录,相当于关系型数据库中的行(row)。在MongoDB中,数组是使用JSON语法表示的,在MongoDB中,也称为BSON格式。数组既可以作为有序对象来操作,也可以作为无序对象来操作。有序对象比如列表等,无序对象比如集合等等。

{
  "a" : 0,               //这明显不是一个数组
  "b" : [],              //定义一个空的数组 
  "c" : [ "1","2","3"]   //包含3个元素的数组
}

如果我使用db.posts.find()来检索我的数据(我的集合被称为posts),那么数据开起来如下所示:

二、创建连接到mongodb数据库:

2、文档嵌套

在NoSQL中,文档与文档之间是可以随意嵌套的,比如某个字段的值为某个类型的对象。理论上,MongoDB支持无限级的自我嵌套,比如:

{
  "a": {"b":1,"c":2}
}

{ “_id” :
ObjectId(“575038831661d710f04111c1”), …

与PyMongo工作时,第一步是建立一个MongoClient到正在运行的mongod实例。

3、文档标识

如果标记当前这个文档,按照关系型数据库的习惯,则可以说,如果保证一条记录在数据库里是唯一的,在传统SQL中,使用主键ID来标识记录的全局唯一性,而在MongoDB中,则使用ObjectId来确保文档的唯一标识,即_id的默认类型。
ObjectID是一个12字节的BSON数据类型,格式如下:

  1. 前四个字节表示时间戳
  2. 接下来三个字节是机器标识码
  3. 紧接的两个字节由进程ID组成,即PID
  4. 最后三个字节是随机数

MongoDB中的每个文档必须有一个名为_id的键,可以是任意类型,默认为ObjectID类型。

以下是在Shell中关于ObjectId的一些方法

#生成一个新的ObjectId
newObjectId = ObjectId()

#返回的id为:ObjectId("5349b4ddd2781d08c09890f3")

#获取文档
ObjectId("5349b4ddd2781d08c09890f3").getTimestamp()

#返回时间为:ISODate("2016-07-06T21:49:17Z")

#将ObjectId转化为字符串
new ObjectId().str

#返回结果为:5349b4ddd2781d08c09890f3

因此,如果我想用ID来检索数据的话,我还需要包含ObjectId方法来访问ID。

from pymongo import MongoClient
client = MongoClient('localhost', 27017)#比较常用

# client = MongoClient('mongodb://localhost:27017/')使用MongoDB的URI格式
3、添加文档

插入文档使用db.col.insert(document)

db.Collection_Name.insert({
  "name":"demo"
});
  1. 如果文档不包含_id键,MongoDB会自动创建一个ObjectId类型的_id值
  2. 默认情况下插入操作时,MongoDB只检查传入数据是否包含_id以及数据大小是否超过16MB,所以可以得到更高的性能插入,但同时也可能录入无效数据。
  3. 因为在插入时是不执行任何代码的,所以与传统SQL相比,MongoDB不存在SQL注入风险。

使用PHP库

创建常见的方式有两种:一种是连接到默认的主机端口;另外一种是指定主机和固定的端口,注意一般mongodb安装时默认的端口为:27017。

4、删除文档
db.Collection_Name.remove();   //清空集合内的所有文档
db.Collection_Name.remove(    //清空指定文档
  {
     "a":"a"
  }
);

//当集合内数据过多时,可以考虑下面这个方法

db.drop_collection(Collection_Name);  //直接删除集合
db.Collection_Name.ensureIndex();      //重建索引

当我使用PHP来做这个事情的时候,我在使用这个新的PHP类库的时并没有找到一个合适的例子(但是,这个类库确实是一个非常不错的库)。在以前的版本中,这个库使用一个叫做MongoID的类来实现。但是我知道这并不是我想要的——但是我确实可以通过这个类来检查文档。因此如果你仅仅能找到以前代码中的例子,那了解这个方法还是很有用的。
通过用这个PHP库将一个ID传给MongoDB,你需要构造一个MongoDB\BSON\ObjectID实例。下面的例子就是通过文档的Id来检索博客中的文档。

三、创建数据库

5、更新文档
//update语法定义
db.Collection_Name.update(query,document,upsert,multi);

//设定原文档为:
var data = {_id:"xxxx","a":1,"b":2};

//query 是指查询条件,相当于SQL中的where子句,比如:
db.Collection_Name.update(
  {_id:"xxxx","a":1,"b":2},       //定位条件,对符合_id=xxxx,a=1,b=2的文档进行更新
  {"a":2},                        //将a的值改为2,替换整个文档
  true,                           //若查询不到符合条件的文档,则新增一个文档
  true                            //允许更新多行
);
  1. update操作会替换整个匹配的文档。而不是进行某些特定字段的修改。如果需要更新某个特定字段值,则应当使用修改器。
  1. upsert模式是一个布尔值选项,表示是否文档更新时,如果不存在,能够自动创建。
  2. multi模式也是一个布尔值选项,默认情况下只更新匹配到的第一个文档,开启了multi模式后(即设置为true),则会更新所有匹配的文档。

//更新文档使用到的一些修改器,由$符号定义

//$inc 增加或减少数字的值,键不存在时自动创建
db.Collection_Name.update(
  {"name" : "翘着二郎腿打代码"},
  {"$inc" : { "lover" : 1 }}               //只将lover字段的值加1
);

//$set 设置某一项或者多个项目的值
db.Collection_Name.update(
  {"name" : "翘着二郎腿打代码"},
  {"$set" : {"name" : "打代码" }}
);

这里列举一些常用的修改器

$inc      设置自增或者自减
$set      设置指定键的值
$unset    $set的反操作,会删除键及键值
$push     将元素追加到数组末尾,数组不存在则自动创建
$pushAll  $push的批量操作版本
$addToSet 与$push一样,会自动过滤重复元素
$pop      从数组中移除元素,1代表从末尾移除,-1代码从开头移除
$pull     从数组中移除所有匹配的元素
$pullAll  $pull的批量操作版本
$rename   修改制定键的键名
$bit      对整型键进行位操作

另外,还有一种方式可以实现文档的更新:

//使用findAndModify()更新文档
db.Collection_Name.findAndModify(
  {
      'query' : {"name" : "翘着二郎腿打代码" },
      'update' : {"$set" : { "favour" : 100 } },
      'new' : true
  }
);

/**
其中的参数如下:

query   :  查询条件,用来定位到匹配的文档
sort    :  如果匹配到多个文档,指定一个排序方式,-1降序,1升序
remove  :  是否删除匹配的文档
new     :  是否返回更新后的文档
update  :  更新操作
upsert  :  是否自动创建,如果匹配不到文档

**/

save()

//文档不存在时,执行insert操作,存在是执行update操作。
db.demo.save(document);

$post = $posts->findOne([“_id” =>
new MongoDB\BSON\数据库,ObjectID($id)]);

MongoDB中的单个实例可以支持多个独立的数据库。当PyMongo工作访问使用上MongoClient实例属性的风格访问数据库:例如创建一个test_database数据库。

6、查询

我们先来举一些简单的例子:

//select * from demo;
db.demo.find();

//select * from demo where a = 1;
db.demo.find({"a":1});

//select a,b from demo where a = 1;
db.demo.find({"a":1},{a:1,b:1});

//select * from demo where a = 1 order by name asc;
db.demo.find({"a":1}).sort({"name":1});

//select * from demo where a > 1;
db.demo.find({"a":{$gt:1}});

//select * from demo where a like 'eee';
db.demo.find({"name":"/^eee/"});

//select * from demo limit 10 skip 20;
db.demo.find().limit(10).skip(20);

当然还有很多其他的语法形式,这里不再一一列举。下面列举一些常见的查询条件操作符

$lt    #小于
$lte   #小于等于
$gt    #大于
$gte   #大于等于
$all   #完全匹配
$mod   #取模
$ne    #不等于
$in    #在...内
$nin   #不在...内
$nor   #既不...也不...
$or    #或
$size  #匹配数组长度
$type  #匹配数据类型

slice()函数用于数组的查询

db.demo.find({},{favours:{'$slice':1}});     //仅返回数组中的前1项
db.demo.find({},{favours:{'$slice':-1}});    //仅返回数组中的最后一项
db.demo.find({},{favours:{'$slice':[1,2]}}); //跳过前1项,返回接下来的10项
db.demo.find({},{favours:{'$slice':[-1,1]}});//跳过最后一项,返回接下来的1项

然后,我将要更新这条记录——这个博客帖子在这条记录中还包含有嵌入的评论,因此向得到_id的这条记录中的评论集合中添加一个数组,

db = client.test_database

# db = client['test-database']或者这种形式

#test_database数据库的名称
7、游标

MongoDB中的游标已经在各个版本的驱动程序中封装好了,不需要像传统SQL那样使用PL/SQL结构化编程来声明游标,在Shell中,游标的使用方式与Java中的迭代器十分相似。

var cursor = db.demo.find();  //声明游标
while(cursor.hasNext()){      //遍历集合
  var element = cursor.next();
}
$result = $posts->updateOne(
   ["_id" => new MongoDB\BSON\ObjectID($id)],
   ['$push' => [
"comments" => $new_comment_data
      ]
]);

四、获取集合(getting a collection)

8、$WHERE

$where操作符也是用来定位查询的,这个SQL中的where非常类似,前面我们说过,匹配文档的时候可以使用各种查询条件操作符来实现,但是为什么还要有这个操作符呢?因为有些查询是无法通过之前讲过
的那些查询操作符来实现的。值得注意的是,$where操作符的性能低,没有使用也无法使用索引机制。

//传统SQL
select * from demo where a > 1;

//使用查询操作符实现
db.demo.find({a:{"$gt":1}});

//使用$where实现
db.demo.find({"$where":"this.a > 1"});
db.demo.find("this.a > 1");
db.demo.find(function(){
  return this.a > 1;
});

最后希望本文对大家有所帮助。谢谢大家对本站的支持!

集合是一组存储在MongoDB中的文件,并且可以被认为是一个表,作为大致在关系数据库中的等效的。获得在PyMongo收集工作与获取数据库:

9、 排序&分页

MongoDB中提供了相关的方法进行排序和分页,主要有limit(),skip()sort()

//每页10条记录,略过前面10条记录,按a降序排序
db.demo.find().limit(10).skip(10).sort({a:-1});

您可能感兴趣的文章:

  • PHP中MongoDB数据库的连接、添加、修改、查询、删除等操作实例
  • 解析PHP中常见的mongodb查询操作
  • php操作MongoDB基础教程(连接、新增、修改、删除、查询)
  • Thinkphp使用mongodb数据库实现多条件查询方法
  • 在php7中MongoDB实现模糊查询的方法详解
collection = db.test_collection

# collection = db['test-collection']或者这种形式
10、索引

MongoDB的索引机制与传统SQL的索引基本上是一样的。

//创建索引
db.demo.ensureIndex({'a':1});

//创建子文档索引
db.demo.ensureIndex({'a.b':-1});

//创建复合索引
db.demo.ensureIndex({
   "a":1,"b":-1
});

//在MongoDB中,1表示升序,-1表示降序

//重新索引,一般是修改索引后重新索引操作
db.demo.reIndex();

//删除索引
db.demo.dropIndexes();

有关集合(和数据库)在MongoDB中一个重要的注意的是:当第一个文件被插入到他们集合和数据库时集合就被创建。

11、聚合

count()

//select count(a) from demo where a = 1;
db.demo.count({"a":1});

distinct()

db.demo.distinct("zip-code",{a:1});

group(key,cond,reduce,initial)
其中,
key :分组依据
cond: 查询条件
reduce:聚合操作
initial : 指定聚合计数器的初始对象

//sql表示
select a,b,sum(c) from demo where a = 1 group by a,b;

//MongoDB表示
db.demo.gourp({
  "key":{
     "a": true,
     "b": true
  },
  "cond":{
     "a":1
  }
});

五、文件

12、归纳一下MongoDB中的一些Tips

1、MongoDB与传统SQL的显著区别

SQL MongoDB
表(Table) 集合(Collection)
行(row) 文档(document)

2、集合不能以system.开头,
这是因为MongoDB中的系统集合保持的前缀。比如

db.system.update(document);  //错误

3、
ObjectId类型是_id的默认类型,也可以自己指定其数据类型。MongoDB的初衷是设计成一个分布式的数据库,所以不会自动实现_id的自增。插入文档时,如果没有指定_id的值,则系统自动创建一个ObjectId类型的值,一般在客户端的驱动程序中完成。


4、插入文档时,MongoDB会解析BSON数据,BSON数据格式与JSON基本一致,在MongoDB中称为BSON。插入时会检查是否包含_id以及检查文档数据是否超过16MB,其余全部不作检查,从而实现其高效率性。


5、MongoDB在插入数据时,不会执行插入数据的代码,而是将BSON数据直接写入,不作任何的数据验证,所以不存在类似于SQL中的注入风险。


MongoDB中的数据是使用JSON风格的文件代表(和存储)。在PyMongo我们用字典来代表文件。作为一个例子,下面的字典可能被用来代表一个博客帖子,下面以一个例子来说明文件的写入mongodb的过程:

说明

本篇文章也是笔者自己在学习过后总结出来的。也是针对习惯于传统SQL的简友们写的,传统的SQL数据库与NoSQL个人感觉差别还算是挺大的。刚开始接触NoSQL的时候最大的不适应就是在MongoDB中是不需要设计表和表结构的,全是基于JSON的数据操作,所以其逻辑原理都在程序代码中实现,而MongoDB本身只负责分布式的数据存储。


文章更新日志
2016-07-08 文章初稿

文件的内如如下:

import datetime

post = {"author": "Mike",

..."text":"My first blog post!",

..."tags":["mongodb", "python", "pymongo"],

..."date":datetime.datetime.utcnow()}

Note:请注意,文档可以包含原生的Python类型(如datetime.datetime实例),这些类型的值会被自动在原生类型和BSON格式之间转换。

5.1文件的插入

若要将文档转换为集合,可以使用insert_one()函数进行:

>>> posts = db.posts

>>> post_id = posts.insert_one(post).inserted_id

>>> post_id

Out[5]: ObjectId('56556b3c9d00010b2f8909cf')

当一个文件被插入一个特殊的键,“_id”,自动添加如果文档没有包含一个“_id”键。“_id”的值必须在这个集合是唯一的。insert_one()返回InsertOneResult的一个实例。有关“_id”的更多信息,请参见_id的文档。

插入第一个文档后,该帖收集实际上已在服务器上创建。我们可以通过列出所有在我们的数据库中收集的验证这一点:

>>> db.collection_names(include_system_collections=False)

Out[6]: [u'posts']

5.2单个文档的获取find_one()

在MongoDB中,最基本的查询是find_one。这个方法返回一个符合查询的文件,或者在没有匹配的时候返回None。

当只有一个文件符合条件的时候,或者只对第一个符合条件的文件感兴趣的时候,这个方法是很有用的。
我们用find_one()来获取posts collection 里的第一个文件:

>>> posts.find_one()
{'date': datetime.datetime(2016, 9, 27, 3, 56, 26, 78000), 'author': 'Mike', 
'_id': ObjectId('57e9edea77eddf223cde3314'), 'tags': ['mongodb', 'python', 'pymongo'], 
'text': 'My first blog post!'}

返回结果是一个我们之前插入的符合条件的字典类型值。
注意,返回的文件里包含_id这个键值,这是自动添加的。
find_one()还支持对特定元素进行匹配的查询。限制我们文档的作者是”Mike”,可以这么做:

>>> posts.find_one({"author":"Mike"})
{'date': datetime.datetime(2016, 9, 27, 3, 56, 26, 78000), 'author': 'Mike', 
'_id': ObjectId('57e9edea77eddf223cde3314'), 'tags': ['mongodb', 'python', 'pymongo'], 
'text': 'My first blog post!'}

如果我们用不同的作者,比如:”Eliot”,将不会得到结果。

>>> posts.find_one({"author":"Eliot"})
>>>

5.3 通过ObjectId查询

通过_id也可以进行查询,在例子中就是ObjectId:

 >>> post_id
 ObjectId('57eb54a877eddf292cbea0a8')
 >>> posts.find_one({"_id": post_id})
{'date': datetime.datetime(2016, 9, 28, 5, 25, 53, 6000), 'author': 'Mike', 
'_id': ObjectId('57eb54a877eddf292cbea0a8'), 'tags': ['mongodb', 'python'], 
'text': 'My first blog post!'}

注意:ObjectId 并不等同于它的字符串形式。

>>> post_id_as_str = str(post_id)
>>> posts.find_one({"_id": post_id_as_str}) #No result
>>> 

在web应用的一个常见任务就是在request的URL里获取ObjectId,然后找到与之匹配的文件。
在本例中,必须要先从字符串转换为ObjectId,然后传给find_one:

>>> from bson.objectid import ObjectId
#从URL里获取post_id,然后把它作为字符串传入
>>> def get(post_id):
       #将字符串转换为ObjectId
       document = client.db.collection.find_one({"_id": ObjectId(post_id)})

小插曲:
MongoDB以BSON格式保存数据。BSON字符串都是UTF-8编码的,所以pymongo必须确保它保存的字符串值
包含有效地UTF-8数据.常规字符串(<type
‘str’>)都是有效的,可以不改变直接保存。
Unicode字符串(<type ‘unicode’>)就需要先编码成UTF-8格式。

5.4 批量插入(Bulk Inserts)

为了让查询更有趣,我们多插入几个文件。除了单个文件插入,也可以通过给insert_many()方法传入一个列表(list),作为该方法的第一个参数,进行批量插入操作。这将会插入列表(list)中的每个文件(document)到集合中去,而且只向server发送一条命令:

new_posts = [{"author": "Mike","text": "Another post!","tags": ["bulk", "insert"],"date": datetime.datetime(2009, 11, 12, 11, 14)},{"author": "Eliot","title": "MongoDB is fun","text": "and pretty easy too!","date": datetime.datetime(2009, 11, 10, 10, 45)}]
>>> result = posts.insert_many(new_posts)
>>> result.inserted_ids
[ObjectId('57eb700b77eddf292cbea0a9'), ObjectId('57eb700b77eddf292cbea0aa')] 

这个例子里有一些比较有趣的地方:
insert_many()现在返回两个ObjectId实例,每个代表一个插入的文件。
new_posts[1]与其他的posts内容格式不相同,里面没有”tags”。另外我们增加了一个新的“title”域。这就是MongoDB所提到的无schema特点。

5.5 多文档查询

为了得到更多的文件,我们使用find()方法。find()返回一个Cursor实例,可使我们遍历所有匹配的文件。
比如遍历每个posts collection里的文件:

>>> for post in posts.find():
post

与使用find_one()时候相同,可以传入一个文件来限制查询结果。比如查询作者”Mike”
文件:

>>> for post in posts.find({"author":"Mike"}):
post

5.6 范围查询

MongoDB的支持许多不同类型的高级查询。作为一个例子,执行我们结果限制的位置早于某个日期,也由作者对结果进行排序的查询:

d = datetime.datetime(2009, 11, 12, 12)

for post in posts.find({"date": {"$lt":d}}).sort("author"):
    print post

结果:

{u'date': datetime.datetime(2009, 11, 10, 10, 45), u'text': u'andpretty easy too!', u'_id': ObjectId('565589029d00010c0ad19cd8'), u'author':u'Eliot', u'title': u'MongoDB is fun'}

{u'date': datetime.datetime(2009, 11, 12, 11, 14), u'text': u'Anotherpost', u'_id': ObjectId('565589029d00010c0ad19cd7'), u'author': u'Mike',u'tags': [u'bulk', u'insert']}

六、简单的统计

如果我们只是想知道有多少文件匹配,我们可以执行查询函数count()。得到所有集合中的文件的计数:

posts.count()

结果:

3

或者那些符合特定格式的文件:

posts.find({"author": "Mike"}).count()

结果:

2

七、删除文档

可以使用集合的remove()方法从集合中删除一个文档。remove方法和find、find_one一样,也可以使用一个字典参数来指定哪个文档需要被删除。比如,要删除所有”author”键的值为”Mike”的文档,输入:

>>>posts.remove({"author": "Mike"})
{u'n': 4, u'ok': 1}

>>>for nl in posts.find():
    print nl

{u'date': datetime.datetime(2009, 11, 10, 10, 45), u'text': u'andpretty easy too!', u'_id': ObjectId('565589029d00010c0ad19cd8'), u'author':u'Eliot', u'title': u'MongoDB is fun'}

{u'date': datetime.datetime(2009, 11, 10, 10, 45), u'text': u'andpretty easy too!', u'_id': ObjectId('56559fb89d00010d55ba1d12'), u'author':u'Eliot', u'title': u'MongoDB is fun'}

八、案例解析

8.1 常见mongoDB函数的创建集成案例

数据库 2

数据库 3

数据库 4

数据库 5

数据库 6

8.2 将10条信息写入mongdb数据库的案例

数据库 7

数据库 8

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图