【问题标题】:Dynamically create local temp table columns from permanent table rows从永久表行动态创建本地临时表列
【发布时间】:2016-01-04 19:04:37
【问题描述】:

背景:我为第三方 Web 平台编写自定义 SQL 报告——多个数据库,其中大多数是 SQL Server 2008 R2 或 2012——我遇到了一种情况我的脚本的不同部分中没有维护临时表。我无法确定是否只是范围不同,或者这些部分是否使用单独的连接。无论如何,这对我来说都是一个大问题,因为这意味着必须从头开始多次重新创建临时表,或者在我不想这样做时必须创建永久表。

为了简化事情,我编写了一个存储过程,它获取临时表(或任何表,实际上)的内容,对其进行透视,并将列作为行存储在永久表中。这是永久表:

CREATE TABLE [dbo].[CustomTempRows] (
[who] [varchar](25) NOT NULL -- A session variable from the web server
,[dtdate] [datetime] NOT NULL -- The date the row was entered
,[stable] [varchar](100) NOT NULL -- The name of the source table
,[scolumn] [varchar](100) NOT NULL -- The name of the source column
,[irow] [int] NOT NULL -- The row number from the source table
,[icolumn] [int] NOT NULL -- The column number from the source table
,[itype] [int] NOT NULL -- Whether or not the column is a string, date, or number
,[dvalue] [numeric](24, 8) NULL -- The value from the source table if it's a number.
,[svalue] [nvarchar](max) NULL -- The value from the source table if it's a string.
,[dtvalue] [datetime] NULL -- The value from the source table if it's a date.
)  

该过程获取临时表中每一行的每一列,并将其作为一行插入CustomTempRows 表中。它要求临时表有一个名为TID 的标识列,以确定行号,该行号进入irow。为简单起见,它将值转换为 nvarchar、数字或日期时间——我相信这在 99% 的情况下应该没问题。供参考,程序如下:

create procedure Custom_Table_Columns_to_Rows (@who varchar(max), @dtdate datetime, @stablename varchar(max), @breverse int)
as
BEGIN
/* NOTES

1. Temp table MUST have an identity column called TID
2. The procedure strips out timestamp and sysname columns

*/

declare @temptable varchar(100)
set @temptable = '##' + convert(varchar,@@SPID) 

if @breverse = 0 /* This parameter indicates that we are inserting to the custom table 
                rather than selecting.  Not sure if selecting is actually possible
                in stored procedure */
BEGIN

/* Clean up the custom table */
delete customtemprows where (who = @who and stable = @stablename) or datediff(mi,dtdate,getdate() )>60

/* Dynamic SQL to create a global temp table and copy the user's temp table into it */
exec('if OBJECT_ID(''tempdb..' + @temptable + ''') is not null drop table ' + @temptable + ';

select * into ' + @temptable + ' from ' + @stablename )

/* Insert into customtemprows for the row and column numbers, as well as the column names and data types
(which are limited to nvarchar, numeric, and datetime -- everything is converted to one of these) 
The data columns are left blank for the moment. */
exec('
insert customtemprows (who, dtdate, stable, scolumn, irow, icolumn, itype)
select 5555
, getdate()
, ''' + @stablename + '''
, c.name
, te.tid
, c.column_id
, itype = case when t.name like ''%char%'' or t.name like ''%name%'' then 1
when t.name like (''%date%'') then 2
else 3 end

from tempdb.sys.columns c
inner join sys.types t on c.system_type_id = t.system_type_id
cross join ' + @temptable + ' te

where 
object_id = object_id(''tempdb..' + @temptable + ''')
and t.name not in (''sysname'', ''timestamp'')
and c.name<>''tid''
order by te.tid, c.column_id')

/* Create variables to use when running the loop */
declare @cols as table (icolumn int, scolumn varchar(100), itype int)
insert @cols
select distinct icolumn, scolumn, itype from customtemprows where who=@who and stable = @stablename and scolumn not in ('tid') order by icolumn

declare @icolumn int, @scolumn varchar(100), @itype int

select top 1 @icolumn = icolumn, @scolumn = scolumn, @itype = itype from @cols order by icolumn

