Quickest way to clone row in SQL - sql

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.

Related

How to use a variable in "Select [some calculations] insert into #NameOfTheTableInThisVariable"?

I have a procedure in which there are calculations being done and the final result is inserted into a permanent table. I want to remove the permanent table and I cannot use Temp table as well. So i want to use a dynamic table name, which is stored in a variable:
Current scenario:
Insert into xyz_table
Select col1,col2,sum(col3)
from BaseTable
(In reality, there are lot of columns and a lot of calculations)
What I want:
Select col1,col2,sum(col3) into #DynamicTableName
from BaseTable
where the name of the table would be dynamic in nature i.e.,
#DynamicTableName = 'xyz ' + cast(convert(date,getdate()) as nvarchar)+' '+convert(nvarchar(5),getdate(),108)
It will have date and time in its name every time the procedure is run.
I want to use this name in the "Select * into statement"
How can I achieve this?
i tried it with the some short code. But since my procedure has a lot of calculations and UNIONS , I cannot use that code for this. Any help would be appreciated.
declare #tablename nvarchar(30)= 'xyz ' + cast(convert(date,getdate()) as nvarchar)+' '+convert(nvarchar(5),getdate(),108)
declare #SQL_Statement nvarchar(100)
declare #SQL_Statement2 nvarchar(100)
declare #dropstatement nvarchar(100)
SET #SQL_Statement = N'SELECT * Into ' +'['+#tablename +'] '+'FROM '+ 'dimBranch'
print #SQL_Statement
EXECUTE sp_executesql #SQL_Statement
SET #SQL_Statement= N'select * from ' + '['+#tablename + '] '
print #SQL_Statement
EXECUTE sp_executesql #SQL_Statement
set #dropstatement = 'DROP TABLE' + '['+#tablename + '] '
PRINT #dropstatement
exec sp_executesql #dropstatement
Reason why I want this is because I use this procedure in ETL job as well as in SSRS report. And if someone runs the package and the SSRS report at the same time, the incorrect or weird data gets stored in the table. Therefore I need a dynamic name of the table with date and time.
You can't parameterize an identifier in SQL, only a value
--yes
select * from table where column = #value
--no
select * from #tablename where #columnname = #value
The only thin you can do to make these things dynamic is to build an sql string and execute it dynamically, but your code is already doing this with sp_executesql
More telling is your complaint at the bottom of your question, that if the procedure is invoked simultaneously it gives problems. Perhaps you should consider using local table variables for temporary data storage that the report is using rather than pushing data back into the db
DECLARE #temp TABLE(id INT, name varchar100);
INSERT INTO #temp SELECT personid, firstname FROM person;
-- work with temp data
select count(*) from #temp;
--when #temp goes out of scope it is lost,
--no other procedure invoked simultaneously can access this procedure'a #temp
Consider a local temp table, which is automatically session scoped without the need for dynamic SQL. For example:
SELECT *
INTO #YourTempTable
FROM dimBranch;
The local temp table will automatically be dropped when the proc completes so there is no need for an explict drop in the proc code.

Is there an elegant way to copy an entire row from one database to another with same schema

