即时将查询结果导出到文件

时间:2010-04-22 08:43:48

标签: sql sql-server sql-server-2005 tsql

我需要将查询结果导出到csv文件并将该文件放在网络共享文件夹中。

  1. 是否可以在存储过程中实现此目的?
  2. 如果是,则会出现另一个约束:我可以在没有sysadmin权限的情况下实现此目的,即使不使用xp_cmdshell + BCP实用程序吗?
  3. 如果不是2.,如果SP所有者具有sysadmin权限,则呼叫者是否必须具有sysadmin权限?或者它是否足够?
  4. 以下是该问题的更多详细信息:SP必须动态导出和传输文件,如果出现问题则引发错误。调用者必须立即得到响应,即如果没有错误,他可以假设结果已成功传输到文件夹。因此,每N分钟运行一次的DTS / SSIS作业不是一种选择。我知道问题有点像我必须在应用程序级别这样做,但如果所有这些都可以通过T-SQL完成,我会非常高兴。

4 个答案:

答案 0 :(得分:2)

在我看来,您不是在等待问题的答案中的SQL代码。您问题的主要方面是安全方面。如果没有 sysadmin 权限并且没有新的安全漏洞,您应该怎么做才能实现您的需求?这是你认为的真正问题。

我发现至少有3种方法可以解决您的问题。但首先简单解释为什么在基于扩展存储过程的所有解决方案中存在sysadmin特权。像xp_cmdshell这样的扩展存储过程非常陈旧。它们至少存在于SQL Server 4.2之前,这是在第一个Windows NT(NT 3.1)下运行的第一个Microsoft SQL Server。在旧版本的SQL Server中,我没有执行此类过程的安全限制,但后来有人制定了此类限制。重要的是要了解所有允许在xp_cmdshellsp_OACreate 等SQL Server帐户下启动任何流程通用过程具有 sysadmin 权限限制。只有具有明确使用区域和基于角色的权限的面向任务的过程才能在没有安全漏洞的情况下解决问题。所以这是我之前承诺的3种解决方式:

  • 使用 sysadmin 权限在SQL Server上创建新的SQL帐户。然后创建一个存储过程,该过程使用扩展存储过程中的一些,如xp_cmdshellsp_OACreate,并在技术上实现您的要求(将一些信息导出到CSV文件中)。关于 EXECUTE AS 子句(请参阅http://msdn.microsoft.com/en-us/library/ms188354.aspx),您可以配置创建的存储过程,使其在具有 sysadmin 权限的帐户下运行。您将此过程的执行委派给具有某个SQL角色的用户,从授权权限方面更灵活。
  • 您可以使用 CLR存储过程而不是xp_cmdshellsp_OACreate。您还应该对创建的过程使用基于角色的权限。
  • 最终用户不会直接调用您创建的任何SQL存储过程。存在一个调用SQL存储过程的软件(如WCF服务或Web站点)。您可以在此软件内部实现导出到CSV文件,而不是在任何SQL存储过程内部。

在所有实施方式中,您应准确定义用于访问文件系统的帐户的您将保留密码的位置。您有不同的选择,都有相应的优点和缺点。可以使用模拟来允许使用最终用户的帐户访问文件系统。最好的方法取决于您在环境中的情况。

答案 1 :(得分:1)

您可以构建一个SQL代理作业,并通过触发器或SP从系统SP中删除它。该作业可能会调用SSIS或批量转储scrits ...返回即时错误消息可能是一个问题,但

一般来说,这是非常不寻常的要求 - 你想要完成什么?

更新: 经过一番思考后 - 这是一个设计问题,我无法通过使用SQL Server SP来找到解决方案。

过去 - 这就是我所做的:

  • 在应用程序级别 - 实现异步过程,用户按下按钮,请求文件下载;该应用程序接受并让用户进入
  • 用户可以通过状态页面查看状态,或者在完成或发生错误时收到电子邮件
  • 同时是应用程序层,无论是SSIS包还是SQL Agent Job
  • 如果需要参数 - 使用设计并实现特殊表:JOB_PARAMETERS - 您可以在哪里放置参数
  • 您还需要创建更多表来管理作业和存储作业状态并与应用程序层进行通信
  • 您可能希望在数据库级别使用SQL Server Broker
  • 您可能希望在应用级别使用MSMQ

这并不容易,但这是导出数据的最有效方式,从数据库到文件,无需通过浏览器访问应用服务器和用户PC。

答案 2 :(得分:1)

您可以使用OLE自动化吗?这很丑陋,你可能会使用一些基于集合的字符串构建技术而不是光标,但这里就是......

    Declare @Dir varchar(4000)
    Set @Dir = 'c:\some\path\accessible\to\SQLServer'

    If @Dir IS NULL
       Begin
        print 'dir is null.'
        Return 1
       End

    declare
        @FilePath as varchar(255),
        @DataToWrite as varchar(8000)

    If right(@DataDir,1) <> '\'
       Set @DataDir = @DataDir + '\'

    Set @FilePath = @DataDir + 'filename.csv' 

    DECLARE @RetCode int , @FileSystem int , @FileHandle int

    EXECUTE @RetCode = sp_OACreate 'Scripting.FileSystemObject' , @FileSystem OUTPUT
    IF (@@ERROR|@RetCode > 0 Or @FileSystem < 0)
    begin
      RAISERROR ('could not create FileSystemObject',16,1)

    End

    declare @FileExists int

    Execute @RetCode = sp_OAMethod @FileSystem, 'FileExists', @FileExists OUTPUT, @FilePath
    --print '@FileExists = ' + cast(@FileExists as varchar)

    If @FileExists = 1
    Begin
        RAISERROR ('file does not exist',16,1)
        /*return 1*/
    End

    --1 = for reading, 2 = for writing (will overwrite contents), 8 = for appending
    EXECUTE @RetCode = sp_OAMethod @FileSystem , 'OpenTextFile' , @FileHandle OUTPUT , @FilePath, 8, 1
    IF (@@ERROR|@RetCode > 0 Or @FileHandle < 0)
    begin
        RAISERROR ('could not create open text file',16,1)
    End


    DECLARE CSV CURSOR
    READ_ONLY
    FOR 

    Select Anything From MyDataTable
    order by whatever

    DECLARE @fld1 nvarchar(50)
        ,@fld2 nvarchar(50)

    OPEN CSV

    FETCH NEXT FROM CSV INTO @fld1, @fld2 

    WHILE (@@fetch_status <> -1)
    BEGIN
        IF (@@fetch_status <> -2)
        BEGIN

            Set @DataToWrite = @fld1 + ',' + @fld2 + char(13) + char(10) 
            EXECUTE @RetCode = sp_OAMethod @FileHandle , 'Write' , NULL , @DataToWrite 

            IF (@@ERROR|@RetCode > 0)
               begin
                RAISERROR ('could not write to file',16,1)

               End
        END

        FETCH NEXT FROM OpenOrders INTO @fld1, @fld2 

    END


CLOSE CSV
DEALLOCATE CSV

EXECUTE @RetCode = sp_OAMethod @FileHandle , 'Close' , NULL
IF (@@ERROR|@RetCode > 0)
RAISERROR ('Could not close file',16,1)

EXEC sp_OADestroy @FileSystem

return 0

End

答案 3 :(得分:1)

一般来说,不,如果没有大量的工作和努力以及系统管理权,这种工作就无法完成。

SQL是一个数据库引擎,专注于数据库问题,因此非常糟糕的文件操作工具。解决方法包括:

  • xp_cmdshell是文件操作的首选工具。
  • 我自己喜欢sp_OA *解决方案,因为它给了我回溯到SQL 7.0。但是使用这些功能总让我感到紧张。
  • 您可以使用OPENROWSET执行某些操作,其中插入的目标是使用此函数定义的文件。听起来不太可能,值得一看。
  • 同样,链接服务器定义可以用作插入目标或选择... into ...语句。

安全似乎是你的风雨。总的来说,当SQL弹出操作系统时,它拥有启动SQL服务的NT帐户的所有权限;如果您想限制网络访问,请仔细配置该帐户(并且永远不要将其设为域管理员!)

可以将xp_cmdshell作为没有sysadmin权限的用户调用,并将这些调用配置为不具有与SQL Service NT帐户相同的访问权限。根据BOL(SQL 2005及以上版本):


xp_cmdshell代理帐户
当不是sysadmin固定服务器角色成员的用户调用它时,xp_cmdshell使用存储在名为## xp_cmdshell_proxy_account ##的凭据中的帐户名和密码连接到Windows。如果此代理凭据不存在,则xp_cmdshell将失败。

可以通过执行sp_xp_cmdshell_proxy_account来创建代理帐户凭据。作为参数,此存储过程采用Windows用户名和密码。例如,以下命令为Windows域用户SHIPPING \ KobeR创建具有Windows密码sdfh%dkc93vcMt0的代理凭据。


因此,您的用户使用任何用户权限( sysadmin!)登录并执行存储过程,该过程调用xp_cmdshell,这将“拾取”已配置的任何代理权限。再次,尴尬,但听起来它会做你想要它做的事情。 (可能的限制因素是您只获得一个代理帐户,因此必须满足所有可能的需求。)

老实说,听起来像我最好的解决方案是:

  • 确定对存储过程的调用的来源,
  • 让程序返回要写入文件的数据(如果需要,您可以在程序中执行所有格式和布局),并且
  • 让调用例程管理所有文件准备步骤(可能就像将从SQL返回的数据重定向到打开的文件一样简单)

那么,什么会启动对存储过程的调用?