Excel使用ADO调用SQL Server存储过程

测试环境:SQL Server 2014Excel 2013

Excel示例文件下载:http://download.csdn.net/detail/zhanghongju/8533555

前期在做一个现场投票的小软件,准备使用Excel实现,工作簿中包含有两个工作表,各包含一个命令按钮,分别用于提交和查看投票结果。

问题出在查看投票按钮上,起初只是使用SELECT语句查询,一切正常;后期对于投票结果需要进行一些权重的计算,过程比较复杂,便想到把计算过程放在SQL Server端,使用存储过程实现。但是,在调用存储过程的过程中却遇到了问题,一直无法返回记录集。最后发现问题出现在记录集的CursorLocation属性设置上,下面便把此问题的过程和解决方案提供如下,以免发生类似问题。

一、使用CursorLocation属性设置游标的区别

CursorLocation属性包含两个属性值:adUseClient(常量值为3)和adUseServer(常量值为2)。adUseClient表示使用由本地游标库提供的客户端游标,adUseServer是默认值,表示使用数据提供者的或驱动程序提供的游标。

adUseClient游标服务的功能通常更加强大一些,许多功能可能是驱动程序提供的游标无法提供的。但是,adUseServer游标具有高度的敏感性,可以看到其他用户对数据源所作的更改,对于一些多用户操作,该特性是具有优势的。也正是因为此特性,会导致记录集行数的具有不确定性,因此某些情况下是无法使用RecordCount属性获取记录集的行数,也无法使用EOFBOF判断是否到达记录集的尾部和头部。

二、在SQL Server中创建示例数据

下面的语句用于在SQL Server中创建两个表。dbo.MyScore用于存储评选人员ID、项目ID和评选分数,dbo.MyPerson用于存储评选人员ID和评选人员是否为专家的标志,标志为1,表示此评选人员为专家。

IF OBJECT_ID(‘dbo.MyScore‘) IS NOT NULL

   DROP TABLE dbo.MyScore;

IF OBJECT_ID(‘dbo.MyPerson‘) IS NOT NULL

   DROP TABLE dbo.MyPerson;

GO

 

CREATE TABLE dbo.MyScore(PersonID int, ProjectID int, ProjectScore int);

CREATE TABLE dbo.MyPerson(PersonID int, IsExpert int);

INSERT INTO dbo.MyScore VALUES

   (1, 1001, 90),

   (1, 1002, 80),

   (1, 1003, 95),

   (2, 1001, 85),

   (2, 1002, 85),

   (2, 1003, 90),

   (3, 1001, 100),

   (3, 1002, 90),

   (3, 1003, 95);

INSERT INTO dbo.MyPerson VALUES

   (1, 0),

   (2, 0),

   (3, 1);  --3是专家评测人员

 

三、Excel中的查询方法

1.执行SELECT查询

打开Excel,在Sheet1中插入一个命令按钮,并为其指定Click方法,代码如下:

Sub 按钮1_Click()

    Dim cn As New ADODB.Connection, strCn AsString, strSQL As String, rs As New ADODB.Recordset

   

    strCn ="Provider=sqloledb;Server=192.0.168.1;Database=MyTMP;Uid=sa;Pwd=111111"

    cn.Open strCn

    If cn.State <> adStateOpen Then

        On Error Resume Next

        cn.Close

        Err.Clear

        o = MsgBox("数据连接失败", vbOKOnly,"提示")

        Exit Sub

    End If

  

   

    ‘清除Excel工作表中的数据并设置列名称

   ActiveWorkbook.Worksheets("Sheet1").Cells.Select

    Selection.ClearContents

    Range("A1").Value = "参评项目ID"

    Range("B1").Value = "平均分值"

   

    ‘直接查询所有评选人员的平均分值

    strSQL = "SELECTProjectID,AVG(ProjectScore) AS ProjectScore FROM dbo.MyScore GROUP BYProjectID"

    rs.Open strSQL, cn, adOpenKeyset,adLockReadOnly

   

    o = MsgBox("记录数:" &rs.RecordCount & ",游标位置:" &rs.CursorLocation, vbOKOnly) ‘提示获取到的行数和游标位置

   

     

    With rs

        If Not (.BOF And .EOF) Then ‘有数据记录,则遍历记录集显示在Excel工作表中

            For i = 2 To .RecordCount + 1

                Cells(i, 1) = .Fields(0)

                Cells(i, 2) = .Fields(1)

                .MoveNext

            Next

        End If

    End With

End Sub

上面的代码中使用MsgBox函数来提示获取到的记录集行数,有数据记录的话,则遍历记录集,将数据写入到Excel工作表中。上面的代码没有为记录集指定CursorLocation属性,则使用默认值adUseServer,执行时可看到弹出对话框中显示的记录数和游标位置,如下图所示。

技术分享

当然,为了确保ADO对象的正常引用,应当在项目中添加对Microsoft ActiveXData Objects支持库的引用,如下图所示。

技术分享

2.执行存储过程

