2016-01-04 1 views
0

ПРЕДПОСЫЛКА: Я пишу собственные SQL-отчеты для сторонней веб-платформы - несколько баз данных, большинство из которых либо SQL Server 2008 R2, либо 2012 год, вы столкнетесь с ситуацией, когда временные таблицы не поддерживаются в разных разделах моих сценариев. Я не смог выяснить, не отличается ли это от объема, или же разделы используют отдельные соединения. В любом случае, это была большая проблема для меня, потому что это означало необходимость воссоздавать временные таблицы несколько раз с нуля или создавать постоянные таблицы, когда мне бы этого не хотелось.Динамически создавать столбцы локальной таблицы тем из постоянных строк таблицы

Чтобы упростить ситуацию, я написал хранимую процедуру, которая занимает содержимое таблицы temp (или любой таблицы), сворачивает ее и сохраняет столбцы в виде строк в постоянной таблице. Это постоянная таблица:

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. Это требует, чтобы таблица temp имела столбец идентификаторов, называемый TID, чтобы определить номер строки, который идет в irow. Для простоты он преобразует значения в nvarchar, numeric или datetime - я считаю, что это должно быть хорошо в 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 [email protected] 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 [email protected] 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 

Это не самое элегантное решение, и я хотел бы, чтобы в конечном счете заменить время цикла с чем-то на основе набора, но эти большие проблемы :

  1. Я хотел бы, чтобы переместить это (или по крайней мере большинство из них) в хранимую процедуру так, что я могу поделиться им с коллегами и не должны воспроизводить, что большой блок кода каждый раз, когда я использую его.
  2. Я хотел бы получить окончательные результаты в локальной, а не в глобальной таблице темп.

Для этого мне нужно будет определить временную таблицу перед вызовом процедуры (ограничение платформы, для которой я разрабатываю). Однако, поскольку я использую динамический SQL для создания таблицы, я не могу понять, как создать таблицу в той же области, где мне нужно ее использовать.

Я бы предпочел использовать последовательный номер, чтобы назвать глобальную таблицу темп, чтобы предотвратить конфликты, когда два пользователя одновременно получают доступ к отчету. Но опять же, если я это сделаю, остальная часть моего кода отчета не будет знать имя таблицы.

МОЙ ВОПРОС: Есть ли еще можно определить локальную временную таблицу на основе данных в CustomTempRows, которые будут доступны в моем объеме отчета, а затем заполнить эту таблицу с помощью хранимой процедуры на основе моего длинного запроса?

Благодарим вас за то, что вы сделали это до конца моего сообщения, и за любые предложения, которые у вас могут быть. Это оценено.

+0

Почему бы не использовать виды? – jean

+0

Кстати: вам не нужна глобальная таблица temp, если ваши запросы используют одно и то же соединение. Обычные таблицы #temp лежат в ** TempDB **, и вы плохо их находите (из других сеансов в том же соединении) с именами, такими как «#MyTempTable ____________________________________________________________________________ 000000000002», проверьте его с помощью 'SELECT [name] FROM tempdb.sys.tables WHERE [name] like '# tempTable%' ' – jean

+0

, конечно же, возможно, я полагаю, что fnGetCreateTableSql нужно будет создать, чтобы получить Sql, когда вы проходите в строкеIdentifier, т. е. ваш iRow, все функции предоставят вам строку с созданием #MyTable, теперь создайте другую функция, называемая fnGetUnpivotSql, передавая в той же Irow, которая также предоставит SQL с univot sql, теперь создаст главный Sql = fnGetCreateTableSql (1) + '; Вставить в #MyTable '+ fnGetUnpivotSql (1) +'; Выберите * из #MyTable; ' должен решить проблему. это лучшее предположение. – bhushanvinay

ответ

0

Создайте временную таблицу с 1 столбцом, а затем динамически запускайте динамический SQL, чтобы добавить дополнительные поля в таблицу temp.

DECLARE @SQL varchar(max) 

CREATE TABLE #tmp(Id int) 

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

Ну, я был явно дезинформирован, потому что я не думал, что в нем будет доступна локальная таблица темпов, созданная вне динамического оператора SQL. Это явно меняет все. Я смог включить этот принцип в свою процедуру, и теперь все работает так, как я этого хочу. Спасибо! –