Dynamically create temp table, insert into temp table, and then select - sql-server

Basically i want to be able to dynamically create a temp table based off of an existing table, and then insert values into the temp table, and select the inserted values.
i've got the part where i can create the temp table working just fine, it's just that inserting and selecting form it aren't working too well.
here's my current code.
declare #table table
(
OrdinalPosition int,
ColumnName nvarchar(255),
DataType nvarchar(50),
MaxChar int,
Nullable nvarchar(5)
)
declare #i int
declare #count int
declare #colname nvarchar(255), #datatype nvarchar(50), #maxchar int
declare #string nvarchar(max)
declare #tblname nvarchar(100)
set #tblname='Projects'
set #string='create table #' + #tblname + ' ('
insert into #table
(
OrdinalPosition,
ColumnName,
DataType,
MaxChar,
Nullable
)
SELECT
ORDINAL_POSITION ,
COLUMN_NAME ,
DATA_TYPE ,
CHARACTER_MAXIMUM_LENGTH ,
IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tblname
set #i=1
select #count=count(*) from #table
while (#i<=#count)
begin
select #colname=ColumnName from #table where OrdinalPosition=#i
select #datatype=DataType from #table where OrdinalPosition=#i
select #maxchar=MaxChar from #table where OrdinalPosition=#i
if (#maxchar is null)
begin
set #string = #string + #colname + ' ' + #datatype
end
else
begin
set #string = #string + #colname + ' ' + #datatype + '(' + cast(#maxchar as nvarchar(20)) + ')'
end
if (#i=#count)
begin
set #string = #string + ')'
end
else
begin
set #string = #string + ', '
end
set #i=#i+1
end
select #string
exec(#string)
set #string='
insert into #Projects (pk_prID, prWASSN_ID, prProjectStatus, prBusinessUnit, prServiceLine, prStudyTypeCode, prStudyNumber, prTimePoint, prStudyDirector,
prGroupLeader, prBookedDate, prBookedAmount, prConsumed, prBudgetedHours, prFinalReport, prFinalYear, prFinalMonth, prStartQA,
prLabWorkStarted, prLabWorkCompleted, prProjImpDate, prCompanyName, prCompanyNumber, prIsFTE, prRevisedDeadlineDate, prProjectFinalized,
prBookedYear, prBookedMonth, prCRMQuoteID, prLineItemNumber, prDraftReport, prInternalTargetDeadlineDate, prProtocolSignedDate,
prDataToRWS, prRWSWorkStarted, prFirstDraftToPL, prFirstDraftToQA, prArchivedDate, prToPLForQACommentReview,
prAnticipatedProjectArchiveDate, prToQAWithPLCommentResponse, prProjectReactivatedDate, prQAFinishDate, prSecondDraftReportToClient)
select *
from cube.Projects'
select #string
exec (#string)
set #string='select * from #Projects'
exec (#string)
this is the error that i get:
(44 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
Msg 208, Level 16, State 0, Line 2
Invalid object name '#Projects'.
Msg 208, Level 16, State 0, Line 1
Invalid object name '#Projects'.

Try to name the table with two ##, this will create a global temp table. It could be an issue with scoping, you might be creating the table with exec but it is not visible when it comes back.

When you call exec I believe it executes outside the context where your temp table was declared I believe if you appended your strings together and executed as one call to exec it would succeed. The other option is to use a global temp table with ## as the prefix instead of #.

Related

Have this problem with calling stored procedure unknown object type

I am trying to write a query that needs to call a stored procedure. But it always throws an error:
Unknown object type 'TABLEIXICHistoricalData' used in a CREATE, DROP, or ALTER statement.
This is query:
USE ETLCourse
DECLARE #LOOP TABLE
(
ID INT IDENTITY(1,1),
TableName NVARCHAR(100)
)
INSERT INTO #LOOP (TableName)
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE '%_Stocks%'
DECLARE #b INT = 1, #m INT, #t NVARCHAR(100)
SELECT #m = MAX(ID) FROM #LOOP
WHILE #b <= #m
BEGIN
SELECT #t = TableName
FROM #LOOP
WHERE ID = #b
EXECUTE [dbo].[stp_BuildNormalizedTable] #t
SET #b = #b + 1
END
and here is the procedure:
ALTER PROCEDURE [dbo].[stp_BuildNormalizedTable]
#table NVARCHAR(100)
AS
BEGIN
DECLARE #cleanTable NVARCHAR(100),
#s NVARCHAR(MAX)
SET #cleanTable = REPLACE(#table, '_Stocks', 'HistoricalData')
SET #s = 'CREATE TABLE' + #cleanTable + '(ID INT IDENTITY(1,1), Price DECIMAL(13, 4), PriceDate DATE)
INSERT INTO' + #cleanTable + '(Price,PriceDate) SELECT [Adj Close],[Date] FROM'
+ #table + ' ORDER BY Date ASC'
--PRINT #s
EXECUTE sp_executesql #s
END
It should copy two specific column and create a new table by using #Loop table and procedure
You need to add 'space' after 'create table' and 'insert into' and 'from'
declare #s nvarchar(max)
declare #cleantable nvarchar(100)
declare #table nvarchar(100)
set #cleantable = 'aaa'
set #table = 'bbb'
SET #s = 'CREATE TABLE' + #cleanTable + '(ID INT IDENTITY(1,1),Price Decimal(13,4),PriceDate DATE)
Insert into' + #cleanTable
+ '(Price,PriceDate) SELECT [Adj Close],[Date] FROM'
+ #table + ' ORDER BY Date ASC'
print #s
Output:
CREATE TABLEaaa(ID INT IDENTITY(1,1),Price Decimal(13,4),PriceDate DATE)
Insert intoaaa(Price,PriceDate) SELECT [Adj Close],[Date] FROMbbb ORDER BY Date ASC
Use 'print' to check your query.

Loop with dynamic table name and where clause stored procedure

I am trying to update records and insert their audits to audit table.
For this purpose stored procedure waiting for above variables.
#m_obj_id INT,
#m_obj_code NVARCHAR(250),
#m_f_code NVARCHAR(250),
#m_nv NVARCHAR(4000),
#m_last_mod_by INTEGER,
#table_name SYSNAME,
--#where_clause NVARCHAR(4000)
Stored procedure formatting these variables as;
UPDATE #table_name SET #m_f_code=#m_nv WHERE id=#m_obj_id
And at last part inserting into audit.
I can use it with doing SELECT CONCAT and copying all the rows then execute.
But my goal is here not expecting #m_obj_id from user and replace it #where_clause. And use this #where_clause to get ids inside.
So far I tried;
DECLARE #Sql NVARCHAR(MAX)
DECLARE #RecordId int = 0
BEGIN
SET #SQL = N'
SELECT #RecordId = MIN(id)
FROM ' + #table_name + '
WHERE id > #RecordId AND (' + #where_clause + ')
IF #RecordId IS NULL BREAK
SET #m_obj_id = #RecordId'
Exec sp_executesql #sql
But couldnt get far with it.
Then I tried something like;
DECLARE #RowsToProcess int
DECLARE #CurrentRow int
DECLARE #SelectCol1 int
DECLARE #sql NVARCHAR(MAX)
SET #sql = N'
DECLARE #table1 TABLE (RowID int not null primary key identity(1,1), col1 int )
INSERT into #table1 (col1) SELECT id FROM ' + #table_name + ' Where ' + #where_clause + '
SET #RowsToProcess=##ROWCOUNT'
EXEC sp_executesql #sql,
N'#RowsToProcess INT OUTPUT', #RowsToProcess OUTPUT
SET #CurrentRow=0
WHILE #CurrentRow<#RowsToProcess
BEGIN
SET #CurrentRow=#CurrentRow+1
DECLARE #sql2 NVARCHAR(MAX)
SET #sql2 = N'
SET #m_obj_id =
(SELECT col1
FROM #table1
WHERE RowID=#CurrentRow)'
EXEC sp_executesql #sql2
But still no luck.
Can I achieve this any how? I am trying to do this for more than it should be.
Thanks all.
The non-dynamic way to implement dynamic filtering on sql is the following:
where id=#m_obj_id or #m_obj_id is null
For a LOT of more details on how to choose between dynamic and non-dynamic sql on this, I recommend this article by Erland Sommarskog
I found a solution. Thanks everyone for responding.
I used a temp table like
DECLARE #RowsToProcess INTEGER
DECLARE #CurrentRow INTEGER
DECLARE #SelectCol1 INTEGER
CREATE TABLE #tmp (RowID INTEGER NOT NULL PRIMARY KEY IDENTITY(1,1), col1 int)
DECLARE #sql NVARCHAR(MAX)
SET #sql = N'
INSERT into #tmp (col1) SELECT id FROM ' + #table_name + ' Where ' + #where_clause + '
SET #RowsToProcess=##ROWCOUNT'
INSERT INTO #tmp
EXEC sp_executesql #sql,
N'#RowsToProcess INT OUTPUT', #RowsToProcess OUTPUT
SET #CurrentRow=0
WHILE #CurrentRow<#RowsToProcess
BEGIN
SET #CurrentRow=#CurrentRow+1
SET #m_obj_id =
(SELECT col1
FROM #tmp
WHERE RowID=#CurrentRow)
Do stuff....

To check whether the table exists or not dynamically

DECLARE #DROP VARCHAR(MAX)
DECLARE #TAB VARCHAR(MAX)='Employees'
DECLARE #CREATE VARCHAR(MAX)
DECLARE #SELECT VARCHAR(MAX)
--IF OBJECT_ID('DBO.'+#TAB,'U') IS NOT NULL
--BEGIN
IF (EXISTS (SELECT 1
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = #TAB))
BEGIN
SET #DROP = N'DROP TABLE '+#TAB
EXEC(#TAB)
END
SET #CREATE= 'CREATE TABLE '+#TAB+
'(
ID INT
,NAME VARCHAR(50)
,PHONE VARCHAR(25)
,ADDRESS VARCHAR(100)
)'
EXEC(#CREATE)
SET #SELECT='SELECT * FROM '+#TAB
EXEC(#SELECT)
I get an error, why?
Msg 2809, Level 16, State 1, Line 1
The request for procedure 'Employees' failed because 'Employees' is a table object.
You did only one mistake instead of using EXEC(#TAB) You should use EXEC(#DROP)
DECLARE #DROP VARCHAR(MAX)
DECLARE #TAB VARCHAR(MAX)='Employees'
DECLARE #CREATE VARCHAR(MAX)
DECLARE #SELECT VARCHAR(MAX)
--IF OBJECT_ID('DBO.'+#TAB,'U') IS NOT NULL
--BEGIN
IF
(EXISTS
(
SELECT
1
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = #TAB
)
)
BEGIN
SET #DROP=N'DROP TABLE '+#TAB
print #DROP
EXEC(#DROP)
END
SET #CREATE=
'CREATE TABLE '+#TAB+
'(
ID INT
,NAME VARCHAR(50)
,PHONE VARCHAR(25)
,ADDRESS VARCHAR(100)
)'
EXEC(#CREATE)
SET #SELECT='SELECT * FROM '+#TAB
EXEC(#SELECT)
From SQL Server 2016 SP1 onwards, objects can DIE - Drop If Exists. So, for your case, you can skip the existence check, and in the dynamic SQL to build only the following T-SQL statement:
...
'DROP TABLE IF EXISTS ' + #TAB + ';' +
'CREATE TABLE ' + #TAB +
...
Literally every object can DIE, you can get more details here:

How to write procedure to get all the data of table of tables?

I have a master table which contains the table names and columns corresponding to that table.
I want to write a procedure which iterates through all the records of tables and gets all the data and returns it as a single result set.
You need to use Dynamic Query
DECLARE #sql VARCHAR(max)=''
SET #sql = (SELECT #sql + 'select ' + column_name + ' from '
+ table_name + ' union all '
FROM master_table
FOR xml path(''))
SELECT #sql = LEFT(#sql, Len(#sql) - 9)
EXEC (#sql)
Note : The datatype of all the columns should be same. If it is not the case then you may have to do explicit conversion to varchar
SET #sql = (SELECT #sql + 'select cast(' + column_name + ' as varchar(4000)) from '
+ table_name
+ ' union all '
FROM Master_table
FOR xml path(''))
Assuming that all tables listed in your Master table is having same columns with same order and data types. Then it will be as follows:
create table ##a
(
Value int
)
create table ##b
(
Value int
)
create table ##c
(
Value int
)
declare #all table
(
Value int
)
declare #master table
(
TableName varchar(10)
)
declare #TableName varchar(10)
insert ##a values (1), (2), (3)
insert ##b values (4), (5), (6)
insert ##c values (7), (8), (9)
insert #master values ('##a'), ('##b'),('##c')
declare looper cursor local static forward_only read_only for
select TableName from #master
open looper
fetch next from looper into #TableName
while ##fetch_status = 0
begin
insert #all exec('select Value from ' + #TableName)
fetch next from looper into #TableName
end
close looper
deallocate looper
select * from #all
drop table ##a
drop table ##b
drop table ##c
If the tables are of different structures, please visit Stored procedures and multiple result sets in T-SQL. It will squeeze the content of each table into a single XML cell. The article also explains how to read them back.
I assume that you are using many tables with different columns in your master table. You should loop your master table. Try like this,
DECLARE #sql NVARCHAR(max) = ''
DECLARE #start INT = 1
,#end INT = 0
,#tablename VARCHAR(100) = ''
DECLARE #TableList TABLE (
id INT identity(1, 1)
,tablename VARCHAR(128)
)
INSERT INTO #TableList (tablename)
SELECT DISTINCT table_name
FROM YourMasterTableName
WHERE TABLE_NAME = 'productss'
SET #end = ##ROWCOUNT
WHILE (#start <= #end)
BEGIN
SET #tablename = (
SELECT tablename
FROM #TableList
WHERE id = #start
)
SET #sql = (
SELECT ',[' + column_name + ']'
FROM YourMasterTableName M
WHERE TABLE_NAME = #tablename
FOR XML path('')
)
SET #sql = 'SELECT ' + stuff(#sql, 1, 1, '') + ' FROM ' + #tablename
EXEC sp_executesql #sql
SET #start = #start + 1
END

SQL Server query to select all columns of a certain type and also show its max values

I am using SQL Server 2012.
The first part of my query is already answered in this thread. But I also want a second column that will show the corresponding maximum value of that column in its corresponding table.
I have tried this approach: use a function that takes in table name and column name as parameter and return the max value. But it is illegal to use dynamic SQL from a function. Moreover, i cannot seem to call a function from within a SELECT query.
I have also tried using stored procedure, but i cannot figure out how to call it and use it. Please suggest alternative ways to achieve this.
I am new to SQL Server.
Thanks
I think the easiest solution would be stored procedure. As far as I know:
Dynamic SQL can't be placed in functions
Dynamic SQL can't be place in OPENROWSET
I addition, if you write such procedure:
Beware of names containing spaces, qoutes (SQL injection possible)
MAX(column) on non-Indexed columns would require full scan (can be very slow)
Table and column names can be duplicated (placed in differend schemas)
Id duplicates and performance is not a problem, take a look at the following snippet:
CREATE PROC FindMaxColumnValues
#type sysname = '%',
#table sysname = '%'
AS
DECLARE #result TABLE (TableName sysname, ColumnName sysname, MaxValue NVARCHAR(MAX))
DECLARE #tab sysname
DECLARE #col sysname
DECLARE cur CURSOR FOR
SELECT TABLE_NAME TableName, COLUMN_NAME [Column Name]
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE LIKE #type and TABLE_NAME LIKE #table
OPEN cur
FETCH NEXT FROM cur INTO #tab, #col
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #sql nvarchar(MAX) = 'SELECT '+QUOTENAME(#tab,'''')+' [TableName], '+QUOTENAME(#col, '''')+' [ColumnName], MAX('+QUOTENAME(#col)+') FROM '+QUOTENAME(#tab)
INSERT INTO #result EXEC(#sql)
FETCH NEXT FROM cur INTO #tab, #col
END
CLOSE cur
DEALLOCATE cur
SELECT * FROM #result
Samples:
--MAX of INT's
EXEC FindMaxColumnValues 'INT'
--MAX of INT's in tables matching 'TestTab%'
EXEC FindMaxColumnValues 'INT', 'TestTab%'
--MAX of ALL columns
EXEC FindMaxColumnValues
Results:
TableName ColumnName MaxValue
IdNameTest ID 2
TestTable ID 5
TestTable Number 3
TableName ColumnName MaxValue
TestTable ID 5
TestTable Number 3
TableName ColumnName MaxValue
UpdateHistory UpdateTime 2016-07-14 12:21:37.00
IdNameTest ID 2
IdNameTest Name T2
TestTable ID 5
TestTable Name F
TestTable Number 3
You can use the below SP and enhance it per your Need,
CRETE PROCEDURE Getmaxtablecolval
AS
BEGIN
CREATE TABLE #t
(
tablename VARCHAR(50),
columnname VARCHAR(50),
id INT,
counts INT
)
INSERT INTO #t
SELECT table_name [Table Name],
column_name [Column Name],
NULL,
NULL
FROM information_schema.columns
WHERE data_type = 'INT'
BEGIN TRAN
DECLARE #id INT
SET #id = 0
UPDATE #t
SET #id = id = #id + 1
COMMIT TRAN
DECLARE #RowCount INT
SET #RowCount = (SELECT Count(0)
FROM #t)
DECLARE #I INT
SET #I = 1
DECLARE #Counter INT
DECLARE #TName VARCHAR(50)
DECLARE #CName VARCHAR(50)
DECLARE #DynamicSQL AS VARCHAR(500)
WHILE ( #I <= #RowCount )
BEGIN
SELECT #TName = tablename
FROM #t
WHERE id = #I
SELECT #CName = columnname
FROM #t
WHERE id = #I
SET #DynamicSQL = 'Update #T Set Counts = '
+ '(Select ISNull(Max(' + #CName + '), 0) From '
+ #TName + ') Where Id = '
+ CONVERT(VARCHAR(10), #I)
--PRINT #DynamicSQL
EXEC (#DynamicSQL)
SET #I = #I + 1
END
SELECT *
FROM #t
END
go
Getmaxtablecolval
You can create a procedure out of this:
CREATE PROCEDURE GET_COLUMNS_WITH_MAX_VALUE
#COLUMN_TYPE NVARCHAR(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- DUMMY VARIABLE TO COPY STRUCTURE TO TEMP
DECLARE #DUMMY TABLE
(
TABLE_NAME NVARCHAR(50),
COLUMN_NAME NVARCHAR(50),
MAX_VALUE NVARCHAR(MAX)
)
-- CREATE TEMP TABLE FOR DYNAMIC SQL
SELECT TOP 0 * INTO #TABLE FROM #DUMMY
INSERT INTO #TABLE
(TABLE_NAME, COLUMN_NAME)
SELECT TABLE_NAME, COLUMN_NAME
FROM information_schema.columns where data_type = #COLUMN_TYPE
DECLARE #TABLE_NAME VARCHAR(50) -- database name
DECLARE #COLUMN_NAME VARCHAR(256) -- path for backup files
DECLARE db_cursor CURSOR FOR
SELECT TABLE_NAME, COLUMN_NAME
FROM #TABLE
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #TABLE_NAME, #COLUMN_NAME
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #SQL NVARCHAR(MAX) = 'UPDATE #TABLE SET MAX_VALUE = (SELECT MAX([' + #COLUMN_NAME + ']) FROM [' + #TABLE_NAME + ']) '
+ 'WHERE [COLUMN_NAME] = ''' + #COLUMN_NAME + ''' AND TABLE_NAME = ''' + #TABLE_NAME + '''';
PRINT #SQL
EXEC (#SQL)
FETCH NEXT FROM db_cursor INTO #TABLE_NAME, #COLUMN_NAME
END
CLOSE db_cursor
DEALLOCATE db_cursor
SELECT * FROM #TABLE
DROP TABLE #TABLE
END
GO
Usage:
EXEC GET_COLUMNS_WITH_MAX_VALUE 'INT'
Results:
TABLE1 ID 50
TABLE2 ID 100
TABLE3 CarID 20
TABLE4 StudentID 30

Resources