ADO 与 临时表

    在这个论坛里第一次接触到ADO,对通过用ADO来获取数据库的数据,对我日常的工作来说真是太有用了,因此慢慢地开始编写一些ADO读取数据库的代码,用得越多,疑问也越多。ADO太灵活了,如对于从数据库获取数据,可以有很多的方法,即可以使用Recrodset对象来完成,也可以直接通过一个connection对象来完成;连接数据库也有多种方法,各种连接字符串又是什么涵义等,这些问题一直困惑着我,对于初学者来说ADO的灵活并不是件好事,会让我们迷惑,至少我当时学着用ADO时,有太多太多的问题,但这些问题又不好找答案。通过慢慢摸索,特别是当我从淘宝淘了一本二手的《ADO编程指南》(该书已经不再发行了)并仔细阅读了几遍后才对ADO有了越来越清晰的认识,一些以前的疑问也慢慢地解开了。现在空的时候还经常翻开读一段,随着对ADO理解地加深,一些以前不被注意到的细节也慢慢地有所理解,真的是温故而知新。
    最近在逛论坛时又看到一个关于“为什么RecordCount属性返回-1”的问题,这个问题当时也困惑着我,从帮助文件中了解到返回-1与游标类型有关,有的游标会返回-1。当时看了帮助后不但问题没解决,又出现了一些新的问题,什么是游标,什么是键集游标,什么是客户机端临时表等等。有人说最好的电脑书就是帮助,我觉得没错,但前提是你有一定的基础,因为帮助会用很简单的语言把一个问题描述清楚,如果你有太多的基础不清楚,那么帮助中的术语会让你看不下去。当时看ADO的帮助就是这种情况,现在对ADO有了一定的理解后回过头再去看ADO的帮助时,就发现帮助里的话可以说是字字精辟,有一些关键点就是在几个字中描述得清清楚楚。呵呵跑得有点远了。接下来进入正题吧。
其实有很多的问题如上面返回-1的问题,为什么有的游标对于别的用户对数据的修改可见,有的游标对于其他用户插入的数据可见等等问题都和游标类型有关。游标是数据库中一个十分重要的概念,是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果,它是一种用来存储查询结果的数据结构,游标应该和临时表是同一样东西(个人认为,因没有系统学过数据库,此处有不正请指正,接下来我就称游标为临时表了,这个称呼似乎更容易理解),当我们对数据库发出一条查询请求后,数据库返回的满足查询条件的记录会存放在临时存放在这种数据结构中。临时表有很多种,每种都有各自的特点,接下来我会分别介绍几中常用的临时表。



    在介绍临时表前先介绍两个ADO中属性。
    第一个属性是CursorLocation,该属性用于设定临时表的位置,我们经常使用的是以下两个值。AdUseServer为默认值,表示服务器端的临时表,使用OLE DB提供程序或数据库来管理查询结果;AdUseClient,客户机端临时表,使用ADO临时表引擎来管理查询结果。
    第二个属性是CursorType,该属性用来设定临时表的类型,有4个值。
        adOpenForwardOnly,正向临时表,服务器端Recordset的默认值,用于打开仅支持向前滚动的Recordset;
        adOpenStatic,静态临时表,客户机端默认的并且唯一可能的值,支持前向和后向滚动,其他用户所作的改动不可见;
        adOpenKeyset,键集临时表,支持前向和后向的滚动,其他用户的修改和删除可见;
        adOpenDynamic,动态临时表,支持前向和后向的滚动,其他用户的修改、删除和插入可见。
    再简单说一下什么是OLE DB提供程序,OLE DB提供程序是一种由C++开发的访问数据库的程序,它可以被C++直接调用,但不能被其他的语言所调用,因此在OLE DB的基础上又开发出了ActiveX Data Object(ADO),它基于ActiveX技术,与语言无关,可以被多种语言所使用,因此我们可以在VBA中使用它,也可以在C++、C#、Delphi等程序设计语言中使用。
    根据临时表所在的位置(CursorLocation属性)不同可以分为服务器端临时表和客户机端临时表。我们如何来区分服务器端和客户机端呢?一般来说客户机直接面向的是用户,服务器是为计算机或其他程序提供支持的程序,因此并不是说服务器一定是指其他的电脑,很多情况下在同一台电脑即是服务器又是客户机。对于这们这里的临时表来说,我个人理解当临时表位于数据库与OLE DB提供程序之间时称为服务器端临时表,位置ADO与OLE DB提供程序之间的称为客户机端临时表。


    首先来介绍服务器端临时表吧。前面已经提到了当CursorLocation=AdUseServer时使用OLE DB提供程序或数据库来管理查询结果,先不讲这是什么意思,这里只要知道一点,那就是ADO默认使用服务器端的临时表。常用的服务器端临时表有正向临时表、静态临时表、键集临时表、动态临时表。
    1、正向临时表:也叫仅向前临时表CursorType=adOpenForwardOnly。正向临时表是最简单的一种临时表,就象它的名字一样,临时表中的记录只能向前移动,一旦移过某个记录,则该记录在临时表中就无效了。这种临时表功能较少,看上去作用有限,但速度极快。我们来看下面的示意图。



    图中临时表位于OLE DB提供程序与数据库之间,临时表可能是由OLE DB程序负责管理,也有可能是由数据库进行管理,这得看数据库和OLE DB提供程序来确定,如SQL Server就是由数据库来管理临时表的,而ACCESS则是由OLE DB提供程序来管理临时表。临时表中虚线部分表示已经读取过的记录,黄色部分表示当前读取到ADO缓存的记录。在Recrodset中有一个属性CacheSize,表示从服务器端临时表中读取记录缓存的大小,现假定CacheSize=5,那么ADO会一次从临时表中读取5条记录到ADO的缓存当中。只有当读取完缓存中第5条记录后再要读下一条记录时,ADO才去从临时表获取下5条记录。因此在这里有一点大家需要注意,正向临时表只能前向滚动,可使用Recordset的MoveNext在临时表中前向滚动,但使用MovePrevious、MoveFirst、MoveLast方法会出错,不过我们可以在Move方法中使用负数对缓存中的数据实现后向滚动,如Move -1表示向前滚动一条缓存中的记录,当然前提条件是滚动不超过缓存区域边界。
    正向临时表有一种特殊情况,也就是当CacheSize=1,这种临时表被称为“消防水带”临时表,数据出来速度极快,一些数据库只支持这类临时表。非常适合我们从数据库读出数据到工作表中。
    从正向临时表的原理来看,在读取数据过程中我们根本无法确定数据有多少,数据从头到尾一批批(条条)地读到缓存中,因此我们要获取记录数只能采取计数的方法或使用SQL的count函数来实现,因此如果去读取该类临时表的RecordCount属性的话会返回-1。
