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?
Related
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.
In my SQL database, I would like to make a general purpose soft link table. Perhaps something similar the following:
create table soft_link(
id uniqueidentifier,
name varchar(255),
LINK uniqueidentifier,
TABLE varchar(255),
primary key(id));
Say I have object "b_object" in some other table in the database. The LINK column would be the the unique identifier of b_object, and TABLE would be the table in which b_object is stored within the database.
Now I want to make a single stored procedure "sp_ResolveSoftLink". This method will take an id of a soft link, look up the LINK and TABLE columns of the soft link, and then use the TABLE and LINK to query for b_object.
The following is NOT proper SQL syntax, but hopefully it helps illustrate my question. I want to combine these two queries into a single stored procedure and return the result of the second query:
select LINK, TABLE from soft_link where id = xxxxxx
select * from TABLE where id = LINK
-- return the result of the second select query
FUNDAMENTAL QUESTION: How/can I use the varchar return from one query to form another query in the same stored procedure in SQL?
You can create a Stored procedure like this.
Here, First you have to store value of LINK and Table in variable and then use these variable to form another Dynamic Query
CREATE PROC spName
#id INT
AS
BEGIN
Declare #LINKValue uniqueidentifier, #TABLE varchar(255),#SQL varchar(max)
SELECT #LINKValue = LINK , #TABLE = TABLE
FROM soft_link
WHERE soft_link id = #id
SET #SQL='SELECT * FROM';
SET #SQL = #SQL +' ' + #TABLE
SET #SQL = #SQL +' ' + 'WHERE ID =' +' ' + #LINKValue
PRINT #SQL -- SEE Here This is what you actually Want
--EXEC (#SQL) --Then Execute this
END
Thank you for helping me out through my previous tides.. I am currently working on SQL Server 2008 for one of my project, a part of which needs to use 22 columns for a set of similar operations.
The column names only differ by the number, e.g.
C1_1,C1_2,c1_3
Is there any way to loop through the column names? I tried out the following code, but it throws out an error
DECLARE #i INT
SET #i=2
while (#i<=22)
begin
SELECT [DEF].[CONCATENATE], SUM(DEF.[C1_+#i+_PREV]) as
[C1_#i_prev]
INTO #TMP_C1_#i_CONCATENATE_PREV
FROM DEF
GROUP BY DEF.[CONCATENATE]
SELECT [ABC].[CONCATENATE], SUM(ABC.[C1_#i_CURR]) as
[c1_#i_curr]
INTO #TMP_C1_#i_CONCATENATE_CURR
FROM ABC
GROUP BY ABC.[CONCATENATE]
UPDATE #tmp_var_concatenate_c1_#i
SET [Amount] = #TMP_C1_#i_CONCATENATE_PREV.[C1_#i_PREV]
FROM #tmp_var_concatenate_c1_#i
INNER JOIN
#TMP_C1_#i_CONCATENATE_PREV ON
#tmp_var_concatenate_c1_#i.[CONCATENATE] = #TMP_C1_#i_CONCATENATE_PREV.
[CONCATENATE]
Please forgive my ignorance, if I am doing something idiotic.
Thanks
This is part of the code in which the code is burping.
alter table #tmp_var_concatenate_C1_'+cast(#i as varchar)+'
add [ColA] varchar(255),
[ColB] Varchar(255),
[ColC] Varchar(255),
[ColD] VARCHAR(50),
[ColE] MONEY,
[ColF] MONEY
Is it because of the #tables that I am using ?? but, ideally, it shouldnt be an issue whether am using a Temp table or a reg. one..
You can use dynamic sql:
DECLARE #SQL varchar(max), #i INT
SET #i=2
while (#i<=22)
begin
/* Then cover all calculations with this one: */
SET #SQL='SELECT [DEF].[CONCATENATE], SUM(DEF.[C1_'+cast(#i as varchar)+'_PREV]) as
[C1_'+cast(#i as varchar)+'_prev]
INTO #TMP_C1_'+cast(#i as varchar)+'_CONCATENATE_PREV
FROM DEF
GROUP BY DEF.[CONCATENATE]
/* and all your code with the same trick in #i to the END */
'
--PRINT (#SQL) -- print it before use to see the result script
EXEC (#SQL)
/* Than do your iterations etc. */
set #i+=1
end
And don't forget to substitute all ' inside #SQL with ''.
Also you need to do all manipulations with temp tables inside #SQL, if you want to do final update outside the dynamic sql, just make tables real and then delete them.
[UPDATE]
As far as you faced with problem of altering temp tables, I tried to reproduce this error, but nothing happens, everything works fine. Please use this code as an example.
declare #sql varchar(max),#i int
set #i=2
while #i<=22
begin
set #sql='
select ID,Code into #TMP_C1_'+cast(#i as varchar)+'_CONCATENATE_PREV from (select 0 as ID, ''a'' as Code) t1
alter table #TMP_C1_'+cast(#i as varchar)+'_CONCATENATE_PREV add [Col1] varchar(255), [Col2] Varchar(255), [Col3] Money
select * from #TMP_C1_'+cast(#i as varchar)+'_CONCATENATE_PREV'
--print (#sql)
exec (#sql)
set #i+=1
end
First, I create temp table with dynamic name. Second, add new columns. The last is successful verifying. Did you execute all creations/alters in the same #sql-batch? If no, this won't work, because this tables are available only inside this batch (that's why we used varchar(max) when declared). Please describe your actions in details, maybe there is a mistake somewhere.
What's a good way to dynamically create tables, then join together all the tables that I have created?
Background
I'm trying to write a stored procedure that collects data from various parts of the database and presents it for use. For example if I wanted to send invoices to both Alice and Bob, this stored procedure would find all the information for the invoice. The trick is that Alice and Bob don't always need all the information I have. If Bob is tax exempt but trades in simoleons, I'll need to include information about currency exchange rates but leave out tax info. I want to give the stored procedure an order number and get back results formatted to the customer.
My solution so far is to create a table holding customer requirements. Then my stored procedure checks the table: if currency data is required I create and populate #Currency, or #Taxes holds tax data and so on. I check the requirement, then create the table as needed. At the end I need to Join the data into one record set.
My problem is that I have a error saying
Incorrect syntax near the keyword 'table'
AND an error saying
Must declare the table variable #Summary
on line 1 where i define #Summary These errors only show up when I use dynamic SQL to join the tables. When I comment out the dynamic SQL and copy-paste the statements into the one it should create, I have the result I want.
Code
I tried to use the following dynamic SQL in Microsoft SQL Server Management Studio
create procedure <procedure> (#OrderNumber varchar(20)) AS
DECLARE #CustomerName varchar(35) -- retrieved based on #OrderNumber
DECLARE #SQLQuery nvarchar(500)
DECLARE #ParamDef nvarchar(500)
DECLARE #SummaryTable table
(
ID varchar(20) --=#OrderNumber
, <Stuff>
)
SET #SQLQuery = 'Select * From #SummaryTable'
SET #ParamDef = '#SummaryTable table'
IF EXISTS(Select <TableName> from CustRequirements where <TableName> = 1 AND Customer = #CustomerName)
BEGIN
--Create table
DECLARE #<TableName> table
(
ID varchar(20)
, <Stuff>
)
--Populate
Insert into <TableName> Select <RelevantData> From <DataSource> where <Condition based on #OrderNumber or #CustomerName>
--Pepare Dynamic SQL
Set #SQLQuery = #SQLQuery + ' FULL OUTER JOIN #<TableName> ON <TableName>.ID = SummaryTable.ID'
SET #ParamDef = ', #<TableName> table'
END
<repeat for other tables>
EXECUTE sp_executesql #SQLQuery, #ParamDef, #Summary, #LineItems, #Taxes, #Currency
Question
Is there something wrong with my code? Is there a better way to do this? The other option I though of was to have IF Exists trees with the entire JOIN statement at the bottom of each branch (Since I can't seem to interrupt the JOIN clause with IF's). The problem with that is that I'll need 2^n JOIN statements to join n possible tables. n in this case might be as high has 20 or 30.
I think I see a problem in the code (maybe 3 problems -- see "questionable" 1 & 2 below) --
1) [Changed on 10/21, after OP's comments] The big problem: table parameters passed in the final "EXECUTE sp_executesql #SQLQuery..." are sometimes not declared.
1a) #Summary is actually never declared... you declared and set #SummaryTable, then use #Summary.
Just change that to #SummaryTable (I did that in (4) below), and I think that will prevent your second error message ("Must declare the table variable #Summary").
1b) All the other tables are sometimes declared: each of their DECLARE statements are within an "IF EXISTS". I suggest either (I) make the declares unconditional (outide IF EXISTS), but still conditionally INSERT... or (II) make the format of the EXECUTE command vary with what's available.
(I doubt the unused table variables need anything in them....)
In point 4 below (added 10/21), I give an example that I have NOT TESTED with my own databases (that test would take a lot more time)... so please let me know how it goes...
2) [questionable 1] Simple case of mixed case ;) Note that the line...
Set #SQLQuery = #SqlQuery + ' FULL OUTER JOIN #<TableName> ON <TableName>.ID = SummaryTable.ID'
... first has an uppercase "SQL", then mixed-case "Sql".
Why I said this is "questionable" -- if your server's COLLATION is case-insensitive, what you typed above would be fine.
3) [questionable 2] You have both '#TableName' and '#Table Name' (with a space). I realize that may just be how you typed it, in posting your question.
Point (4), added in update to answer -- possible code
create procedure <procedure> (#OrderNumber varchar(20)) AS
DECLARE #CustomerName varchar(35) -- retrieved based on #OrderNumber
DECLARE #SQLQuery nvarchar(500)
DECLARE #ParamDef nvarchar(500)
DECLARE #SummaryTable table
(
ID varchar(20) --=#OrderNumber
, <Stuff>
)
SET #SQLQuery = 'Select * From #SummaryTable'
SET #ParamDef = '#SummaryTable table'
--Create table variables, though they may not be populated
DECLARE #LineItems
(
ID varchar(20)
, <Stuff>
)
DECLARE #Taxes
(
ID varchar(20)
, <Stuff>
)
DECLARE #Currencytable
(
ID varchar(20)
, <Stuff>
)
IF EXISTS(Select <TableName> from CustRequirements where <TableName> = 1 AND Customer = #CustomerName)
BEGIN
--Populate
Insert into <TableName> Select <RelevantData> From <DataSource> where <Condition based on #OrderNumber or #CustomerName>
--Prepare Dynamic SQL
Set #SQLQuery = #SQLQuery + ' FULL OUTER JOIN #<TableName> ON <TableName>.ID = SummaryTable.ID'
SET #ParamDef = ', #<TableName> table'
END
<repeat for other tables>
EXECUTE sp_executesql #SQLQuery, #ParamDef, #SummaryTable, #LineItems, #Taxes, #Currency
The solution I've decided tp use is to create a table then use ALTER TABLE to add columns as needed.
CREATE Table #Output
(
InvoiceNumber varchar(20)
, <stuff>
)
IF EXISTS(Select <ThisSegment> FROM CustRequirements WHERE <ThisSegment> = 1 AND Customer = #CustomerName)
BEGIN
ALTER TABLE #Output ADD <ThisSegment> <dataType>
UPDATE #Output
SET <ThisSegment> = <data> from <DataSource>
WHERE <InvoiceNumber = DataSource.invoice> AND <Other conditions>
END
<Repeat as needed>
I want to clone multiple tables' rows that have a single primary key (PrimKey). What's the quickest way to clone a row in SQL Server 2005?
Here's an example,
Clone PrimKey1 to get PrimKey2. So I try the following :
INSERT INTO PrimKeys
SELECT 'PrimKey2' AS PrimKey,*
FROM PrimKeys
WHERE PrimKey='PrimKey1'
But of course the issue here is, the column PrimKey gets repeated in the inner SELECT statement. Is there any other way similar in simplicity to the above?
Thank you all for your responses. I went ahead and wrote a function that handles that in my application. I don't use Stored Procs or Temp tables so I couldn't use a couple of valid answers posted by some of you.
You can run something like the stored procedure below to avoid typing out all of the column names. The example below assumes an int, but you can swap the key type out for any data type.
create procedure [CloneRow]
#tableName varchar(max),
#keyName varchar(max),
#oldKeyId int,
#newTableId int output
as
declare #sqlCommand nvarchar(max),
#columnList varchar(max);
select #columnList = coalesce(#columnList + ',','') + sys.columns.name
from sys.columns
where object_name(sys.columns.object_id) = #tableName
and sys.columns.name not in ( #keyName )
and is_computed = 0;
set #sqlCommand = 'insert into ' + #tableName + ' ( ' + #columnList + ') (' +
'select ' + #columnList + ' from ' + #tableName + ' where ' + #keyName + ' = #oldKeyId )'
exec sp_executesql #sqlCommand, N'#oldKeyId int', #oldKeyId = #oldKeyId
select #newTableId = ##identity -- note scope_identity() won't work here!
GO
You call it like this:
declare #newOrderId int
exec [CloneRow] 'orderTable', 'orderId', 625911, #newOrderId output
This is not the most beautiful solution, but I think it will work for you. First of all, you select your data into a temporary table with a "new" primary key and next you drop the old primary key column from the temp table and use the temp table to insert your "cloned" row.
SELECT
'PrimKey2' AS NewPrimKey,
*
INTO #TMP
FROM PrimKeys
WHERE PrimKey='PrimKey1';
ALTER TABLE #TMP DROP COLUMN PrimKey;
INSERT INTO PrimKeys
SELECT * FROM #TMP;
Not sure what do you mean by "multiple tables' rows that have a single primary key".
PRIMARY KEY, by definition, is UNIQUE.
To do your query you need to enumerate all columns:
INSERT
INTO PrimKeys (PrimKey, col1, col2, …)
SELECT 'PrimKey2' AS PrimKey, col1, col2, …
FROM PrimKeys
WHERE PrimKey = 'PrimKey1'
First, if you need to perform generic operations against a large number of tables, then dynamic SQL and the system tables are your friends.
Short of that, Hakan's solution will work for non-identity PKs. I would tighten it up to:
SELECT * INTO #TMP
FROM PrimKeys
WHERE PrimKey='PrimKey1';
UPDATE #TMP SET PrimeKey = 'PrimeKey2';
INSERT INTO PrimKeys
SELECT * FROM #TMP;
For identity PKs, change the UPDATE above to a DROP COLUMN:
SELECT * INTO #TMP
FROM PrimKeys
WHERE PrimKey=101;
ALTER TABLE #TMP DROP COLUMN PrimeKey;
INSERT INTO PrimKeys
SELECT * FROM #TMP;
If you are cloning from multiple tables, the fastest way will be to use a stored procedure, so that all the queries stay on the database, and you don't pay a price for communication between the client and the server.
Once you do that, then start unit-testing, to see how long it takes to do the operation. Then begin experimenting with changing it, based on suggestions you get, and see if you get any improvement.