C# 通过SqlServer提供openrowset实现最快速度导出数据到excel

/****** Object:  StoredProcedure [dbo].[proc_ExportDataToExcel]    Script Date: 09/25/2019 21:29:35 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

/*
 将查询结果集直接导出到excel文件中(支持多工作表,65500条为一个工作表)
 通过SQLServer数据库内置对象提高数据导出速率
 exec proc_ExportDataToExcel 'select top 1000 * from Users',0,'UserId','D:/1.xls','用户数据'
 注意:使用此存储过程,数据库登录用户要有sysadmin权限,导出到sql语句中必须提供主键
 (事理 2011.5) 部分代码参考“邹建”写的导出代码
*/
Create PROC [dbo].[proc_ExportDataToExcel]
(
    @SelectSQL nvarchar(4000),--查询语句
    @HasOrderBy bit =0, --sql语句最后是否有Order by
    @PrimaryKey varchar(30),--主键id名称
    @FileSavePath nvarchar(500),--excel文件存放目录如,D:/1.xls
    @SheetName nvarchar(250)=N''--要创建的工作表名,默认为Sheet加索引
)
AS 
BEGIN     
    declare @tableName varchar(55)--随机临时表名称
    declare @pageSize int --excel一个表显示多少条数据
    declare @sql varchar(max),@tempSQL varchar(max)
    declare @errorMsg bit
    set @pageSize=65500

    --生成随机名称,防止多个人同时导出数据问题               
    select @tableName = replace('##ExportDataToExcel'+Convert(varchar(36),newid()),'-','')
    
    --拼接复制表结构的sql语句
    --判断第一个select后面是否有top
    declare @hasTop varchar(10)
    declare @index int
    set @index=charindex(' ',@SelectSQL)
    set @hasTop=lower(ltrim(substring(@SelectSQL,@index+1,10)))
    set @hasTop=ltrim(substring(@hasTop,0,4))
    if(@hastop='top')
        begin --将其它top换成top 0
            set @tempSQL=substring(@SelectSQL,12,len(@SelectSQL)-11)--截取"select top "之后字符串
            set @index=patindex('%[0-9][^0-9]%', @tempSQL)--查询top后最后一个数字位置
            set @pageSize=cast(substring(@tempSQL,0,@index+1) as int)    
            if @pageSize > 65500 set @pageSize=65500        
            set @tempSQL='select top 0 '+substring(@tempSQL,@index+1,len(@tempSQL)-@index)
        end
    else
        begin--在第一个select后面加上top 0            
            set @tempSQL='select top 0 '+substring(@SelectSQL,8,len(@SelectSQL)-7)    
        end    

    --通过查询语句创建用于复制表结构的空临时表
    begin try
        set @sql='select * into '+@tableName+' from ('+@tempSQL+') as temp where 1=0'
        exec (@sql)    
    end try
    begin catch
        raiserror('创建复制表结构的空临时表失败!',16,1)
        return
    end catch;

    --查询总记录数
    declare @recordCount int,@sheetCount int--记录总数和excel中表数
    begin try
        declare @recordCountSQL nvarchar(max)
        declare @position int
        set @position = charindex(reverse('order by'),reverse(@SelectSQL))    
        if @HasOrderBy=1
            begin--去除order by        
                if @position>0
                    begin
                        set @position=len(@SelectSQL)-@position-7
                        set @recordCountSQL='select @i=count(*) from ('+substring(@SelectSQL, 0,@position)+') as temp'
                    end
                else
                    begin
                        raiserror('查询语句最后不包含order by,和参数指定不符合',16,1)
                        return
                    end
            end    
        else
            set @recordCountSQL='select @i=count(*) from ('+@SelectSQL+') as temp'
        execute sp_executesql @recordCountSQL,N'@i int out',@recordCount out
    end try
    begin catch
        raiserror('查询总记录数失败!可能hasOrderBy参数设置错误!',16,1)
        return
    end catch;

    --需要生成的excel工作表数目
    set @sheetCount = ceiling(@recordCount/cast(@pageSize as float))

    --查询表结构
    declare @columnName nvarchar(4000)
    declare @columnName2 nvarchar(4000)
    select @columnName=isnull(@columnName+',','')+SC.name,@columnName2=isnull(@columnName2+',','')+SC.name+' '+ 
    case when ST.name like '%char'
            then case when SC.length>255 then 'memo' else 'text('+cast(SC.length as varchar)+')' end
         when ST.name like '%int' or ST.name='bit' then 'int'
         when ST.name like '%datetime' then 'datetime'
         when ST.name like '%money' then 'money'     
         when ST.name like '%text' then 'memo'
         when ST.name='uniqueidentifier' then 'text(36)'
         else ST.name end
    from tempdb..sysobjects SO,tempdb..syscolumns SC,tempdb..systypes ST 
    where SO.id=SC.id and SO.xtype='U' and SO.status>=0 and SC.xtype=ST.xusertype and SO.name=@tableName
    and ST.name not in('image','sql_variant','varbinary','binary','timestamp')
    order by SC.colorder
    
    --删除用于复制表结构的空临时表
    declare @dropTableSql varchar(200)
    set @dropTableSql='if exists(select * from tempdb..sysobjects where name='''+@tableName+''') drop table '+@tableName
    exec (@dropTableSql)
            
    /*开启sp_oacreate服务,需要sysadmin权限*/
    begin try
        exec sp_configure 'show advanced options', 1
        reconfigure
        exec sp_configure 'ole automation procedures', 1
        reconfigure
        /*开启openrowset服务*/
        exec sp_configure 'show advanced options',1
        reconfigure
        exec sp_configure 'Ad Hoc Distributed Queries',1
        reconfigure
    end try
    begin catch
        raiserror('开启sp_oacreate服务失败,无法导出数据,可能数据库登录帐号没有sysadmin权限',16,1)
        return
    end catch;        

    declare @err int,@out int,@obj int,@src nvarchar(255),@desc nvarchar(255)
    --创建excel数据源,新建excel文件    
    exec @err=sp_oacreate 'adodb.connection',@obj out
    if @err<>0 begin set @errorMsg=1 goto errorMsg end
    set @sql='Provider=Microsoft.Jet.OLEDB.4.0;Extended Properties="Excel 8.0;HDR=YES;DATABASE='+@FileSavePath+'"'    
    exec @err=sp_oamethod @obj,'open',null,@sql
    if @err<>0 begin set @errorMsg=1 goto  errorMsg end
    
    --导出数据
    declare @startRow varchar(50)
    declare @sheetIndex int--当前excel表索引    
    declare @SheetNameNow nvarchar(300)    
    declare @orderBy varchar(30)--order by条件        
    if @HasOrderBy=1
        set @orderBy=','+@PrimaryKey
    else
        set @orderBy=' order by '+@PrimaryKey
    set @sheetIndex=@sheetCount    

    begin try
        while @sheetIndex > 0 
        begin
            --excel工作表名
            if @SheetName ='' or @SheetName is null
                set @SheetNameNow='Sheet'+cast((@sheetCount-@sheetIndex+1) as varchar)
            else
                set @SheetNameNow=@SheetName+cast((@sheetCount-@sheetIndex+1) as varchar)

            --在excel文件中创建@SheetNameNow工作表、插入表头
            set @sql='create table ['+@SheetNameNow+']('+@columnName2+')'
            exec @err=sp_oamethod @obj,'execute',@out out,@sql
            if @err<>0 begin set @errorMsg=1 goto errorMsg end
            
            set @sql='openrowset(''MICROSOFT.JET.OLEDB.4.0'',''Excel 8.0;HDR=YES;DATABASE='+@FileSavePath+''',['+@SheetNameNow+'$])'                    
            set @startRow = cast(((@sheetCount-@sheetIndex)*@pageSize) as nvarchar(50))

            if(@hastop='top')
                begin
                    set @tempSQL=substring(@SelectSQL,12,len(@SelectSQL)-11)--截取"select top "之后字符串
                    set @index=patindex('%[0-9][^0-9]%', @tempSQL)--查询top后最后一个数字位置
                    if @sheetIndex=@sheetCount
                        set @tempSQL='select top '+cast(@pageSize as varchar)+' '+substring(@tempSQL,@index+1,len(@tempSQL)-@index)    
                    else
                        set @tempSQL='select top '+@startRow+' '+substring(@tempSQL,@index+1,len(@tempSQL)-@index)    
                end
            else
                begin
                    if @sheetIndex=@sheetCount                            
                        set @tempSQL='select top '+cast(@pageSize as varchar)+' '+substring(@SelectSQL,8,len(@SelectSQL)-7)
                    else
                        set @tempSQL='select top '+@startRow+' '+substring(@SelectSQL,8,len(@SelectSQL)-7)
                end

            if @sheetIndex=@sheetCount--第一页
                exec('insert into '+@sql+'('+@columnName+') select '+@columnName+' from ('+@tempSQL+') as temp')                

            if @sheetIndex < @sheetCount--top max分页
                begin
                    exec('insert into '+@sql+'('+@columnName+') select top '+@pageSize+' '+@columnName
                    +' from ('+@SelectSQL+') as temp where '+@PrimaryKey+'>(SELECT MAX('+@PrimaryKey+') FROM ('+@tempSQL+@orderBy+') AS T)')                    
                end   
            set @sheetIndex=@sheetIndex-1
        end
    end try
    begin catch
        raiserror('导出数据失败!',16,1)
        return
    end catch;

    errorMsg:
        begin
            if @errorMsg=1
                begin
                    exec sp_oageterrorinfo 0,@src out,@desc out
                    declare @msg nvarchar(max)
                    select @msg=isnull(@msg,'')+'错误源:'+@src+',错误描述:'+@desc+' '
                    raiserror(@msg,16,1)
                end
        end
    exec sp_oamethod @obj,'close',null--关闭excel连接
    
    /*关闭相关服务    
    begin try
        /*关闭sp_oacreate服务
        exec sp_configure 'show advanced options', 1
        reconfigure
        exec sp_configure 'ole automation procedures',0
        reconfigure */

        /*关闭openrowset服务
        exec sp_configure 'Ad Hoc Distributed Queries',0
        reconfigure
        exec sp_configure 'show advanced options',0
        reconfigure */        
    end try
    begin catch
    end catch; */