下面给出一个正向临时表的示例。
复制内容到剪贴板
代码:

Sub ForwardOnlyCursor()
    Dim cn As ADODB.Connection
    Dim rs As ADODB.Recordset
    Dim sSqlCommand As String
    Set cn = New ADODB.Connection   '实例化Connection对象
    cn.CursorLocation = adUseServer '设置connection对象的临时表位置
    cn.ConnectionString = "Provider=SQLOLEDB;User ID=sa;Password=123;Initial Catalog=testDB;Data Source = localhost"    '连接字符串,本处为连接SQL Server的连接字符串
    cn.Open '建立连接
   
    Set rs = New ADODB.Recordset    '实例化Recordset对象
    rs.CursorLocation = adUseServer '前面已经设置了connection对象的临时表位置,如此处省略,则rs会继承cn的这个值,因此本句可以省略,这里只是为了说明而加上了这一句。
    rs.CursorType = adOpenForwardOnly   '设置rs为正向临时表
    rs.CacheSize = 5    '设置rs缓存大小为5
    sSqlCommand = "SELECT * FROM test"
    rs.Open sSqlCommand, cn  '打开rs
   
    '........
    '.代码.
    '........
   
    rs.Close
    Set rs = Nothing
    cn.Close
    Set cn = Nothing