if you have say table Contacts in database1 and you want to copy ContactID=123 to database2. Copying all columns (except ID I guess) assuming Contacts table exists in both databases and identical schema. Also both databases are in same server.
the long way is to list each column in the insert statement, but is there a way without listing all columns? or at least automatically get all columns.
I am looking at SQL Server 2012
I tried:
SELECT * into [database2].[dbo].[Contacts] from [database1].[dbo].[Contacts] where [ContactsID]=123
but got error:
There is already an object named 'Contacts' in the database.
Maybe something like
DECLARE #Sql VARCHAR(MAX),
#COLUMNS VARCHAR(MAX)
SELECT #COLUMNS = COALESCE(#COLUMNS + ',', '') + COLUMN_NAME
FROM Database1.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'Contacts' AND COLUMN_NAME <> 'Id'
SET #Sql = CONCAT('INSERT INTO Database2.dbo.Contacts',
'SELECT ', #COLUMNS, ' FROM Database1.dbo.Contacts WHERE Id = 123')
EXEC (#Sql)
If 'Id' is an identity column, and you want to include it in the insert just set Identity_Insert on for the table you're inserting into
SET IDENTITY_INSERT Database2.dbo.Contacts ON
Then insert * from Database1
Insert Into Database2.dbo.Contacts
Select * From Database1.dbo.Contacts Where Id = 123
Then turn Identity_Insert back off for the table
SET IDENTITY_INSERT Database2.dbo.Contacts OFF
You can do it without specifying colls, and then maybe update the id:
DECLARE #newId int = 1;
INSERT INTO newTable SELECT * FROM initial_Table WHERE initial_Table.ContactsID=123;
UPDATE newTable SET ContactsID=#newId WHERE ContactsID=123;

Convert Empty string into NULL when data insert into table from another table

I am stuck on inserting data from one table into another table and exporting data into csv file using bcp. The problem is that the csv text file contains null instead of '' empty string.
When I insert data into a table from another table the empty string column is treated as a NULL. Doing the bcp command generate correct file.
I am using this command to export bcp
bcp ClientReportNewOrder out "D:\Temp\Neeraj\TestResults\oOR.txt" -c -t"," -r"\n" -S"." -U"sa" -P"123"
and this for insert data into table
insert into ClientReportNewOrder
select * from ClientReportNewOrder_import
After lots of brainstorming ,I have been found the solution.
Execute Insert statement
insert into ClientReportNewOrder select * from ClientReportNewOrder_import
After insert record update record by Dynamic query like below.
DECLARE #qry NVARCHAR(MAX)
SELECT #qry = COALESCE( #qry + ';', '') +
'UPDATE ClientReportNewOrder SET [' + COLUMN_NAME + '] = NULL
WHERE [' + COLUMN_NAME + '] = '''''
FROM INFORMATION_SCHEMA.columns
WHERE DATA_TYPE IN ('char','nchar','varchar','nvarchar') and TABLE_NAME='ClientReportNewOrder '
EXECUTE sp_executesql #qry
And follows above steps, I have been able to resolved the issue.
If any one have good technique for achieve same please post.
Not sure about bcp but if you are inserting using SQL query (INSERT .. SELECT construct) directly like below
insert into ClientReportNewOrder
select * from ClientReportNewOrder_import
Then you can use either ISNULL() or COALESCE() function to get around this like below (a sample, considering that col3 having NULL)
insert into ClientReportNewOrder(col1,col2,col3)
select col1,col2,ISNULL(col3,'') from ClientReportNewOrder_import
You can use trigger on the table "ClientReportNewOrder".
The trigger which you can create is "Instead of" trigger on the table and check if the value in there is null, then you can force that to write "">
CREATE TRIGGER trgInsteadOfInsert ON dbo.Yourtable
INSTEAD OF Insert
declare #value varchar(55)
BEGIN
BEGIN TRAN
if(#value is Null)
begin
Insert into Yourtable (Value) values (#value);
COMMIT;
End
End
Hope this helps.

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?

How to BULK INSERT a file into a *temporary* table where the filename is a variable?

I have some code like this that I use to do a BULK INSERT of a data file into a table, where the data file and table name are variables:
DECLARE #sql AS NVARCHAR(1000)
SET #sql = 'BULK INSERT ' + #tableName + ' FROM ''' + #filename + ''' WITH (CODEPAGE=''ACP'', FIELDTERMINATOR=''|'')'
EXEC (#sql)
The works fine for standard tables, but now I need to do the same sort of thing to load data into a temporary table (for example, #MyTable). But when I try this, I get the error:
Invalid Object Name: #MyTable
I think the problem is due to the fact that the BULK INSERT statement is constructed on the fly and then executed using EXEC, and that #MyTable is not accessible in the context of the EXEC call.
The reason that I need to construct the BULK INSERT statement like this is that I need to insert the filename into the statement, and this seems to be the only way to do that. So, it seems that I can either have a variable filename, or use a temporary table, but not both.
Is there another way of achieving this - perhaps by using OPENROWSET(BULK...)?
UPDATE:
OK, so what I'm hearing is that BULK INSERT & temporary tables are not going to work for me. Thanks for the suggestions, but moving more of my code into the dynamic SQL part is not practical in my case.
Having tried OPENROWSET(BULK...), it seems that that suffers from the same problem, i.e. it cannot deal with a variable filename, and I'd need to construct the SQL statement dynamically as before (and thus not be able to access the temp table).
So, that leaves me with only one option which is to use a non-temp table and achieve process isolation in a different way (by ensuring that only one process can be using the tables at any one time - I can think of several ways to do that).
It's annoying. It would have been much more convenient to do it the way I originally intended. Just one of those things that should be trivial, but ends up eating a whole day of your time...
You could always construct the #temp table in dynamic SQL. For example, right now I guess you have been trying:
CREATE TABLE #tmp(a INT, b INT, c INT);
DECLARE #sql NVARCHAR(1000);
SET #sql = N'BULK INSERT #tmp ...' + #variables;
EXEC master.sys.sp_executesql #sql;
SELECT * FROM #tmp;
This makes it tougher to maintain (readability) but gets by the scoping issue:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'CREATE TABLE #tmp(a INT, b INT, c INT);
BULK INSERT #tmp ...' + #variables + ';
SELECT * FROM #tmp;';
EXEC master.sys.sp_executesql #sql;
EDIT 2011-01-12
In light of how my almost 2-year old answer was suddenly deemed incomplete and unacceptable, by someone whose answer was also incomplete, how about:
CREATE TABLE #outer(a INT, b INT, c INT);
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'SET NOCOUNT ON;
CREATE TABLE #inner(a INT, b INT, c INT);
BULK INSERT #inner ...' + #variables + ';
SELECT * FROM #inner;';
INSERT #outer EXEC master.sys.sp_executesql #sql;
It is possible to do everything you want. Aaron's answer was not quite complete.
His approach is correct, up to creating the temporary table in the inner query. Then, you need to insert the results into a table in the outer query.
The following code snippet grabs the first line of a file and inserts it into the table #Lines:
declare #fieldsep char(1) = ',';
declare #recordsep char(1) = char(10);
declare #Lines table (
line varchar(8000)
);
declare #sql varchar(8000) = '
create table #tmp (
line varchar(8000)
);
bulk insert #tmp
from '''+#filename+'''
with (FirstRow = 1, FieldTerminator = '''+#fieldsep+''', RowTerminator = '''+#recordsep+''');
select * from #tmp';
insert into #Lines
exec(#sql);
select * from #lines
Sorry to dig up an old question but in case someone stumbles onto this thread and wants a quicker solution.
Bulk inserting a unknown width file with \n row terminators into a temp table that is created outside of the EXEC statement.
DECLARE #SQL VARCHAR(8000)
IF OBJECT_ID('TempDB..#BulkInsert') IS NOT NULL
BEGIN
DROP TABLE #BulkInsert
END
CREATE TABLE #BulkInsert
(
Line VARCHAR(MAX)
)
SET #SQL = 'BULK INSERT #BulkInser FROM ''##FILEPATH##'' WITH (ROWTERMINATOR = ''\n'')'
EXEC (#SQL)
SELECT * FROM #BulkInsert
Further support that dynamic SQL within an EXEC statement has access to temp tables outside of the EXEC statement. http://sqlfiddle.com/#!3/d41d8/19343
DECLARE #SQL VARCHAR(8000)
IF OBJECT_ID('TempDB..#BulkInsert') IS NOT NULL
BEGIN
DROP TABLE #BulkInsert
END
CREATE TABLE #BulkInsert
(
Line VARCHAR(MAX)
)
INSERT INTO #BulkInsert
(
Line
)
SELECT 1
UNION SELECT 2
UNION SELECT 3
SET #SQL = 'SELECT * FROM #BulkInsert'
EXEC (#SQL)
Further support, written for MSSQL2000 http://technet.microsoft.com/en-us/library/aa175921(v=sql.80).aspx
Example at the bottom of the link
DECLARE #cmd VARCHAR(1000), #ExecError INT
CREATE TABLE #ErrFile (ExecError INT)
SET #cmd = 'EXEC GetTableCount ' +
'''pubs.dbo.authors''' +
'INSERT #ErrFile VALUES(##ERROR)'
EXEC(#cmd)
SET #ExecError = (SELECT * FROM #ErrFile)
SELECT #ExecError AS '##ERROR'
http://msdn.microsoft.com/en-us/library/ms191503.aspx
i would advice to create table with unique name before bulk inserting.