MSSQL存储过程动态创建临时表

时间:2015-07-01 14:47:27

标签: sql-server

我们正在尝试编写一些自动化报告来执行我们存储在表中的SQL语句。表数据通常在触发器调用的存储过程中使用,并使用通过临时表传递的数据(在触发器语句中创建),并具有表名,然后是对#TempInserted和#TempDeleted有效的SQL语句,对应于触发器中的Inserted和Deleted对象,然后是一些确定将输出发送到何处的电子邮件列。

这一切都可以在触发器语句中正常工作,因为每个临时表在执行期间都会创建一次: -

SELECT * INTO #TempInserted FROM INSERTED
SELECT * INTO #TempDeleted FROM DELETED

然后触发器调用TriggerHandler存储过程,将表名作为参数传递。

...

但是,当我尝试从一般存储过程动态创建这些语句以便将这些语句作为报告触发时(因此我们不重复语句),在批处理中,我遇到了一个问题: - < / p>

SELECT * INTO #TempInserted FROM ...

从定义的表或对象(例如“FROM INSERTED”)中工作正常,但我发现它无法从动态查询中获取它的模式。

例如,我可以做

SELECT TOP 1 * INTO #Test FROM TableA 
SELECT * FROM #Test
DROP TABLE #Test

但我不能那么做

EXECUTE sp_executesql N'SELECT TOP 1 * INTO #Test FROM TableA'
SELECT * FROM #Test
DROP TABLE #Test

因为#Test是EXECUTE上下文的本地,而不是它的父。 但是,我可以在EXECUTE(或存储过程)中执行插入,因为临时表在范围内,如果我已经创建了表模式: -

SELECT * INTO #Test FROM TableA WHERE 1 = 2 -- create an empty schema
EXECUTE sp_executesql N'INSERT INTO #Test SELECT TOP 10 * FROM TableA'
SELECT * FROM #Test
DROP TABLE #Test

所以,没关系,但是当我想动态创建该架构时,我的问题就出现了,具体取决于运行报告的表名。 INSERT有效: -

SELECT * INTO #Test FROM TableA WHERE 1 = 2 -- create an empty schema
DECLARE @Table NVARCHAR(20) = 'TableA'
DECLARE @SQL NVARCHAR(200) = N'INSERT INTO #Test SELECT TOP 10 * FROM ' + @Table
EXECUTE sp_executesql @SQL
SELECT * FROM #Test
DROP TABLE #Test

但是只有临时表已经有一个模式。如果我尝试有条件地创建模式,根据所选的表,我得到一个解析错误: -

DECLARE @Table NVARCHAR(20) = 'TableA'
IF @Table = 'TableA'
    SELECT * INTO #Test FROM TableA WHERE 1 = 2 -- create an empty schema
IF @Table = 'TableB'
    SELECT * INTO #Test FROM TableB WHERE 1 = 2 -- create an empty schema

DECLARE @SQL NVARCHAR(200) = N'INSERT INTO #Test SELECT TOP 10 * FROM ' + @Table
EXECUTE sp_executesql @SQL
SELECT * FROM #Test
DROP TABLE #Test

给出“数据库中已经有一个名为'#Test'的对象。” - 因此查询解析器不遵循查询的结构,该结构实际​​上只创建临时表一次。如果你这样做也是如此

SELECT * INTO #Test FROM ....
DROP TABLE #Test
SELECT * INTO #Test FROM ....

那么,在SQL Server 2012中是否存在能够执行

的方法
SELECT * INTO #Test FROM (dynamic SQL statement)

或绕过解析器,认为你正在创建对象两次

DECLARE @Table NVARCHAR(20) = 'TableA'
IF @Table = 'TableA'
    SELECT * INTO #Test FROM TableA WHERE 1 = 2 -- create an empty schema
IF @Table = 'TableB'
    SELECT * INTO #Test FROM TableB WHERE 1 = 2 -- create an empty schema

或从现有数据库表的模式动态创建本地作用域临时表,其中表名存储在变量中(我发现的所有示例都使用“SELECT * INTO #Test”代码,正如我所提到的,需要一个静态定义的对象来创建?)

------- --------编辑

对于一些上下文,这是我们为什么这样做的一个例子: - 如果某个项目类型被交易到某个位置,触发器可能会触发产生警告电子邮件。这适用于我们当前的触发器。我们之所以这样做,是因为我们将来可以编写UI,以便用户可以自己将其他项类型添加到此列表中,而不是我们必须更新触发器 - 这也意味着我们可以控制/验证在点击式界面的幕后生成的SQL,这样我们的用户就不需要知道任何SQL,并且我们可以确定不会使用任何恶意或导致错误的内容。

我们也不能在BLL中这样做,因为它来自我们的ERP系统,这意味着我们必须对基础对象进行更改,如果可以避免这种情况,这显然是不可取的。

这些电子邮件中有可能被遗漏/忽略/遗忘/未被处理,因此用户定期请求相同的信息,以及在交易发生时: - < / p>

接下来,我们希望针对其中一些触发器语句生成每日/每周/每月报告。现在,显然,如果我们可以使用我们设置的现有SQL触发器语句,那将是理想的,如果更改它将自动影响定期报告 - 保持DRY。这也意味着如果我们设置一个新的触发器,我们可以自动将它包含在报告中,只需将触发器代码的引用以及表名,频率等插入到驱动存储的定期报告的表中。程序。同样,将来我们可以编写一个用户界面,这样用户就可以自己请求和安排这些报告,而无需我们的干预。

1 个答案:

答案 0 :(得分:0)

我怀疑我在这里遇到了陷阱22的情况。但是,我已经找到了解决它的方法,而且太乱了。我将项目处理代码提取到另一个存储过程中,然后将其执行复合到动态&#34; SELECT INTO&#34;语句 - 这种方式它在同一个执行实例中运行,因此可以访问在该实例中创建的临时表: -

SET @SQL = 'SELECT * INTO #TestTable FROM ' + @Table + ' WHERE ' + @WhereClause
SET @SQL = @SQL + '; EXEC ReportProcess'
EXECUTE sp_executeSQL @SQL
然后,ReportProcess存储过程可以访问临时表,并可以相应地处理它