End Sub



    2、静态临时表,CursorType=adOpenStatic,静态临时表与正向临时表类似,不同的地方在于表态临时表支持前滚和后滚,当查询结束后,查询结果会全部保存在临时表中,这时其他用户对数据库中数据的插入、修改、删除都不会影响到临时表中的数据,临时表中的信息是静态的,因此可以通过RecordCount属性获取到记录数,该记录数只表示查询时数据库中满足记录的个数。静态临时表的示意图如下。


    我们可以看到整个结构和正向临时表基本一致,唯一的区别在于记录只可以前后滚动。黄色区域表示ADO缓存中的数据。当ADO Recrodset中使用各种MOVE方法对记录进行浏览时,一旦超过了当前缓冲区中的数据,则ADO将从临时表中获取下一批的数据。静态临时表一般来说都是只读的,所有客户机端的临时表都是静态临时表,但客户机端的临时表与服务器端的临时表还是有一定的区别的,我们在后面再讲座这个问题。


    3、键集临时表,CursorType=adOpenKeyset。
    我们先来讲讲什么是键,键是数据库中一个非常重要的概念,一般来说在一张数据库的表中会有一个字段,这个字段内的值是不可以重复的,我们可以称这个字段为键,通过键我们可以唯一地确定一条记录,表中可以存在多个键,但只能确定一个键为主键(Primary Key),当一张表中的主键存在于另一张表中,则该主键是另一张表的外键,通过主键和外键,我们可以把多张表很方便的连接起来。举个例子来说,假设一个学校建立一个学生信息库,那么会有一张表中记录有学生的姓名、性别、出生年月等等信息,当然最重要的是在这张表中应该还有一个学号,我们可以把学号看成是这张表的主键,因为一般来说学号是不重复的,我们就称这张表为基本情况表吧。第一学期结束了,学校把每个学生的考试成绩录入数据库,建立一张表,我们称为成绩表,这张表中当然还会有一个主键,这个主键可能是由计算机产生的一个序列数,反正是不能重复的。表中还会有如学号、科目、成绩等字段,因为一个学生会有几个考试成绩,因此在这张表中同一学号会多次出现,学号在成绩表中被称为外键,通过学号,我们可以很方便的把基本情况表和成绩表连接起来,这就是主键和外键的主要功能。
接下来我们就可以讲讲键集临时表了。我先把示意思贴上。



    当ADO提出查询后,临时表中只保存满足条件的记录的键值,当要填充Recrodset缓冲区时,才根据键值,从数据库中把数据读取出来填充到缓冲区。当浏览超过缓冲区边界后,又会要根据键值去读取数据库中的信息,因此键集临时表可以看到其他用户对数据库中数据的更改。那么当其他用户在数据库中插入了新的满足条件的记录呢?由于键集在查询结束后已经确定,之后不会重新将满足记录的键值读取到临时表中,因此其他用户的插入操作对于键集临时表来说是不可见的。正因为键集在查询结束后基本保持静态,因此可以通过RecrodCount属性获取记录数。另一个问题,如果在临时表存在的过程中,其他用户将键集中的一条记录删除了会怎样?这时当键集临时表填充缓冲区时发现一条记录找不到了,那么这个键值会被临时表从键集中删除。因为这个删除的过程是临时表操作的,因此还是可以通过RecrodCount属性获取记录数。
键集临时表对键集中的键值只减不增,因此该临时表对其他用户的插入不可见,修改和删除可见。键集临时表支持前向和后向的滚动。


    4、动态临时表,CursorType= adOpenDynamic。
    动态临时表与键集临时表非常相似,最大的区别在于每当填充ADO Recordset缓冲区都将重新查询生成键集,因此动态临时表对于其他用户的插入也可见。




    动态临时表对键集部的键值可增可减,因此动态临时表对其他用户的插入、修改、删除都可见,可以是说这么多临时中功能最强大的了,但缺点也很明显,因为每当填充缓冲区都将触发查询,因此对于数据库的压力会较大,速度也最慢。


    接下来我们来讲讲客户机端的临时表吧CursorLocation=AdUseClient。客户机端临时表都是静态临时表,与服务器端静态临时表不同之处在于,服务器端静态临时表是由OLE DB提供程序或数据库来管理的,客户机端临时表是由ADO临时表引擎来管理的。





    前面我们在服务器端静态临时表中提到,静态临时表一般都是只读的,客户机端临时表也是静态临时表,也是只读的,但我们却可以通过ADO对客户机端临时表的数据实现读与写。大家看到这里肯定觉得我逻辑混乱吧。没错,刚开始我也这么觉得。其实客户机端的临时表的确是只读的,那为什么又支持读写呢,那是因为ADO临时表引擎对读写提供了支持,当对临时表数据进行写操作时,其实并没有真正去写临时表,而是由ADO临时表引擎把相关的操作记录下来,并存入缓存中,当调用recordset的Update或UpdateBatch操作时,ADO临时表引擎把这些改动转换为操作查询来更新数据库,这就是客户机端静态临时表可更新的原因。
    客户机端临时表由于使用了ADO临时表引擎,因此有非常丰富的功能,如搜索、排序、过滤、批更新、暂存、自动建立索引等。而且客户机端临时表可以不需要数据库连接,当查询数据以消防水带的形式填充到静态临时表中后,我们可以断开Recordset的活动连接,继续对临时表中的数据进行其他的操作,当需要更新数据库时重新找开连接,完成更新。ADO临时表引擎提供的暂存功能也很有意思,可以先把活动连接断开,然后把临时表的信息保存成一个文件存放在硬盘中,当需要时重新打开这个文件,恢复连接,更新数据库。这是一个非常酷的功能。