END

C#调用代码

        volatile static int outputCount = 0;
        private static object lockOutput = new object();
        /// <summary>
        /// 导出数据到excel文件中,支持3个人同时导出数据,因为导出大量数据时消耗cpu资源高,所以限制3个人同时导出数据,可以根据情况进行调整
        /// </summary>
        /// <param name="selectSQL">查询的sql语句</param>
        /// <param name="hasOrderBy">sql语句最后是否有Order by</param>
        /// <param name="primaryKey">主键</param>
        /// <param name="fileSavePath">文件保存路径,绝对路径</param>
        /// <param name="sheetName">要创建的工作表名,默认为Sheet加索引</param>
        public static void ExportDataToExcel(string selectSQL, bool hasOrderBy, string primaryKey, string fileSavePath, string sheetName)
        {
            if (outputCount <= 3)
            {
                lock (lockOutput)
                {
                    outputCount++;
                }
                if (outputCount <= 3)
                {
                    try
                    {
                        SqlHelper helper = new SqlHelper();
                        helper.CreateStoredProcedureCommand("[proc_ExportDataToExcel]");
                        helper.AddParameter("@SelectSQL", selectSQL);
                        helper.AddParameter("@HasOrderBy", hasOrderBy);
                        helper.AddParameter("@PrimaryKey", primaryKey);
                        helper.AddParameter("@FileSavePath", fileSavePath);
                        helper.AddParameter("@SheetName", sheetName);
                        helper.ExecuteNonQuery();
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                    finally
                    {
                        lock (lockOutput)
                        {
                            outputCount--;
                        }
                    }
                }
                else
                {
                    lock (lockOutput)
                    {
                        outputCount--;
                    }
                    throw new Exception("当前已有3人正在导出数据,为节约服务器资源,请稍候再试!");
                }
            }
            else
            {
                throw new Exception("当前已有3人正在导出数据,为节约服务器资源,请稍候再试!");
            }
        }

 string fileSavePath = Server.MapPath("~/TempFile/导出数据/") + "用户名单(" + DateTime.Now.ToShortDateString() + ")" + DateTime.Now.ToFileTime() + ".xls";

posted @ 2019-09-25 21:39  事理  阅读(832)  评论(0编辑  收藏  举报