首先在SQL Server中创建一个名为dbo.usp_GetScore的存储过程,该过程用于判断评选时是否有专家参与,如果有专家参与,则按照普通人员70%、专家人员30%的权重计算项目的分值;如果没有,则直接计算项目的平均分值。

CREATE PROCEDURE  dbo.usp_GetScore

AS

BEGIN

   DECLARE@IsExpert AS int;

 

   SELECT TOP 1 @IsExpert = B.IsExpert

   FROM dbo.MyScore A

      INNER JOIN dbo.MyPerson B

      ON A.PersonID = B.PersonID

   WHERE B.IsExpert = 1;

 

   /*有专家投票的情况下,专家比重为30%,其他人员为70%;

   否则,则直接取所有人的平均值*/  

   IF @IsExpertIS NOT NULL 

      WITH CTE AS

         (SELECT A.ProjectID,A.ProjectScore,B.IsExpert

          FROMdbo.MyScore A

             INNERJOIN dbo.MyPersonB

                ONA.PersonID = B.PersonID

          ),

         CTE1 AS

          (SELECT ProjectID, AVG(ProjectScore) * 0.7 AS ProjectScore

          FROM CTE

          WHERE IsExpert <>1

          GROUP BYProjectID

          UNION ALL

          SELECT ProjectID,AVG(ProjectScore) * 0.3 AS ProjectScore

          FROM CTE

          WHERE IsExpert =1

          GROUP BYProjectID

          ) SELECTProjectID, SUM(ProjectScore) AS ProjectScore

            FROM CTE1

             GROUP BY ProjectID;

    ELSE

       SELECT ProjectID, AVG(ProjectScore) AS ProjectScore

       FROM dbo.MyScore

       GROUP BY ProjectID;

END;

将前面介绍的Excel中按钮strSQL的代码替换为存储过程,如下所示:

Sub 按钮1_Click()

    Dim cn As New ADODB.Connection, strCn AsString, strSQL As String, rs As New ADODB.Recordset

   

    strCn ="Provider=sqloledb;Server=46.0.187.151;Database=MyTMP;Uid=sa;Pwd=zhj11111"

    cn.Open strCn

    If cn.State <> adStateOpen Then

        On Error Resume Next

        cn.Close

        Err.Clear

        o = MsgBox("数据连接失败", vbOKOnly,"提示")

        Exit Sub

    End If

  

   

    ‘清除Excel工作表中的数据并设置列名称

   ActiveWorkbook.Worksheets("Sheet1").Cells.Select

    Selection.ClearContents

    Range("A1").Value = "参评项目ID"

    Range("B1").Value = "平均分值"

   

    ‘直接查询所有评选人员的平均分值

    strSQL ="EXECUTE dbo.usp_GetScore"

    rs.Open strSQL, cn, adOpenKeyset,adLockReadOnly

   

    o = MsgBox("记录数:" &rs.RecordCount & ",游标位置:" &rs.CursorLocation, vbOKOnly) ‘提示获取到的行数

   

    With rs

        If Not (.BOF And .EOF) Then ‘有数据记录,则遍历记录集显示在Excel工作表中

            For i = 2 To .RecordCount + 1

                Cells(i, 1) = .Fields(0)

                Cells(i, 2) = .Fields(1)

                .MoveNext

            Next

        End If

    End With

End Sub

执行代码,这时会发现对话框返回记录集的行数为-1,如下图所示。由于无法确定记录行数,所以也就无法写入到Excel中。

技术分享

解决的方法是把记录集的CursorLocation属性指定为adUseClient,这样就可以正常运行了。参考下面的代码:

strSQL ="EXECUTE dbo.usp_GetScore"

rs.CursorLocation = adUseClient

rs.Open strSQL,cn, adOpenKeyset, adLockReadOnly

即使未指定adUseClient,虽然记录集的RecordCount返回-1,但是,记录集实际上是有数据的,我们可以不使用遍历记录集的方法,而是使用ExcelCopyFromRecordset方法把记录集直接粘贴到工作表中,参考下面的代码:

Sub 按钮1_Click()

    Dim cn As New ADODB.Connection, strCn AsString, strSQL As String, rs As New ADODB.Recordset

   

    strCn ="Provider=sqloledb;Server=46.0.187.151;Database=MyTMP;Uid=sa;Pwd=zhj11111"

    cn.Open strCn

    If cn.State <> adStateOpen Then

        On Error Resume Next

        cn.Close

        Err.Clear

        o = MsgBox("数据连接失败", vbOKOnly,"提示")

        Exit Sub

    End If

  

   

    ‘清除Excel工作表中的数据并设置列名称

   ActiveWorkbook.Worksheets("Sheet1").Cells.Select

    Selection.ClearContents

    Range("A1").Value = "参评项目ID"

    Range("B1").Value = "平均分值"

   

    ‘直接查询所有评选人员的平均分值

    strSQL = "EXECUTEdbo.usp_GetScore"

    rs.CursorLocation = adUseClient

    rs.Open strSQL, cn, adOpenKeyset,adLockReadOnly

   

   Range("A2").CopyFromRecordset rs  ‘直接粘贴到Excel

End Sub

 

 

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。