Create SQL view with dynamic name and columns within Store Procedure - sql-server

I want to create an SQL table using a stored procedure with a dynamic name and dynamic columns. After creating the table in the stored procedure I want to create a view with that dynamic table name and dynamic columns within the stored procedure.
CREATEPROCEDURE [dbo].[Tmp_CloneOfDataset]
#Filter NVARCHAR(500),
#Value NVARCHAR(50)=NULL
AS
BEGIN
SET NOCOUNT ON
DECLARE #GetTemName varchar(200)= RTRIM(LTRIM( SUBSTRING(#Filter,CHARINDEX('into',#Filter)+4,CHARINDEX('from',#Filter)-CHARINDEX('into',#Filter)-4)))
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #GetTemName)
BEGIN
DECLARE #alreadyExist int =1;
return #alreadyExist
EXECUTE(#Filter)
END
ELSE
BEGIN
EXECUTE(#Filter)
// Here i want to create view after creating table
DECLARE #status varchar(200)='SELECT * from '+ #GetTemName
Execute(#status)
END
END

Related

Save dynamic result from stored procedure into a dynamic table

I am facing some issues in saving the execution of stored procedure / scalar function into a table variable.
The function / stored procedure returns dynamic columns and I need to create a dynamic table to save the result of that function into it so that I can use the table.
Example: the stored procedure spGetEmployeeInfo could return employee name, employee id, etc. on such criteria they return only employee name,.
Is there a way to create a dynamic table and save the result into it after execute the stored procedure, or any suggestion.
Thanks
I don't like to get into this situation too often, but when I do, what I do is have the stored proc output into a global temp table. The name of the table is passed in as a parameter by the user. For instance:
create procedure dynamicBeCareful
#toggle bit,
#globalTempTableName varchar(50)
as
-- initializations
if left(#globalTempTableName,2) <> '##'
throw 50000, '#globalTempTableName must start with ##', 1;
declare #sql varchar(max);
-- build dynamic sql
if #toggle = 1
set #sql = 'select * into #tempTable from table1';
else
set #sql = 'select * into #tempTable from table2';
set #sql = replace(#sql, '#tempTable', #globalTempTableName);
-- terminations
exec (#sql);
declare #msg = 'Your results are in ' + #globalTempTableName;
print (#msg);
Then use it like this:
exec dynamicBeCareful 1, '##temp';
select * from ##temp;
Beyond just being dynamic in output, it also can get you out of nested insert-exec limitations.

I am searching for a loop query over multiple databases and insert result into existing table in one database to collect al data

I am searching for a loop query over multiple databases and insert result into existing table in one database to collect al data.
There are 28 existing databases at the moment but when i start the query below it says table already exists at the second database.
when this works i want to loop a much larger query then this.
I also tried executing and union all but if a new database is added it must be collected autmatically.
See example i've tried below:
--drop table if exists [hulptabellen].dbo.HIdatabases
declare #dbList table (dbName varchar(128), indx int)
insert into #dbList
select dbName = dbname, row_number() over (order by dbname)
from [hulptabellen].dbo.HIdatabases
--declare variables for use in the while loop
declare #index int = 1
declare #totalDBs int = (select count(*) from #dbList)
declare #currentDB varchar(128)
declare #cmd varchar(300)
--define the command which will be used on each database.
declare #cmdTemplate varchar(300) =
'
use {dbName};
select * insert into [hulptabellen].dbo.cladrloc from {dbname}.dbo.cladrloc
'
--loop through each database and execute the command
while #index <= #totalDBs
begin
set #currentDB = (select dbName from #dbList where indx = #index)
set #cmd = replace(#cmdTemplate, '{dbName}', #currentDB)
execute(#cmd)
set #index += 1
end
Create the table outside your loop and insert into the table this way:
INSERT INTO [hulptabellen].dbo.cladrloc (col1,col2)
SELECT col1,col2
FROM {dbname}.dbo.cladrloc
FYI: When you use the following syntax, a new table is created, so it can be executed only once.
SELECT *
INTO [hulptabellen].dbo.cladrloc
FROM {dbname}.dbo.cladrloc

Attempting to change databases using cursor loop, but database won't change

I'm writing a script that, when executed, will drop any tables in a list of given DBs if the DB Name + Table Name combo does not exist in a table named CleanUpTableList. All DBs reside on the same server. I am using SQL Server 2014.
I am trying to do this by creating an outer cursor loop which cycles through the list of DB names and an inner cursor loop which pulls in a list of table names within the given database which are not found in CleanUpTableList and drops those tables. However, it seems that the outer loop fails to change databases. The script will only access the relevant tables of the starting database X times, with X being however many database name entries there are in the outer cursor. So, for example, if I start in Database1, and I have three database name entries in my outer cursor, instead of getting:
DROP TABLE Database1..TableB
DROP TABLE Database1..TableC
DROP TABLE Database2..TableE
DROP TABLE Database2..TableF
DROP TABLE Database3..TableH
DROP TABLE Database3..TableI
I get:
DROP TABLE Database1..TableB
DROP TABLE Database1..TableC
DROP TABLE Database1..TableB
DROP TABLE Database1..TableC
DROP TABLE Database1..TableB
DROP TABLE Database1..TableC
...Which is not really what I want, so I am assuming something is amiss in the outer loop. I know the usual DB change command is
USE Database1;
GO
But I wasn't able to figure out how to do that with EXEC(). It kept telling me there was a syntax error near GO, I assume because GO can't be on the same line as 'USE Database1;', and I don't know how make a new line when using EXEC(). I tried using
SET #ChangeDB = 'USE ' + #DatabaseName + ';'
EXEC(#ChangeDB + CHAR(13) + 'GO')
and
SET #ChangeDB ='USE ' + #DatabaseName + ';' +CHAR(13) + 'GO'
EXEC(#ChangeDB)
but these also returned a syntax error.
Here is the relevant code:
DB/Table Creation Script:
CREATE DATABASE Database1;
CREATE DATABASE Database2;
CREATE DATABASE Database3;
CREATE DATABASE Database4;
CREATE TABLE Database1.dbo.TableA (Column1 INT, Column2 INT);
CREATE TABLE Database1.dbo.TableB (Column1 INT, Column2 INT);
CREATE TABLE Database1.dbo.TableC (Column1 INT, Column2 INT);
CREATE TABLE Database2.dbo.TableD (Column1 INT, Column2 INT);
CREATE TABLE Database2.dbo.TableE (Column1 INT, Column2 INT);
CREATE TABLE Database2.dbo.TableF (Column1 INT, Column2 INT);
CREATE TABLE Database3.dbo.TableG (Column1 INT, Column2 INT);
CREATE TABLE Database3.dbo.TableH (Column1 INT, Column2 INT);
CREATE TABLE Database3.dbo.TableI (Column1 INT, Column2 INT);
CREATE TABLE Database4.dbo.CleanUpTableList (DBName VARCHAR(20), TableName VARCHAR(20));
INSERT INTO Database4..CleanUpTableList VALUES ('Database1','TableA')
INSERT INTO Database4..CleanUpTableList VALUES ('Database2','TableD')
INSERT INTO Database4..CleanUpTableList VALUES ('Database3', 'TableG')
Clean Up Script:
DECLARE #fetch_database_cursor INT
DECLARE #DatabaseName VARCHAR(50)
DECLARE DatabaseList CURSOR FOR
select name from sys.databases
where
name IN ('Database1','Database2', 'Database3'
)
OPEN DatabaseList
FETCH NEXT FROM DatabaseList INTO #DatabaseName
/* Keep track of the outer loop FETCH_STATUS in a local variable */
SET #fetch_database_cursor = ##FETCH_STATUS
/* Use outer loop FETCH_STATUS local variable as condition for outer WHILE loop */
WHILE #fetch_database_cursor = 0
BEGIN
DECLARE #ChangeDB VARCHAR(2500)
DECLARE #TableName VARCHAR(50)
DECLARE #ExecuteSQL VARCHAR(2500)
DECLARE #fetch_table_cursor INT
/* Change DB here */
SET #ChangeDB = 'USE ' + #DatabaseName
EXEC(#ChangeDB)
/* Declare inner cursor */
DECLARE TableList CURSOR FOR
select table_name
from information_schema.tables
WHERE TABLE_TYPE = 'BASE TABLE'
AND table_name NOT IN (
SELECT TableName
FROM Database4..CleanUpTableList
WHERE DBName = #DatabaseName
)
ORDER BY table_name
OPEN TableList
FETCH NEXT FROM TableList INTO #TableName
/* Store inner cursor fetch_status in local variable */
SET #fetch_table_cursor = ##FETCH_STATUS
/* Use inner cursor fetch_status local variable as condition for inner WHILE loop */
WHILE #fetch_table_cursor = 0
BEGIN
SET #ExecuteSQL = 'DROP TABLE ' +#Tablename
EXEC(#ExecuteSQL)
SELECT #Tablename, 'Has Been Successfully Dropped'
FETCH NEXT FROM TableList INTO #TableName
SET #fetch_table_cursor=##FETCH_STATUS
END
/* Close and deallocate inner cursor */
CLOSE TableList
DEALLOCATE TableList
FETCH NEXT FROM DatabaseList INTO #DatabaseName
SET #fetch_database_cursor = ##FETCH_STATUS
END
/* Close and deallocate outer cursor */
CLOSE DatabaseList
DEALLOCATE DatabaseList
Any suggestions are appreciated.
From Your code ,i understood that you are trying to do same operation in all databases,that can be implemented by sp_msforeachdb..
--all databases
EXECUTE master.sys.sp_MSforeachdb
'USE [?];
if db_id()<=4 return;
drop table dbo.sometable'
--run only few databases..
EXECUTE master.sys.sp_MSforeachdb
'USE [?];
if db_name(db_id()) in (''master'',''tempdb'') --your dbnames
Begin
select db_name() --your query
end'
You could also use Aaron Bertrand's rewrite of Sp_msforeachDB which also can deal with some limitations of Sp_msforeachdb :Making a more reliable and flexible sp_MSforeachdb
Instead of trying to execute the Use database statement, try fully qualifying the database.dbo.tablename in the drop statement. You have all of the database and table names.

What is the best way to toggle user defined column name or default column name in stored procedure

What is the best approach for stored procedure to toggle between user defined column names or default column name
Here is what I have done so far. This is fine for small query, is there a better way of doing this for larger query.
-- Drop stored procedure if it already exists
IF EXISTS(SELECT * FROM sys.procedures
WHERE schema_id = schema_id('dbo')
AND name = N'sp_test')
DROP PROCEDURE dbo.sp_test
GO
CREATE PROCEDURE [dbo].[sp_test]
-- /* Declare parameters */
#columnName BIT =0
AS
BEGIN
-- SELECT statement to fetch record
IF(#columnName =1)
(
SELECT
TOP 100
IM.INC_REF,
IM.ID
FROM
dbo.TEST AS IM
)
ELSE
(
SELECT
TOP 100
IM.INC_REF AS REF,
IM.ID AS ID
FROM
dbo.TEST AS IM
)
END
GO
-- ============================================
-- Execute stored procedure
-- ============================================
DECLARE #columnName AS BIT
SET #columnName =0
EXEC [dbo].[sp_test] #columnName
Thanks in advance
When we need different results from a stored procedure based on a parameter, we'll call one of two "sub" stored procedures. In your case, that would be:
CREATE PROCEDURE [dbo].[sp_test]
-- /* Declare parameters */
#columnName BIT = 0
AS
BEGIN
IF ( #columnName = 1 )
EXEC dbo.[sp_test1]
ELSE
EXEC dbo.[sp_test2]
END
GO
CREATE PROCEDURE dbo.[sp_test1]
AS
BEGIN
SELECT TOP 100
IM.INC_REF ,
IM.ID
FROM dbo.TEST AS IM
END
GO
CREATE PROCEDURE dbo.[sp_test2]
AS
BEGIN
SELECT TOP 100
IM.INC_REF AS REF ,
IM.ID AS ID
FROM dbo.TEST AS IM
END
GO
I've found this gets around the issue of badly cached plans.

INSERT INTO with exec with multiple result sets

SQL Server allows me to insert the returned result set of a stored procedure as:
DECLARE #T TABLE (
ID int,
Name varchar(255),
Amount money)
INSERT INTO #T
exec dbo.pVendorBalance
This works as long as the stored procedure only returns 1 result set.
Is there a way to make this work if the stored procedure returns several result sets?
E.g.
DECLARE #T1 (...)
DECLARE #T2 (...)
INSERT INTO #T1 THEN INTO #T2
exec dbo.pVendorBalance
One workaround to this problem is using OUTPUT parameters (JSON/XML) instead of resultsets.
CREATE TABLE tab1(ID INT, Name NVARCHAR(10), Amount MONEY);
INSERT INTO tab1(ID, Name, Amount)
VALUES (1, 'Alexander', 10),(2, 'Jimmy', 100), (6, 'Billy', 20);
CREATE PROCEDURE dbo.pVendorBalance
AS
BEGIN
-- first resultset
SELECT * FROM tab1 WHERE ID <=2;
-- second resultset
SELECT * FROM tab1 WHERE ID > 5;
END;
Version with OUT params:
CREATE PROCEDURE dbo.pVendorBalance2
#resultSet1 NVARCHAR(MAX) OUT,
#resultSet2 NVARCHAR(MAX) OUT
AS
BEGIN
SELECT #resultSet1 = (SELECT * FROM tab1 WHERE ID <=2 FOR JSON AUTO),
#resultSet2 = (SELECT * FROM tab1 WHERE ID > 5 FOR JSON AUTO);
END;
And final call:
DECLARE #r1 NVARCHAR(MAX), #r2 NVARCHAR(MAX);
EXEC dbo.pVendorBalance2 #r1 OUT, #r2 OUT;
-- first resultset as table
SELECT *
INTO #t1
FROM OpenJson(#r1)
WITH (ID int '$.ID', [Name] NVARCHAR(50) '$.Name',Amount money '$.Amount');
-- second resultset as table
SELECT *
INTO #t2
FROM OpenJson(#r2)
WITH (ID int '$.ID', [Name] NVARCHAR(50) '$.Name',Amount money '$.Amount');
SELECT * FROM #t1;
SELECT * FROM #t2;
DBFiddle Demo
EDIT:
Second approach is to use tSQLt.ResultSetFilter CLR function (part of tSQLt testing framework):
The ResultSetFilter procedure provides the ability to retrieve a single result set from a statement which produces multiple result sets.
CREATE TABLE #DatabaseSize (
database_name nvarchar(128),
database_size varchar(18),
unallocated_space varchar(18)
);
CREATE TABLE #ReservedSpaceUsed (
reserved VARCHAR(18),
data VARCHAR(18),
index_size VARCHAR(18),
unused VARCHAR(18)
);
INSERT INTO #DatabaseSize
EXEC tSQLt.ResultSetFilter 1, 'EXEC sp_spaceused';
INSERT INTO #ReservedSpaceUsed
EXEC tSQLt.ResultSetFilter 2, 'EXEC sp_spaceused';
SELECT * FROM #DatabaseSize;
SELECT * FROM #ReservedSpaceUsed;
No. But there is more of a work around since you cannot do an insert into with a procedure that returns multiple results with a different number of columns.
If you are allowed to modify the stored procedure, then you can declare temp tables outside of the procedure and populate them within the stored procedure. Then you can do whatever you need with them outside of the stored procedure.
CREATE TABLE #result1(Each column followed by data type of first result.);
----Example: CREATE TABLE #result1(Column1 int, Column2 varchar(10))
CREATE TABLE #result2(Each column followed by data type of second result.);
EXEC pVendorBalance;
SELECT * FROM #result1;
SELECT * FROM #result2;
I had a similar requirement, and ended up using the a CLR function which you can read about here (it's the answer with the InsertResultSetsToTables method, by user Dan Guzman):
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/da5328a7-5dab-44b3-b2b1-4a8d6d7798b2/insert-into-table-one-or-multiple-result-sets-from-stored-procedure?forum=transactsql
You need to create a SQL Server CLR project in Visual Studio to get going. I had a project already written by a co-worker that I could just expand, but if you're starting from scratch, try reading this guide:
http://www.emoreau.com/Entries/Articles/2015/04/SQL-CLR-Integration-in-2015-year-not-product-version.aspx
If you've succeeded in writing and publishing the CLR project to the database, here is an example of using it I wrote:
-- declare a string with the SQL you want to execute (typically an SP call that returns multiple result sets)
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'exec usp_SomeProcedure #variable1 = ' + #variable1 + '...' -- piece together a long SQL string from various parameters
-- create temp tables (one per result set) to hold the output; could also be actual tables (non-temp) if you want
CREATE TABLE #results_1(
[CustomerId] INT, [Name] varchar(500), [Address] varchar(500)
);
CREATE TABLE #results_2(
[SomeId] UNIQUEIDENTIFIER, [SomeData] INT, [SomethingElse] DateTime
);
-- on the exemplary 'CustomerDatabase' database, there is an SP (created automatically by the SQL CLR project deployment process in Visual Studio) which performs the actual call to the .NET assembly, and executes the .NET code
-- the CLR stored procedure CLR_InsertResultSetsToTables executes the SQL defined in the parameter #sourceQuery, and outputs multiple result sets into the specified list of tables (#targetTableList)
EXEC CustomerDatabase.dbo.CLR_InsertResultSetsToTables #sourceQuery = #sql, #targetTableList = N'#results_1,#results_2';
-- The output of the SP called in #sql is now dumped in the two temp tables and can be used for whatever in regular SQL
SELECT * FROM #results_1;
SELECT * FROM #results_2;
We can do it in the following way
Consider the input SP (which returns 2 tables as output) as usp_SourceData
Alter the usp_SourceData to accept a parameter as 1 and 2
Adjust the SP in a way that when
usp_SourceData '1' is executed it will return first table
and when
usp_SourceData '2' is executed it will return second table.
Actually stored procedures can return multiple result sets, or no result sets, it's pretty arbitrary. Because of this, I don't know of any way to navigate those results from other SQL code calling a stored procedure.
However, you CAN use the returned result set from a table-valued user defined function. It's just like a regular UDF, but instead of returning a scalar value you return a query result. Then you can use that UDF like any other table.
INSERT INTO #T SELECT * FROM dbp.pVendorBalanceUDF()
http://technet.microsoft.com/en-us/library/ms191165(v=sql.105).aspx
DROP TABLE ##Temp
DECLARE #dtmFrom VARCHAR(60) = '2020-12-01 00:00:00', #dtmTo VARCHAR(60) = '2020-12-02 23:59:59.997',#numAdmDscTransID VARCHAR(60) =247054
declare #procname nvarchar(255) = 'spGetCashUnpaidBills',
#procWithParam nvarchar(255) = '[dbo].[spGetCashUnpaidBills] #dtmFromDate= ''' +#dtmFrom+ ''' ,#dtmToDate= ''' +#dtmTo+''',#numCompanyID=1,#numAdmDscTransID='+ #numAdmDscTransID +',#tnyShowIPCashSchemeBills=1',
#sql nvarchar(max),
#tableName Varchar(60) = 'Temp'
set #sql = 'create table ##' + #tableName + ' ('
begin
select #sql = #sql + '[' + r.name + '] ' + r.system_type_name + ','
from sys.procedures AS p
cross apply sys.dm_exec_describe_first_result_set_for_object(p.object_id, 0) AS r
where p.name = #procname
set #sql = substring(#sql,1,len(#sql)-1) + ')'
execute (#sql)
execute('insert ##' + #tableName + ' exec ' + #procWithParam)
end
SELECT *FROM ##Temp
If the both result sets have same number of columns then
insert into #T1 exec dbo.pVendorBalance
will insert the union of both data set into #T1.
If not
Then edit dbo.pVendorBalance and insert results into temporary tables and in outer stored proc, select from those temporary tables.
Another way(If you need it), you can try
SELECT * into #temp
from OPENROWSET('SQLNCLI', 'Server=(local)\\(instance);Trusted_Connection=yes;',
'EXEC dbo.pVendorBalance')
it will take first dataset.

Resources