/* Loop through as long as there are columns */
while exists (select * from @cols) 
BEGIN

/* If this column is a string, put it into the svalue column */
exec('update r
set svalue = t.' + @scolumn + '
FROM 
  customtemprows r
  inner join 
 (SELECT tid, ' + @scolumn + '
   FROM ' + @temptable + ') t on r.irow=t.tid
where
r.itype = 1
and r.icolumn = ' + @icolumn + '
and r.who = ''' + @who + ''' 
and r.stable = ''' + @stablename+ '''' ) 

/* If this column is a date, put it into the dtvalue column */
exec('update r
set dtvalue = t.' + @scolumn + '
FROM 
customtemprows r
  inner join 
     (SELECT tid, ' + @scolumn + '
   FROM ' + @temptable + ') t on r.irow=t.tid
where
r.itype = 2
and r.icolumn = ' + @icolumn + '
and r.who = ''' + @who + ''' 
and r.stable = ''' + @stablename+ '''' ) 

/* If this column is a number, put it into the dvalue column */
exec('update r
set dvalue = convert(numeric(24,10),t.' + @scolumn + ')
FROM 
  customtemprows r
  inner join 
     (SELECT tid, ' + @scolumn + '
   FROM ' + @temptable + ') t on r.irow=t.tid
where
r.itype = 3
and r.icolumn = ' + @icolumn + '
and r.who = ''' + @who + ''' 
and r.stable = ''' + @stablename+ '''' ) 

delete @cols where icolumn = @icolumn

select top 1 @icolumn = icolumn, @scolumn = scolumn, @itype = itype from @cols order by icolumn

END /* Loop */

/* Return the inserted values.  For troubleshooting */
select * from customtemprows where who = @who and stable = @stablename order by icolumn, irow
END /* @breverse = 0 */

END

我的问题正在寻找将数据从CustomTempRows 中取出并返回到临时表中以用于我报告的其他部分的最佳方法。我已经写了一份声明来完成这项工作,但我对此并不满意。我会解释原因,但首先,这里是查询:

declare @who varchar(100), @stablename varchar(100), @temptable varchar(100)

/* Set user variables */
set @who = '5555' /* Usually Session ID */
set @stablename = '#temp' /* The temp table you started with */
set @temptable = '##global' /* The name of the global temp table to create */

/* Drop the global temp table if it exists */
exec('if OBJECT_ID(''tempdb..' + @temptable + ''') is not null drop table ' + @temptable )

declare @columns varchar(max)
declare @rows as table (irow int, icolumn int, scolumn varchar(100), itype int, svalue nvarchar(max))
declare @irow int, @icolumn int, @scolumn varchar(100), @itype int, @svalue nvarchar(max), @convert varchar(50)
declare @sql varchar(max)

/* Create a list of columns to include in the temp table */
set @columns = '';
select @columns = @columns + ', ' + scolumn + ' ' + case r.itype when 1 then 'nvarchar(max)' when 2 then 'datetime' else 'numeric(24,10)' end + ' NULL'
FROM (select distinct icolumn, scolumn, itype from customtemprows 
where stable=@stablename and who = @who) r

select @columns = STUFF(@columns, 1, 2, '')

/* Create the global temp table and populate the TID column */
exec('create table ' + @temptable + '  (tid int, ' + @columns + ');

insert ' + @temptable + ' (tid)
select distinct irow from customtemprows where stable=''' + @stablename + ''' and who = ''' + @who + '''')

/* create data for the loop */
insert @rows
select irow, icolumn, scolumn, itype

/* Can't combine data types; convert everything to string */
, svalue = coalesce(svalue, convert(nvarchar,dvalue), convert(nvarchar,dtvalue)) 

from customtemprows where stable = @stablename and who = @who
order by icolumn, irow

select top 1 @irow = irow, @icolumn = icolumn, @scolumn = scolumn, @itype = itype, @svalue = svalue
/* For converting the string back to its previous data type */
, @convert = case when itype = 1 then 'convert(nvarchar(max),svalue)' 
    when itype = 2 then 'convert(datetime,svalue)'
    else 'convert(numeric(24,10),svalue)' end
     from @rows order by icolumn, irow

/* As long as there are rows */
while exists (select * from @rows)

BEGIN
set @sql = ''

/* Update the temp table one column at a time with data from the table variable */
select @sql = 'update t
set ' + @scolumn + ' = ' + @convert + '
from 
' + @temptable + ' t
inner join (
select irow, icolumn, scolumn, itype, svalue = coalesce(svalue,    convert(nvarchar,dvalue), convert(nvarchar,dtvalue)) 
from customtemprows where stable=''' + @stablename + ''' and who = ''' + @who + ''') r
on r.irow=t.tid and r.icolumn = ' + convert(varchar,@icolumn) 


exec(@sql)

delete @rows where icolumn = @icolumn

select top 1 @irow = irow, @icolumn = icolumn, @scolumn = scolumn, @itype = itype, @svalue = svalue
, @convert = case when itype = 1 then 'convert(nvarchar(max),svalue)' 
    when itype = 2 then 'convert(datetime,svalue)'
    else 'convert(numeric(24,10),svalue)' end
     from @rows order by icolumn, irow 


END /* Loop */

select * from ##global

这不是最优雅的解决方案,我想最终用基于集合的东西替换 while 循环,但这些是更大的问题:

  1. 我想将这个(或至少大部分)移动到一个存储过程中,这样我就可以与同事共享它,而不必在每次使用它时都重现那个大块代码。
  2. 我希望将最终结果放入本地而非全局临时表中。

不过,为了做到这一点,我必须在调用过程之前定义临时表(这是我正在开发的平台的限制)。但是,因为我使用动态 SQL 来创建表,所以我无法弄清楚如何在需要使用它的同一范围内创建表。

我也宁愿使用序号来命名全局临时表,以防止两个用户同时访问报告时发生冲突。但同样,如果我这样做,我的报告代码的其余部分将不知道表的名称。

我的问题:是否甚至可以根据我的报告范围内可用的CustomTempRows 中的数据定义一个本地临时表,然后使用基于我的长查询?

感谢您将我的帖子写到最后,以及您可能提出的任何建议。不胜感激。

【问题讨论】:

  • 顺便说一句:如果您的查询使用相同的连接,则不需要全局临时表。普通的#temp 表位于 TempDB 中,您会发现它们(来自同一连接中的其他会话),名称如“#MyTempTable____________________________________________________________________________000000000002”用SELECT [name] FROM tempdb.sys.tables WHERE [name] like '#tempTable%' 检查
  • 当然有可能我相信,当您传入 rowIdentifier 时,需要创建 fnGetCreateTableSql 以获得 Sql,即,您的 iRow 所有函数将为您提供一个带有 create #MyTable 的字符串,现在创建另一个名为 fnGetUnpivotSql 的函数通过传入相同的 Irow 也将在其中提供带有 unpivot sql 的 SQL 现在创建主 Sql = fnGetCreateTableSql (1) + ';插入#MyTable ' + fnGetUnpivotSql (1) + ';从#MyTable中选择*;'应该解决问题。这是一个最好的猜测。
  • @jean -- 观点:数据的条件并不总是相同的。每次运行报告时,我都必须重新创建视图。我不相信查询(即报告的不同部分)使用相同的连接。即使是全局临时表也不能在部分之间共享。我认为当部分完成时它们会被销毁。

标签: sql sql-server tsql stored-procedures dynamic-sql


【解决方案1】:

创建一个包含 1 列的临时表,然后动态运行动态 SQL 以向临时表添加其他字段。

DECLARE @SQL varchar(max)

CREATE TABLE #tmp(Id int)

SET @SQL = 'ALTER TABLE #tmp ADD Column1 varchar(20)'
EXEC(@SQL)

【讨论】:

  • 好吧,我显然被误导了,因为我不认为在动态 SQL 语句之外创建的本地临时表可以在其中访问。这显然改变了一切。我能够将这一原则融入我的程序中,现在一切都按照我想要的方式进行。谢谢!
猜你喜欢
  • 2023-03-22
  • 1970-01-01
  • 2011-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多