【问题标题】:Select results from stored procedure into a table从存储过程中选择结果到表中
【发布时间】:2022-01-08 07:01:44
【问题描述】:

我有一个存储过程,usp_region,它有一个包含 50 列的 select 语句作为结果集。此过程被我们应用程序中的多个其他存储过程调用。

大部分存储过程都会向这个过程传递一个参数并显示它返回的结果集。我有一个存储过程usp_calculatedDisplay,它从这个存储过程中获取列并将值插入到临时表中,并对列进行更多计算。

这是usp_calculatedDisplay中的部分代码。

Begin Procedure

/* some sql statements */

Declare #tmptable
(
    -- all the 50 columns that are returned from the usp_region procedure
)

Insert Into #tmptable
    exec usp_region @regionId = @id

Select t.*, /* a few calculated columns here */
From #tmptable t

End of procedure

每次我在usp_region 过程中添加一列时,我还必须确保我必须将它添加到此过程中。否则它会破裂。维护它变得很困难,因为当将列添加到usp_region 时,很可能有人会错过将列添加到usp_calculatedDisplay 过程。

为了克服这个问题,我决定这样做:

Select *
Into #tmptable
From OPENROWSET('SQLNCLI', 
                'Server=localhost;Trusted_Connection=yes;', 
                'EXEC [dbo].[usp_region]')

问题是“即席分布式查询”组件已关闭。所以我不能用这种方法来克服这个问题。我想知道是否有任何其他方法可以克服这个问题。我真的很感激任何帮助。谢谢!

【问题讨论】:

  • 您可以调查的一个选项是使用 sp_describe_first_result_set 并动态构建表格。
  • usp_calculatedDisplay 第一次遇到ShoeWidth 列时,应该自动执行什么计算?
  • 你正在解决一个更大的问题。您的架构正在发生变化,没有人在管理更改过程或似乎使用任何工具来帮助评估架构更改的影响。无论如何使用SELECT * 是一个坏习惯,但一种选择是将ups_region 更改为TVF,这样您就不需要使用临时表了。
  • 谢谢大家的回复。非常感谢!
  • @stu 这是我绝对可以调查的事情

标签: sql sql-server tsql stored-procedures


【解决方案1】:

每次我在 usp_region 过程中添加一列

SQL Server 是一个结构化数据库,它并不意味着要解决需要每天更改结构的情况。

如果您经常添加/删除列,那么您可能没有选择正确的数据库类型,您最好重新设计您的系统。

维护它变得很困难,因为在将列添加到 usp_region 时,很可能有人会错过将列添加到 usp_calculatedDisplay 过程。

有两个简单的解决方案 (1) 使用 DDL 触发器 - 非常糟糕的想法,但易于实现和工作。 (2) 用我的伎俩select from stored procedure

选项 1:使用 DDL 触发器

您可以自动化整个过程并在每次更改存储过程 usp_region 时更改存储过程 usp_calculatedDisplay

https://docs.microsoft.com/en-us/sql/relational-databases/triggers/ddl-triggers

基本方法是

CREATE OR ALTER TRIGGER NotGoodSolutionTrig ON DATABASE FOR ALTER_PROCEDURE AS BEGIN
    DECLARE @var_xml XML = EVENTDATA();
    IF(
        @var_xml.value('(EVENT_INSTANCE/DatabaseName)[1]', 'sysname') = 'tempdb'
        and 
        @var_xml.value('(EVENT_INSTANCE/SchemaName)[1]', 'sysname') = 'dbo'
        and 
        @var_xml.value('(EVENT_INSTANCE/ObjectName)[1]', 'sysname') = 'usp_region'
        )
        BEGIN
            -- Here you can parse the text of the stored procedure 
            -- and execute ALTER on the first SP
            -- To make it simpler, you can design the procedure usp_region so the columns names will be in specific row or between to comment which will help us to find it
        
            -- The code of the Stored Procedure which you need to parse is in the value of:
            -- @var_xml.value('(EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)'))
            -- For example we can print it
            DECLARE @SP_Code NVARCHAR(MAX)
            SET @SP_Code = CONVERT(NVARCHAR(MAX), @var_xml.value('(EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)'))
            PRINT @SP_Code
            
            -- In your case, you need to execute ALTER on the usp_calculatedDisplay procedure using the text from usp_region
        END
END

选项 2:使用 sys.dm_exec_describe_first_result_set 从存储过程中进行选择的技巧

这是获得所需内容的简单直接的方法。

CREATE OR ALTER PROCEDURE usp_calculatedDisplay AS 

    -- Option: using simple table, so it will exists outsie the scope of the dynamic query
    DROP TABLE IF EXISTS MyTable;
    DECLARE @sqlCommand NVARCHAR(MAX)
    select @sqlCommand = 'CREATE TABLE MyTable(' + STRING_AGG ([name] + ' ' +  system_type_name, ',') + ');'
    from sys.dm_exec_describe_first_result_set (N'EXEC usp_region', null,0)
    PRINT @sqlCommand
    EXECUTE sp_executesql @sqlCommand

    INSERT MyTable EXECUTE usp_region;

    SELECT * FROM MyTable;

GO

注意!!! 不建议在生产中使用这两种解决方案。我的建议是通过重新设计系统来避免这种需求。如果您需要重新编写 20 SP 就这样做,不要偷懒!您的目标应该是最适合数据库使用的目标。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-20
    • 1970-01-01
    • 2014-07-17
    • 2013-09-24
    • 2011-06-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多