3.4 游标和存储过程
游标是系统为用户开设的一个数据缓冲区,用来存放SQL语句的执行结果。在数据库中,游标是一个十分重要的概念,游标提供了一种对从表中检索出的数据进行操作的灵活手段。
存储过程是一组为了完成特定功能的操作语句集,它可以经编译后存储在数据库中。存储过程是数据库中的一个重要对象,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。
3.4.1 游标
每个游标区都有一个名字,用户可以用SQL语句逐一从游标中获取记录,并赋给主变量,交由主语言进一步处理。游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。
跟大多数数据库产品一样,MongoDB也是用游标来循环处理每一条结果数据,具体语法如下面的代码所示:
> for( var c = db.t3.find(); c.hasNext(); ) {
... printjson( c.next());
... }
{ "_id" : ObjectId("4fb8e4838b2cb86417c9423a"), "age" : 1 }
{ "_id" : ObjectId("4fb8e4878b2cb86417c9423b"), "age" : 2 }
{ "_id" : ObjectId("4fb8e4898b2cb86417c9423c"), "age" : 3 }
{ "_id" : ObjectId("4fb8e48c8b2cb86417c9423d"), "age" : 4 }
{ "_id" : ObjectId("4fb8e48e8b2cb86417c9423e"), "age" : 5 }
在本例中,首先声明了一个游标c,代表t3表的所有记录,通过c.hasNext方法判断是否还有数据,然后将找到的数据通过c.next方法取出来,最后通过printjson方法将结果输出。
MongoDB还有另一种forEach方式来处理游标,如下面的代码所示:
> db.t3.find().forEach( function(u) { printjson(u); } );
{ "_id" : ObjectId("4fb8e4838b2cb86417c9423a"), "age" : 1 }
{ "_id" : ObjectId("4fb8e4878b2cb86417c9423b"), "age" : 2 }
{ "_id" : ObjectId("4fb8e4898b2cb86417c9423c"), "age" : 3 }
{ "_id" : ObjectId("4fb8e48c8b2cb86417c9423d"), "age" : 4 }
{ "_id" : ObjectId("4fb8e48e8b2cb86417c9423e"), "age" : 5 }
>
这两种处理方式实现的结果是一样的,都是要返回t3表的所有数据,只是语法上略有不同。
3.4.2 存储过程
MongoDB为很多问题提供了一系列的解决方案,面对其他数据库的特性,它仍然毫不示弱,表现得非比寻常。MongoDB同样支持存储过程。
存储过程可由应用程序通过一个调用来执行,而且允许用户声明变量 。同时,存储过程可以接收和输出参数、返回执行存储过程的状态值,也可以嵌套调用。关于存储过程你需要知道的第一件事就是它是用JavaScript来写的。也许这会让你很奇怪,为什么用JavaScript来写,但实际上它会让你非常满意。
MongoDB存储过程存储在db.system.js表中,想象一个简单的SQL自定义函数如下面的代码所示:
function addNumbers( x , y ) {
return x + y;
}
本例中声明了一个存储过程addNumbers,实现的功能是加法计算,它接收2个参数,都代表被加数,最后返回一个相加后的结果。
将这个SQL自定义函数转换为MongoDB的存储过程,只需要调用db.system.js.save命令即可,它接收2个参数,参数_id代表存储过程的名字,参数value代表存储过程的定义,如下面的代码所示:
> db.system.js.save({_id:"addNumbers", value:function(x, y){ return x + y; }});
存储过程可以被查看、修改和删除,用find来查看是否这个存储过程已经被创建,如下面的代码所示:
> db.system.js.find()
{ "_id" : "addNumbers", "value" : function cf__1__f_(x, y) {
return x + y;
} }
>
在本例中通过执行“db.system.js.find”命令获得了系统中存储的存储过程列表,下面来实际调用这个存储过程,如下面的代码所示:
> db.eval('addNumbers(3, 4.2)');
7.2
>
通过执行“db.eval”命令调用加法计算的接口,这样的操作方法太简单了,也许这就是MongoDB的魅力所在。
db.eval()是一个比较奇怪的东西,可以将存储过程的逻辑直接放在db.eval()的参数里直接调用,而无需事先声明存储过程的逻辑,如下面的代码所示:
> db.eval( function() { return 3+3; } );
6
>
从上面代码可以看出,MongoDB的存储过程可以方便地完成算术运算,但其他数据库产品在存储过程中可以处理数据库内部的一些事情。例如,取出某张表的数据量等操作,这些MongoDB能做到吗?答案是肯定的,MongoDB可以通过在存储过程的定义里写上表级别来实现,如下面的代码所示:
> db.system.js.save({_id:"get_count", value:function(){
return db.c1.count(); }});
> db.eval('get_count()')
2
本例中通过定义一个存储过程“get_count”来统计c1表的记录条数。可以看到,存储过程可以很轻松地操作表。
综上所述,存储过程有如下优点:
- 存储过程用流控制语句编写,有很强的灵活性,可以完成复杂的判断和运算。
- 通过存储过程可以使相关的动作同时发生,从而维护数据库的完整性。
- 降低网络的通信量。
当企业规则发生变化时,只在服务器中改变存储过程即可,无需修改任何应用程序。企业规则的特点是要经常变化,如果把体现企业规则的运算程序放入应用程序中,当企业规则发生变化时,就需要修改应用程序,工作量非常大(修改、发行和安装应用程序)。如果把体现企业规则的运算放入存储过程中,当企业规则发生变化时,只要修改存储过程就可以了,应用程序无需任何变化。