How to Create a table on variable name in SQL Server? - sql

WHILE #i < #deptcount + 1
BEGIN
--creating dynamic tables
DECLARE #tablenames NVARCHAR(50)
SET #tablenames = 'dept' + Cast(#i AS NVARCHAR)
EXECUTE ('create table '+#tablenames+
' (deptno int, formno int, stdpr int, agg int)')
SET #i = #i + 1
END

Your code seems to work:
DECLARE #i INT = 0, #deptcount INT = 4;
while #i < #deptcount+1
Begin
--creating dynamic tables
declare #tablenames nvarchar(50)
set #tablenames = '##dept'+CAST(#i as nvarchar)
execute('create table '+#tablenames+' (deptno int, formno int, stdpr int, agg int)')
set #i = #i +1
End
SELECT *
FROM ##dept1
UNION ALL
SELECT *
FROM ##dept2
UNION ALL
SELECT *
FROM ##dept3;
LiveDemo
But reconsider your approach:
CREATE TABLE #tbl
The desire here is to create a table of which the name is determined
at run-time.
If we just look at the arguments against using dynamic SQL in stored
procedures, few of them are really applicable here. If a stored
procedure has a static CREATE TABLE in it, the user who runs the
procedure must have permissions to create tables, so dynamic SQL will
not change anything. Plan caching obviously has nothing to do with it.
Etc.
Nevertheless: Why? Why would you want to do this? If you are creating
tables on the fly in your application, you have missed some
fundamentals about database design. In a relational database, the set
of tables and columns are supposed to be constant. They may change
with the installation of new versions, but not during run-time.
Sometimes when people are doing this, it appears that they want to
construct unique names for temporary tables. This is completely
unnecessary, as this is a built-in feature in SQL Server. If you say:
CREATE TABLE #nisse (a int NOT NULL)
then the actual name behind the scenes will be something much longer,
and no other connections will be able to see this instance of #nisse.

Related

Creating a stored procedure using variables

Is there any good way to do this, or am I just heading in the wrong direction? I would like to create a stored procedure inside an SQL script. I would like to have variables declared at the beginning of the script so that I can create the SPROCs to use in different contexts/servers.Here is what I would like to do (I know this obviously doesn't work, but I'm looking for any ideas of an alternative)..
DECLARE #golbalValue = 'SomeValue'
GO
CREATE PROCEDURE [dbo].[MyStoredProcedure](
AS
BEGIN
SELECT * FROM Mytable WHERE MyCol = #globalValue
END
GO
What you could do is use a scalar function for the variable
create function f ()
returns varchar(20)
as
begin
return 'some value'
end
go
then use it in your procedure
create proc p ()
as
begin
select *
from my_table
where col = f()
end
go
another possibility which is perhaps more appropriate is to use sqlcmd here's an example.
From what I understand, you need to create stored procedures with set value from your parameters. You don't want input parameters in the stored Procedures though. Second, you want to switch database contexts. So I think you'll need a tempTable for your parameters and some dynamic SQL. Try this out:
IF OBJECT_ID('tempdb..#globalParam') IS NOT NULL
DROP TABLE #globalParam;
IF OBJECT_ID('AdventureWorks2012.dbo.myTable') IS NOT NULL
DROP TABLE AdventureWorks2012.dbo.myTable
IF OBJECT_ID('Master..myTable') IS NOT NULL
DROP TABLE Master..mytable
--Create your data tables
SELECT 'SomeValue' AS col1 INTO AdventureWorks2012.dbo.myTable;
SELECT 1000 AS col1 INTO master.dbo.myTable;
CREATE TABLE #globalParam(
ParamName VARCHAR(100),
val SQL_VARIANT --SQL_Variant is designed to hold all data types.
);
--Here are your globalParams
DECLARE #globalParam1 VARCHAR(100) = 'SomeValue';
DECLARE #globalParam2 INT = 1000;
--Load your parameters into a table. Might have to cast some of your parameters to SQL_Variant
INSERT INTO #globalParam
VALUES ('globalParam1',#globalParam1),
('globalParam2',CAST(#globalParam2 AS sql_variant));
GO
--Switch database context
USE AdventureWorks2012
GO
--Variable to hold CREATE PROC
DECLARE #sql VARCHAR(MAX);
--Set #SQL with parameter value from #globalParam
SELECT #sql =
'CREATE PROCEDURE dbo.myStoredProc AS
BEGIN
SELECT * FROM myTable WHERE col1 = ''' + CAST(val AS VARCHAR(100)) + '''
END'
FROM #globalParam
WHERE ParamName = 'globalParam1'
--Execute to create the stored procedure
EXEC(#sql)
--Execute it to see if it works
EXEC dbo.myStoredProc
--Switch context. Repeat same steps
USE master
GO
DECLARE #sql VARCHAR(MAX);
SELECT #sql =
'CREATE PROCEDURE dbo.myStoredProc AS
BEGIN
SELECT * FROM myTable WHERE col1 = ''' + CAST(val AS VARCHAR(100)) + '''
END'
FROM #globalParam
WHERE ParamName = 'globalParam2'
EXEC(#sql)
EXEC dbo.myStoredProc
--Cleanup
DROP PROCEDURE dbo.myStoredProc;
USE AdventureWorks2012
GO
DROP PROCEDURE dbo.myStoredProc;
You cannot do what you want. T-SQL doesn't have the concept of global variables. One method is to store values in a "global" table and then reference them as needed. Something like:
create table GlobalParams (
name varchar(255) not null primary key,
value varchar(255) not null
);
create procedure . . .
begin
. . .
declare #value varchar(255);
select #value = value from Globalparams where name = 'name';
select *
from Mytable
where MyCol = #value;
. . .
end;
Note: this is a simplistic example that only allows variables whose type is a string.
You can also wrap the logic in a user-defined function, so the call looks like:
select *
from Mytable
where MyCol = udf_GlobalLookup('name');
It is rather rare to need global parameters that are shared among different stored procedures. Such a global context can be useful, at times, for complex systems. It is unlikely that you need all this machinery for a simple application. An alternative method, such as just passing the parameters in as arguments, is probably sufficient.

How to use variables when using more than one "use db"

I have a SQL script that I've been working on making more flexible. I need it to be able to insert rows into tables in one DB then create a table and insert rows in another DB on the same server. I can each of these things in two different scripts of course, but I'm trying to figure out how to do it all in one. Here is an example of what I've tried:
use firmTriad
create table #TempTable (IssueId nvarchar(300),[SQL] nvarchar(300),FirmID varchar(255),[Table] varchar(255),SecurityDelete varchar(255)
,SecurityKeep varchar(255))
declare #IssueID nvarchar(300),#SQL nvarchar(300),#FirmID varchar(255),#SecurityCUSIPdelete varchar(255),#SecurityCUSIPkeep varchar(255)
set #SecurityCUSIPkeep = 'AET133A20'
set #SecurityCUSIPdelete = 'AEXVA0559'
set #FirmID = 'firmTriad'
set #IssueID = 'RQST00002652426'
set #IssueID = 'tblSecurity_MSReference_' + #IssueID
insert into #TempTable (IssueId,FirmID,SecurityDelete,SecurityKeep)
select #IssueID,#FirmID,#SecurityCUSIPdelete,#SecurityCUSIPkeep
use PSBackup
select * from #TempTable
declare #Table varchar(255), #PSTable varchar(255)
set #PSTable = 'tblSecurity_MSReference_' + #IssueID
set #Table = #FirmID + '.dbo.' + left(#Table,len(#Table)-16)
print (#FirmID)
set #SQL = 'create table ' + #IssueID + '([RecID],[SecurityID],[MSCUSIP],[LastUpdateDT]
,Issue_Key,Issue_Backup_Date,Issue_Backup_Len)'
exec #SQL
insert into sys. (#firmID + '.dbo.tblSecurity_MSReference') --firmTriad.dbo.tblSecurity_MSReference
select [RecID],[SecurityID],[MSCUSIP],[LastUpdateDT],Issue_Key,Issue_Backup_Date,Issue_Backup_Len
from #Table
where [SecurityID] in(select RecID from firmTriad.dbo.tblSecurity where CUSIP in(#SecurityCUSIPdelete,#SecurityCUSIPkeep))
I can't figure out how to get the variables to carry over between the two use db sections.
I see you are creating a materialized temp table (#TempTable) and then doing some SELECT queries and then creating a new table and inserting some data there.
As I understand, your problem is with cross-database variables, right?
Does it help you to know, that this works:
create table PSBackup.dbo.TheNameOfYourTable(
IssueId nvarchar(300),
[SQL] nvarchar(300),
FirmID varchar(255),
[Table] varchar(255),
SecurityDelete varchar(255),
SecurityKeep varchar(255)
);
You do not need to "use PSBackup" in order to create a table or basically do anything in that database. The generic "formula" is:
SELECT * FROM <dbname>.<schemaname>.<tablename>
EXEC <dbname>.<schemaname>.<StoredProcedureName>
The statement "USE PSBackup" changes the context in which you are working in. If you are creating a table and dont provide dbname and schemaname, the SQL server just uses the defaults (which are master and dbo). And of course, if you change the context, the variables are discarded. Think in terms of transactions and it will become clear as day.
The other, non-related thing is, you really shouldn't use reserved names for column names. This can cause all sorts of problems in the future...Try changing the column names:
"[SQL]" to "Query"
"[Table]" to "TBL" or "TableName"
Does this help?

Dynamic change schema in SQL procedure

I have a database with multiple schemas. In every schema I got table called [Logs], so my database tables looks like:
[s1].[Logs]
[s2].[Logs]
[s3].[Logs]
...
[sN].[Logs]
Every day I would like to run stored procedure, which will do same operations on every above table. Is there a way to pass schema name into stored procedure? I am using SQL on Azure.
No, it is not - unless the SP Uses then dynamic SQL to execute some SQL String you constructed in the SP.
This happens via the sp_executesql stored procedure
http://technet.microsoft.com/en-us/library/ms188001.aspx
has more information.
Microsoft has a few undocumented procedures that perform "foreach" operations on tables (sp_msforeachtable) and databases (sp_msforeachdb). Both of these rely on another undocumented proc called sp_msforeachworker which you might be able to exploit to create a foreachschema type of routine. Theres an article (reg required) here that demonstrates this approach.
That said, its unlikely Azure supports anything of these, so you might have to fashion your own using a crude loop:
declare #schemas table (i int identity(1,1), name sysname);
insert into #schemas
select name from sys.schemas where name like 's[0-9]%';
declare #i int, #name sysname, #cmd nvarchar(max);
select #i = min(i) from #schemas;
while #i is not null
begin
select #name = name from #schemas where i = #i;
set #cmd = replace(N'select count(*) from [{0}].[Logs];', '{0}', #name);
print #cmd;
--exec(#cmd);
select #i = min(i) from #schemas where i > #i;
end

How can I spot in what database is a stored procedure with name 'myStoredProcedure'?

There are bunch of databases to the SQL server I am connected.
How should I query the sysobjects in order to spot in what database a stored procedure with name 'myStoredProcedure' is located ?
The query should return the database name.
Thanks
I know you are not asking for this, but I'd really download RedGate's Sql Search add-in for SSMS and use that. It allows you to find any object (proc, table, view, column, etc) on any database easily.
And it's free!
I'd give this a try:
CREATE TABLE ##DatabaseList
(
DatabaseName varchar(50)
)
EXECUTE SP_MSForEachDB 'USE [?]; INSERT INTO ##DatabaseList SELECT DB_NAME() FROM [sys].[objects] WHERE name = "MyStoredProcedure" AND type_desc = "SQL_STORED_PROCEDURE"'
SELECT * FROM ##DatabaseList
DROP TABLE ##DatabaseList
That's using the undocumented/ unsupported system stored procedure SP_MSForEachDb and writing any hits to a global temp table, then outputting the contents to the Results window before dropping the table. If you just need to know which database (or databases - there may of course be more than one) has an appropriately named SP, this should do it. If you want to use the output elsewhere as a parameter, it may take a little more work.
By the way, I'm only learning this stuff myself over the last few months so if anyone can critique the above and suggest a better way to go at it I'm happy to receive feedback. Equally, I can answer any further questions posted here to the best of my ability.
Cheers
So out of curiosity I decided to try write this myself, especially since ADG mentioned his solution was using an unsupported, undocumented procedure. This could also be expanded to take a 2nd parameter so where it checks the type = P (stored Proc) you could probably change it to look for other things like views / tables etc.
My solution is a bit long but here goes:
CREATE PROCEDURE spFindProceduresInDatabases
(
#ProcedureName NVARCHAR(99)
)
AS
BEGIN
-- Get all the database names and put them into a table
DECLARE #Db TABLE (DatabaseName Varchar(99))
INSERT INTO #Db SELECT name FROM Sys.databases
-- Declare a table to hold our results
DECLARE #results TABLE (DatabaseName VARCHAR(99))
-- Make a Loop
-- Declare a variable to be incremented
DECLARE #count INT
SET #count = 0
-- Declare the end condition
DECLARE #endCount INT
SELECT #endCount = COUNT(*) FROM #Db
-- Loop through the databases
WHILE (#count < #endCount )
BEGIN
-- Get the database we are going to look into
DECLARE #dbWeAreChecking VARCHAR(99)
SELECT TOP 1 #dbWeAreChecking = DatabaseName FROM #Db
DELETE FROM #Db WHERE DatabaseName = #dbWeAreChecking
-- Create and execute our query
DECLARE #Query NVARCHAR(3000)
SET #Query = N'SELECT #outParam = COUNT(*) FROM '+#dbWeAreChecking+'.sys.sysobjects WHERE type = ''P'' and name = #ProcedureName'
Declare #outParam INT
print (#Query)
DECLARE #ParmDefinition NVARCHAR(500)
DECLARE #IntVariable INT
SET #ParmDefinition = N'#ProcedureName VARCHAR(99),#outParam INT OUTPUT'
SET #IntVariable = 35
EXECUTE sp_executesql
#Query ,
#ParmDefinition,
#ProcedureName,
#outParam = #outParam OUTPUT
-- If we have a result insert it into the results table
If (#outParam > 0)
BEGIN
INSERT INTO #results(DatabaseName) VALUES(#dbWeAreChecking)
END
-- Increment the counter
SET #count = (#count + 1)
END
-- SELECT ALL OF THE THINGS!!!
SELECT * FROM #results
END

SQL Server Multi-Step Stored Procedure

I've got a software suite that is based off of multiple libraries where:
1 library = 1 SQL Database.
Different users can have different access to different libraries.
In addition, the databases are named in a specific manner to help identify which are "mine" and which aren't.
I'd like to create a stored procedure that takes a variable called #UserName and returns the databases that have a name starting with MYDB, where #UserName is found in a table USERS.
I'm figuring that I'll start with EXEC sp_databases, but I'm unsure how to continue.
What I need to know is:
How do I iterate the results of sp_databases to pull out just the databases that have a name matching my pattern?
How do I then check for #UserName in the [USER NAME] column of the USERS table of each database returned from #1?
I'm guessing it has something to do with temp tables and cursors, but I'm not really sure where to start.
Any help?
Thanks!
Here is some proof of concept code to show you an approach. sys.databases contains a more accessible list of databases. You'll pretty much have to use dynamic sql at some point though.
CREATE PROCEDURE MyDBs #userName VARCHAR(255)
AS
BEGIN
DECLARE #max INT
DECLARE #i INT
DECLARE #sql VARCHAR(500)
CREATE TABLE #SQL
(
rid int identity primary key clustered,
query varchar(500)
)
INSERT INTO #SQL(query)
SELECT 'SELECT * FROM ['+ name '+].USERS WHERE username = #UserName'
FROM master.sys.databases
WHERE NAME LIKE '%yourpattern%'
SELECT #max = ##rowcount, #i = 1
WHILE #i <= #max
BEGIN
SELECT #sql = query FROM #sql WHERE rid = #i
EXEC #sql
SET #i = #i + 1
END
DROP TABLE #SQL
For 1, just look at the sp_databases code, copy it and modify it to your needs. For Example (see last 2 conditions of where clause. This is the actual code of the sp_databases stored proc. You can look at it on the master db):
declare #UserName varchar(50)='someuser'
select
DATABASE_NAME = db_name(s_mf.database_id),
DATABASE_SIZE = convert(int,
case -- more than 2TB(maxint) worth of pages (by 8K each) can not fit an int...
when convert(bigint, sum(s_mf.size)) >= 268435456
then null
else sum(s_mf.size)*8 -- Convert from 8192 byte pages to Kb
end),
REMARKS = convert(varchar(254),null)
from
sys.master_files s_mf
where
s_mf.state = 0 and -- ONLINE
has_dbaccess(db_name(s_mf.database_id)) = 1 and
--db_name(s_mf.database_id) like '%'+#UserName+'%' and exists -- you may or may not want to leave this condition here. You'll figure out what condition to use
(select 1 from databasename.dbo.Users where [UserName]=#UserName)
group by s_mf.database_id
order by 1