First post from a self-taught data warehouse guy. I've done lots of searching and reading to get where I am now, but can't get past this sticking point.
Background: as part of our nightly ETL job, we have to copy many tables from many remote DBs (linked servers) into staging-area DBs. After table copies have finished, I continue with the transformation from the staging area DBs into production tables.
Since the remote DBs all have identical schema, I made a stored procedure in the production DB to do the work. The stored procedure accepts parameters of the remote database name and the table name. In the nightly job, SQL Server Agent runs an SSIS package; the package contains one (retry-looping) SSIS task for each remote database; all the tasks run concurrently; each task uses a variable to pass the DB name to SQL file; then the SQL file calls the stored procedure once for each table.
Example remote table and local staging-area table:
Remote: [FLTA].[cstone].[csdbo].[CLIENT]
Local: [FLTAL].dbo.[FLTA CLIENT]
The stored procedure is pretty simple, dropping the old table and using SELECT to make a fresh copy from the remote DB. It looks approximately like this:
CREATE PROCEDURE dbo.spTableCopyNew
(#p VARCHAR(50), #Tablename VARCHAR(50))
AS
-- Drop the existing table
EXEC('IF OBJECT_ID(''[' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +']'', ''U'') IS NOT NULL
DROP TABLE [' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +']'
)
-- Copy the new table
EXEC('SELECT * into [' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +']
FROM [' + #p + '].[cstone].[csdbo].[' + #Tablename +']'
)
GO
The SQL looks roughly like this:
-- Set local variables for the remote server connection, the local database name, and the table prefix
DECLARE #Prefix varchar(50)
-- Accept the variables passed in from the SSIS task
SET #Prefix = ?
-- Copy the two tables
EXEC Datawarehouse.dbo.spTableCopy #Prefix, 'CLIENT'
EXEC Datawarehouse.dbo.spTableCopy #Prefix, 'PATIENT'
Maintenance is a breeze: when we need to grab a new table from all the remote databases, I just add it to the "productionLoad.sql" file.
It works really well...except when it doesn't.
Due to un-figured-out-yet reasons, sometimes a table fails to copy. And since I'm dropping the existing table before copying the new one, this will sometimes break things further down the line. My SSIS tasks will retry up to three times per remote DB, so occasional failures are no big deal. But if the same remote DB has three failures in one night, I'm gonna have a bad time.
My current attempt at a solution is to copy the remote table to a temp table, then ONLY AFTER that copy is successful, drop the local table and rename the temp table to the "real" table. Which brings me to the problem:
I can't get sp_rename to work when called from a stored procedure, to rename tables that exist in a different database than the stored procedure. I've created new variables to resolve expressions, then send those variables to sp_rename, since I can't pass expressions into that stored procedure.
Here's my attempt at a new stored procedure:
CREATE PROCEDURE dbo.spTableCopy
(#p VARCHAR(50), #Tablename VARCHAR(50))
AS
BEGIN
EXEC('USE [' + #p + 'L]')
-- Create variables for schema and table names
-- Since sp_rename can accept variables, but not expresssions containing variables.
DECLARE #RemoteTable VARCHAR(50) = '[' + #p + '].[cstone].[csdbo].[' + #Tablename +']'
DECLARE #LocalTableTemp VARCHAR(50) = '[' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +'_temp]'
DECLARE #LocalTable VARCHAR(50) = '' + #p + ' ' + #Tablename + ''
-- Check for previous temp table and drop it
EXEC('IF OBJECT_ID(''[' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +'_temp]'', ''U'') IS NOT NULL
DROP TABLE [' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +'_temp]'
)
-- Copy the new table
EXEC('SELECT * into ' + #LocalTableTemp + '
FROM ' + #RemoteTable + ''
)
-- Drop the existing table
EXEC('IF OBJECT_ID(''[' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +']'', ''U'') IS NOT NULL
DROP TABLE [' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +']'
)
-- Rename temp table to real table
EXEC sp_rename #LocalTableTemp, #LocalTable
END
GO
This all works when executing it as normal SQL code, but when I make it into a stored procedure, sp_rename fails (everything else works). The final table [FLTAL CLIENT_temp] is there and contains the right data.
sp_rename returns the following error:
Msg 290, Level 16, State 2, Procedure sp_rename, Line 318
Invalid EXECUTE statement using object "Object", method "LockMatchID".
I've fought with this way too long.
Am I just screwing up the syntax?
Can I get sp_rename to work on other DBs with "USE?"
If not, will it work if I make a copy of my sp_tableCopy in every staging-area DB?
If not, will catch-try work inside this stored procedure, even if I call this stored procedure many times concurrently?
What else can I do to recover from failed table copies?
My alternate solution that I haven't pursued yet: after the temp table is successfully created, to TRUNC the existing table and insert everything from the temp table into the real table. That seems messy though.
P.S. Our IT guys are "looking into" the nature of the copy failures.
Try This...
USE
EXEC ..sp_rename '..', '<target_table>'
USE sourcedb
EXEC targetdatabase..sp_rename 'schema.oldtable', 'target_table'
Related
I am consistently running a report and creating tables for this report. Now other users are running thsi report as well. So I need users to be able to run stored procedure simultaniously without worry of overwriting tables. I tried using a simple temp table but I need the temporary table to work through out two "functions." One dynamic sql statement that creates a table and one dynamic sql statment thats table driven.
My primary issue is I want the table driven piece of code to be able to see the global temporary table variable but it does not. Is there a work around for this while still using temporary tables? is there a way to run both dynamic sql statements at once so the other type of temp table would work?
Any advice in the right direction is helpful. Thank you.
DECLARE #TmpGlobalTable varchar(255) = 'SomeText_' + convert(varchar(36),NEWID())
SELECT #SQL = #SQL +'
SELECT IDENTITY(INT) as idcol, date, Desc As [Description]
INTO [##' + #TmpGlobalTable + ']
FROM dbo.appendix
WHERE RecordStatus = 1
and casestatement from user input
'
print(#sql)
exec(#sql)
Declare #sql1 varchar(max) = ''
SELECT #SQL1 = #SQL1 +'
insert into dbo.'+#table+'
select ''1'', '''+date+''' as Sequence, Description as Description_color, buyer, seller, price, option
from '+#ClientTable+'
where isnull('+Seq+',9999) <= cutoffvalue
group by description , buyer, seller, price, option
'
from
[##' + #TmpGlobalTable + ']
print(#sql1)
exec(#sql1)
EXEC ('DROP TABLE [##' + #TmpGlobalTable + ']')
PRINT 'Dropped Table ' + #TmpGlobalTable
I need to convert some tables as Temporal (System versioned) ones. For this purpose, I have written an SQL command to be executed in a dynamic manner.
The query does not throw an error, but it doesn't execute the SQL command. It does not print the command using PRINT either.
I read the related articles in SO. According to them I have declared the variables in the script as well as providing them with sp_executesql. Please advise.
DECLARE #sqlCommand nvarchar(2000)
DECLARE #tableName nvarchar(100)
SET #sqlCommand =
'ALTER TABLE ' + #tableName + '
ADD [SysStartTime] DATETIME2
GO
ALTER TABLE ' + #tableName + '
ADD [SysEndTime] DATETIME2
GO
UPDATE ' + #tableName + ' SET [SysStartTime] = ''19000101 00:00:00.0000000'', [SysEndTime] = ''99991231 23:59:59.9999999''
GO
ALTER TABLE ' + #tableName + '
ALTER COLUMN [SysStartTime] DATETIME2 NOT NULL
GO
ALTER TABLE ' + #tableName + '
ALTER COLUMN [SysEndTime] DATETIME2 NOT NULL
GO
ALTER TABLE ' + #tableName + '
ADD PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime])
ALTER TABLE ' + #tableName + ' SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [History].ConditionAssessment))
GO'
PRINT #sqlCommand
EXECUTE sp_executesql #sqlCommand, N'#tableName nvarchar(100)', #tableName = 'ConditionAssessmentData'
It won't print nor execute because #tableName and #sqlCommand are both null and anything + null = null in T-SQL so they remain null.
In fact you can't do what you want anyway, because you can only use parameters in the same wsy that you can in a non-dynamic query, and that doesn't let you do things like alter table see http://www.sommarskog.se/dynamic_sql.html.
You would need to build the entire string including the table name and not pass any parameters into sp_executesql.
You would also want to consider carefully whether you want to provide such a powerful, and dangerous (because of the danger of SQL injection) procedure.
I have filtering SQL that returns query with uncertain number of columns, and want to use results in stored procedure.
DECLARE #RecordSelectionSql VARCHAR(MAX)
SET #RecordSelectionSql = (SELECT SQLQUERY FROM RecordSelection WHERE Id = #Id) + ' AND ([Active] = 1)'
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += ',' + CHAR(13) + CHAR(10) + CHAR(9) + name + ' ' + system_type_name
FROM sys.dm_exec_describe_first_result_set(#RecordSelectionSql, NULL, 0);
SELECT #sql = N'CREATE TABLE #TmpImport
(' + STUFF(#sql, 1, 1, N'') + '
);';
EXEC (#sql)
INSERT INTO #TmpImport
EXEC (#RecordSelectionSql)
However I am getting error
Invalid object name '#TmpImport'.
How to properly code this part?
EDIT: added missing condition on RecordSelection
EDIT2:
I cannot use code below because #TmpImport destroyed after #RecordSelectionSql being executed.
DECLARE #RecordSelectionSql AS VARCHAR(MAX)
SET #RecordSelectionSql = 'SELECT X.* INTO #TmpImport FROM ('
+ (SELECT SQLQUERY FROM RecordSelection WHERE Id = #Id) + ' AND ([Active] = 1) AS X'
EXEC (#RecordSelectionSql)
SELECT * FROM #TmpImport
Gives the same error
Invalid object name '#TmpImport'.
Temporary tables are only available within the session that created them. With Dynamic SQL this means it is not available after the Dynamic SQL has run. Your options here are to:
Create a global temporary table, that will persist outside your session until it is explicitly dropped or cleared out of TempDB another way, using a double hash: create table ##GlobalTemp
--To incorporate Radu's very relevant comment below: Because this table persists outside your session, you need to make sure you don't create two of them or have two different processes trying to process data within it. You need to have a way of uniquely identifying the global temp table you want to be dealing with.
You can create a regular table and remember to drop it again afterwards.
Include whatever logic that needs to reference the temp table within the Dynamic SQL script
For your particular instance though, you are best off simply executing a select into which will generate your table structure from the data that is selected.
It's much easier to select into your temp table.
For example
SELECT * INTO #TmpImport FROM SomeTable
I have a MSSQL GPS database which create every month a database and every day a table. This is a real time database that getting data every 3 seconds. And all operations are making by a GPS program which I have no source code or access.
Databases looks like this :
-Comms201502 (Database)
-Comms201503 (Database)
-Comms201504 (Database)
-GPS20150401 (Database's Table)
-GPS20150402 (Database's Table)
-GPS20150403 (Database's Table)
-GPS20150404 (Database's Table)
-...
I have a trigger that receiving data from a table which I have to create that trigger for every day, and writing to my database.
Is there a anyway to create single trigger or create an automaticly create trigger every day ?
Best regards,
I would create a DDL trigger, this would be raised each time a CREATE TABLE Statement is executed and then adds a trigger to the new table to log the records to your table, such as:
CREATE TRIGGER trg_DDLCreateTrigger ON DATABASE
FOR CREATE_TABLE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #EventData XML = EVENTDATA();
DECLARE #Schema SYSNAME
DECLARE #TableName SYSNAME
SELECT #Schema = EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]','nvarchar(max)'),
#TableName = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','nvarchar(max)')
DECLARE #sql VARCHAR(MAX)
SET #sql = 'CREATE TRIGGER [trg_' + #TableName + '] ON [' + #Schema + '].[' + #TableName + ']' +
' AFTER INSERT AS ' +
' INSERT INTO MyTable SELECT * FROM inserted' --Change this as required
EXEC (#Sql)
END
GO
You should probably add some validation in the trigger to check that the table is one that you are interested in capturing.
You can probably extend this so that there is a DDL trigger on a create database statement, which adds this trigger to the database.
When I run the following code, I get an "invalid object name" error, any idea why?
I need to create a dynamically named temp table to be used in a stored procedure.
DECLARE #SQL NVARCHAR(MAX)
DECLARE #SessionID NVARCHAR(50)
SET #SessionID = 'tmp5l7g9q3l1h1n5s4k9k7e'
;
SET
#SQL = N' CREATE TABLE #' + #SessionID + ' ' +
N' (' +
N' CustomerNo NVARCHAR(5), ' +
N' Product NVARCHAR(3), ' +
N' Gross DECIMAL(18,8) ' +
N' )'
;
EXECUTE sp_executesql #SQL
;
SET
#SQL = N' SELECT * FROM #' + #SessionID
;
EXECUTE sp_executesql #SQL
Thanks!
WHY MESS WITH THE NAMES? Let SQL Server will manage this for you:
Temporary Tables in SQL Server
from the above link:
If the same routine is executed simultaneously by several processes,
the Database Engine needs to be able to distinguish between the
identically-named local temporary tables created by the different
processes. It does this by adding a numeric string to each local
temporary table name left-padded by underscore characters. Although
you specify the short name such as #MyTempTable, what is actually
stored in TempDB is made up of the table name specified in the CREATE
TABLE statement and the suffix. Because of this suffix, local
temporary table names must be 116 characters or less.
If you’re interested in seeing what is going on, you can view the
tables in TempDB just the same way you would any other table. You can
even use sp_help work on temporary tables only if you invoke them from
TempDB.
USE TempDB
go
execute sp_Help #mytemp
or you can find them in the system views of TempDB without swithching
databases.
SELECT name, create_date FROM TempDB.sys.tables WHERE name LIKE '#%'
You are doing it wrong!
Try:
exec(#SQL)
instead of:
EXECUTE sp_executesql #SQL
To use sp_executesql the variable must be inside #SessionID the quotes and it must be provided has input parameter. Check this for a full example!
You've to be aware that Dynamic SQL is a good port for SQL injections!
This syntax works
CREATE TABLE #SessionID (CustomerNo NVARCHAR(5), Product NVARCHAR(3), Gross DECIMAL(18,8));
Select COUNT(*) from #SessionID;
Drop Table #SessionID;