当然客户机端临时表也有它不足的地方,因为可以脱机操作,这就导致在数据库会频繁更改的情况下不能及时更新数据。
总的来说客户机端临时表功能丰富,对数据库压力小,效率高(可参见我以前的一个贴子),因此除非一定要用服务器端临时表,强烈建议大家尽可能地使用这种临时表。ADO默认使用的是服务器端临时表,这是因为客户机端临时表出现得比服务器端临时表晚,为了向后兼容,所以把服务器端临时表作为默认值。
    每种临时表都有各自的特点,大家在使用过程中要保持够用原则,毕竟功能的增加会带来很多负面的影响,希望我对临时表的讲解对大家有所帮助。文中有的地方是我对自己的理解,可能会有错误,望大家发现错误能指正。谢谢

                                    2010-1-24
                                      lbpp








ado.net执行存储过程提示找不到该临时,但是在sql中直接执行就没问题,求教各位大神。 sql所在环境windows azure ``` if (exists (select * from sys.objects where name = 'proc_Activity_StudioList')) drop proc proc_Activity_StudioList go create proc proc_Activity_StudioList @province int, @type int, @collegeId int, @index int, @size int as IF EXISTS (SELECT * FROM dbo.SysObjects WHERE ID = object_id(N'#StudioList') AND OBJECTPROPERTY(ID, 'IsTable') = 1) drop table #StudioList declare @page int,@pageend int, @sql nvarchar(4000) set @page =(@index - 1) * @size + 1 set @pageend = @index*@size set @sql = 'create table #StudioList(num float,PId int,UserNickName nvarchar(500),UserAvatar nvarchar(500),UpCount int,CommentCount int,RegisterCount int, Userid bigint,Region nvarchar(100),Province nvarchar(100)) INSERT INTO #StudioList select distinct num=( a.[RegisterCount]*0.5 + a.[UpCount]*0.25 + a.[CommentCount]*0.25 ), a.[PId],temp.[UserNickName],temp.[UserAvatar],a.UpCount,a.CommentCount,a.RegisterCount,temp.[Userid],r.[CityName] as Region,r1.[CityName] as Province from [UserInfo] as temp inner join [dbo].ActivityStudioSet as a on a.userid = temp.[UserId] left join [dbo].[Region] as r on temp.[Region] = r.[Codeid] left join [dbo].[Region] as r1 on temp.[Province] = r1.[Codeid] where temp.[UserRole]=3 ' if(@province > 0) set @sql = @sql +' and temp.[Province] = '+cast(@province as varchar(10)) if(@collegeId > 0) set @sql = @sql +' and temp.CollegeId = '+cast(@collegeId as varchar(10)) if(@type=1) set @sql = @sql +' and a.[IsRecommend] =1 ' --print @sql exec(@sql) set @sql ='select * from ( select ROW_NUMBER() over(order by [Userid] ) as rownumber, * from #StudioList ) as a' if(@type = 1) set @sql = @sql + ' where a.rowNumber between '+cast(@page as varchar(10))+' and '+cast(@pageend as varchar(10)) else if(@type = 2) set @sql = @sql + ' where a.rowNumber between '+cast(@page as varchar(10))+' and '+cast(@pageend as varchar(10))+' order by a.[RegisterCount] desc ' else set @sql = @sql + ' where a.rowNumber between '+cast(@page as varchar(10))+' and '+cast(@pageend as varchar(10))+' order by num desc ' --print @sql exec( @sql) IF EXISTS (SELECT * FROM dbo.SysObjects WHERE ID = object_id(N'#StudioList') AND OBJECTPROPERTY(ID, 'IsTable') = 1) drop table #StudioList go exec proc_Activity_StudioList @province=0,@type=1,@collegeId=0,@index=1,@size=30 ```
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页