SQL Replace call in dynamic SQL - sql-server

I am trying to update rows of data with a replacement string (built by another SP). I have the following table of data that shows a default expacct and replacement glseg1 and glseg2 values:
What I am trying to accomplish is updating the expacct number with an updated expacct number generated by the ReplacementString. The ReplacementString is in a table and associated with the default expacct:
Each section of the expacct (the number set between each hyphen) can be updated.
In a nutshell, for each section of the expacct, the ReplacementString should take the default expacct, use what is there currently if overwriting is not allowed (i.e., substring('#Acct',1,4)) or overwrite with the value from the designated glseg column, using the default value if no glseg value exists (i.e., rtrim(ltrim(isnull(nullif(d.glseg2, ''), substring('#Acct',10,4)))))
I have a somewhat working version of code but the replacement string is not being updated for each row that is being updated, it appears that the ReplacementString being used is the last one in the above table.
What I need to see in this output table is the expacct for ID 1 to be 6720-010-0000 (default value for first set, glseg1 value for second set and 0000 for third set since glseg2 is empty). The updated expacct value for the row with ID 2 is correct.
We have to support SQL back to 2008 R2 so please limit solutions to those that work with SQL 2008 R2.
Here is the complete SQL code I currently have:
if object_id(N'tempdb..#TimeData') is not null drop table #TimeData
create table #TimeData(ID int, expacct varchar(45), glseg1 varchar(10), glseg2 varchar(10))
insert into #TimeData
values(1, '6720-000-0000', '010', '')
, (2, '6720-999-0000', '030', '0404')
select * from #TimeData
-- create table of all possible account numbers from ALL_TimeCardDetail with their associated replacement strings
if object_id(N'tempdb..#AccountNumbers') is not null drop table #AccountNumbers
create table #AccountNumbers(Original varchar(100), ReplacementString nvarchar(max), Updated varchar(100))
insert into #AccountNumbers
values('6720-000-0000', 'substring(''#Acct'',1,4) + ''-'' + rtrim(ltrim(isnull(nullif(d.glseg1, ''''), substring(''#Acct'',6,3)))) + ''-'' + rtrim(ltrim(isnull(nullif(d.glseg2, ''''), substring(''#Acct'',10,4))))', '')
, ('6720-999-0000', 'substring(''#Acct'',1,4) + ''-'' + substring(''#Acct'',6,3) + ''-'' + rtrim(ltrim(isnull(nullif(d.glseg2, ''''), substring(''#Acct'',10,4))))', '')
select * from #AccountNumbers
declare #cmd nvarchar(max)
select #cmd = N'update #TimeData set expacct = ' + replace(n.ReplacementString, '#Acct', d.expacct) + '
from #TimeData d
inner join #AccountNumbers n
on d.expacct = n.Original'
from #TimeData d
inner join #AccountNumbers n
on d.expacct = n.Original
print #cmd
exec (#cmd)
select * from #TimeData

This works, but is not optimal for performance point of view :
SET NOCOUNT OFF
if object_id(N'tempdb..#TimeData') is not null drop table #TimeData
create table #TimeData(ID int, expacct varchar(45), glseg1 varchar(10), glseg2 varchar(10))
insert into #TimeData
values(1, '6720-000-0000', '010', '')
, (2, '6720-999-0000', '030', '0404')
select * from #TimeData
-- create table of all possible account numbers from ALL_TimeCardDetail with their associated replacement strings
if object_id(N'tempdb..#AccountNumbers') is not null drop table #AccountNumbers
create table #AccountNumbers(id int identity, Original varchar(100), ReplacementString nvarchar(max), Updated varchar(100))
insert into #AccountNumbers (original, ReplacementString, updated)
values('6720-000-0000', 'substring(''#Acct'',1,4) + ''-'' + rtrim(ltrim(isnull(nullif(d.glseg1, ''''), substring(''#Acct'',6,3)))) + ''-'' + rtrim(ltrim(isnull(nullif(d.glseg2, ''''), substring(''#Acct'',10,4))))', '')
, ('6720-999-0000', 'substring(''#Acct'',1,4) + ''-'' + substring(''#Acct'',6,3) + ''-'' + rtrim(ltrim(isnull(nullif(d.glseg2, ''''), substring(''#Acct'',10,4))))', '')
select * from #AccountNumbers
declare #cmd nvarchar(max)
declare #id int
declare c cursor for select id from #AccountNumbers
open c
fetch next from c into #id
while ##FETCH_STATUS = 0
begin
select #cmd = N'update #TimeData set expacct = ' + replace(n.ReplacementString, '#Acct', d.expacct) + '
from #TimeData d
inner join #AccountNumbers n
on d.expacct = n.Original
where n.id = ' + convert(varchar, #id)
from #TimeData d
inner join #AccountNumbers n
on d.expacct = n.Original
where n.id = #id
print #cmd
exec (#cmd)
fetch next from c into #id
end
close c
deallocate c
select * from #TimeData

Related

Select all unique values from all columns in a table

I need to select all unique values from all columns in a table.
I have tried to implement the query below which I found in the thread How to get unique values from all columns of a table in SQL Server.
declare #Sql_Str varchar(8000)='';
select #Sql_Str=#Sql_Str+' select cast (' +name +' as varchar(500))
from <yourtable> union'
from sys.columns
where [object_id]=object_id('<yourtable>');
set #Sql_Str=SUBSTRING(#Sql_Str,1,len(#Sql_Str)-6);
exec(#Sql_Str)
I cannot get that query to work however. My table has 118 columns. I think that may be more data than the query above may handle.
Try something like this:
DECLARE #Schema VARCHAR(500)='dbo';
DECLARE #tableName VARCHAR(500)='SomeTable';
DECLARE #cmd NVARCHAR(MAX)=
(
SELECT STUFF(
(
SELECT ' UNION ALL SELECT ''' + c.TABLE_SCHEMA + ''' AS TableSchema '
+ ',''' + c.TABLE_NAME + ''' AS TableName '
+ ',''' + c.COLUMN_NAME + ''' AS ColumnName '
+ ',''' + c.DATA_TYPE + ''' AS ColumnType '
+ ',CAST(' + QUOTENAME(c.COLUMN_NAME)+' AS NVARCHAR(MAX)) AS Value '
+ ' FROM ' + QUOTENAME(c.TABLE_SCHEMA) + '.' + QUOTENAME(c.TABLE_NAME)
+ ' WHERE ' + QUOTENAME(c.COLUMN_NAME) + ' IS NOT NULL '
+ ' GROUP BY ' + QUOTENAME(c.COLUMN_NAME) + ' '
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE TABLE_NAME=#TableName
AND TABLE_SCHEMA=#Schema
--exclude not supported types
--AND c.DATA_TYPE NOT IN('xml') --add more types
FOR XML PATH(''),TYPE
).value('.','nvarchar(max)'),1,10,'')
);
--PRINT #cmd
EXEC(#cmd);
This statement will first create a long list of UNION ALL SELECT with GROUP BY (better than DISTINCT) as dynamically created SQL and executes this with EXEC().
You can decomment PRINT to examine the statement created.
This should work in tSQL:
declare #table_name varchar(55)
set #table_name= 'IV00101' ---- <-- Change this to your table name
create table #colcount (
colname varchar(55),
dct int,
tot int
)
create table #colContent (
colname varchar(55),
col_val nvarchar(max),
col_val_count int
)
create table #sqlexecs( s varchar(max))
declare #col_name varchar(max), #sql nvarchar(max), #sql2 nvarchar(max)
declare c cursor for
select name from sys.columns where [object_id]=object_id(#table_name)
open c
fetch next from c into #col_name
while ##FETCH_STATUS = 0
begin
set #sql = 'select cn.name, count(distinct '+#col_name+') as dct_numrow, count('+#col_name+') as tot_numrow from '+#table_name+' join (select name from sys.columns where name = '''+#col_name+''' and [object_id]=object_id('''+#table_name+''')) cn on cn.name = '''+#col_name+''' group by cn.name'
set #sql2 = 'select ' +#col_name+', count('+#col_name+') as colvalcnt from '+#table_name+' group by '+#col_name
--insert into #sqlexecs values (#sql) --uncomment to view sql selects produced by #sql
--insert into #sqlexecs values (#sql2) --uncomment to view sql selects produced by #sql2
insert into #colcount execute sp_executesql #sql
------
declare #d int, #t int
set #d = (select dct from #colcount where colname = #col_name)
set #t = (select tot from #colcount where colname = #col_name)
if (#d <> #t)
begin
insert into #colContent (colname) values (#col_name)
insert into #colContent (col_val,col_val_count) execute sp_executesql #sql2
end
else
begin
insert into #colContent values (#col_name,1,1)
end
fetch next from c into #col_name
end
close c
deallocate c
--select * from #sqlexecs -- uncomment to view sql code produced by #sql and #sql2
select * from #colcount --order by dct desc
select * from #colContent
drop table #colcount
drop table #colContent
drop table #sqlexecs
The first table shows column name, distinct value count, and total value count.
The second table shows column name, distinct values, and the number of times a distinct value appears. If values in column are all distinct (column is a candidate key), colname | 1 | 1 is shown. This should work if copy/pasted, please let me know it doesn't. Dev for use in Dynamics GP.

Logging data changes into table with dynamically changing name in MS SQL

I am trying to log data changes in MS SQL with trigger. I want to create a new History table in every month. After I found the answer how to change table name Dynamically I can't access the DELETED and INSERTED tables anymore. It says invalid object name.
ALTER TRIGGER [dbo].[teszttablatrigger] ON [teszt].[dbo].[teszt] FOR DELETE, INSERT, UPDATE AS
declare #hist nvarchar(40)
set #hist='teszthistory_' + CAST(YEAR(getdate()) as NCHAR(4))+ '_' + (case when Month(GETDATE())<10 then '0' + CAST (Month(GETDATE()) as NCHAR(1))
when Month(GETDATE())>=10 then CAST (Month(GETDATE()) as NCHAR(2)) end)
declare #DynamicSql1 nvarchar(2000)
declare #DynamicSql2 nvarchar(2000)
set #DynamicSql1 = N'IF NOT EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N''[History][dbo].[#hist]'')
AND OBJECTPROPERTY(id, N''IsUserTable'') = 1)
CREATE TABLE [History].[dbo].[#hist] ( kulcs int, szoveg varchar(40), modtip varchar(40), datum datetime default getdate())'
Exec sp_executesql #DynamicSql1, N'#hist nvarchar(40)', #hist=#hist
set #DynamicSql2 = N'INSERT INTO [History].[dbo].[#hist] (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''delete''
FROM DELETED
INSERT INTO [History].[dbo].[#hist] (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''insert''
FROM INSERTED'
Exec sp_executesql #DynamicSql2, N'#hist nvarchar(40)', #hist=#hist
Thanks for the answers in advance.
Dynamic sql is executed in his own scope, so you can't acces inserted/deleted objects.
You could write a SQLCLR trigger in C# look this example SQLCLR Trigger
but I think the easiest way is to use a temp table to write changes to, so the dynamic part is fixed.
Take a look:
DROP TRIGGER [test_history]
GO
CREATE TRIGGER [test_history] ON [test_table]
FOR DELETE, INSERT, UPDATE
AS
BEGIN
declare #date datetime = getdate()
declare #guid uniqueidentifier = newid()
declare #hist nvarchar(40)= 'test_history_' + CAST(YEAR(#date ) as VARCHAR(4))+ '_' + right('0' + CAST(Month(#date) as VARCHAR(2)), 2)
DECLARE #T1 BIT = 0
SELECT top 1 #T1 = 1 FROM sys.tables WHERE [TYPE] = 'U' AND name = 'test_history_9999_99'
IF #T1 = 1 TRUNCATE table test_history_9999_99
DECLARE #T2 BIT = 0
SELECT top 1 #T2 = 1 FROM sys.tables WHERE [TYPE] = 'U' AND name = #hist
IF #T1=0 BEGIN
SELECT ID, [desc], #date DATE_TIME, cast('delete' as varchar(20)) as operation, CAST(#guid AS varchar(64)) BATCH
INTO test_history_9999_99
FROM DELETED
END else begin
INSERT INTO test_history_9999_99
SELECT ID, [desc], #date, cast('delete' as varchar(20)) as operation, CAST(#guid AS varchar(64)) BATCH
FROM DELETED
end
INSERT INTO test_history_9999_99
SELECT ID, [desc], #date, cast('insert' as varchar(20)) as operation, CAST(#guid AS varchar(64)) BATCH
FROM inserted
IF #T2 = 0 BEGIN
EXEC sp_rename 'test_history_9999_99', #hist
END ELSE BEGIN
declare #DynamicSql nvarchar(2000)
SET #DynamicSql = 'INSERT INTO ' + #hist + ' SELECT * FROM test_history_9999_99;'
Exec sp_executesql #DynamicSql
END
END
My test_table contains only two columns ID and [Desc].
In the history tables I have added a DATETIME column with change date and a UNIQUEIDENTIFIER column so you can group all changes in a batch if you INSERT/UPDATE many records with a single operation
Tanks for the answer #MtwStark. Now it works, I can check if the table exists, and create it if not. And have eaccess to the DELETED and INSERTED tables.
I'm not sure, if in your solution I have to create the test_history_9999_99 table in advance. Because when I used your trigger I've got an error about column insertion (I didn't understand the error completly).
Now my code looks like this. I'm not sure if it can handle INSERT/UPDATE many records with a single operation. Probably I still need to insert this code for it? CAST(#guid AS varchar(64)) BATCH . I'm not sure what it really does, I have to look into it deeper.
CREATE TRIGGER [dbo].[teszttablatrigger] ON [teszt].[dbo].[teszt] FOR DELETE, INSERT, UPDATE AS
declare #hist nvarchar(40)
set #hist='teszthistory_' + CAST(YEAR(getdate()) as NCHAR(4))+ '_' + (case when Month(GETDATE())<10 then '0' + CAST (Month(GETDATE()) as NCHAR(1))
when Month(GETDATE())>=10 then CAST (Month(GETDATE()) as NCHAR(2)) end)
select * into #ins from inserted
select * into #del from deleted
declare #DynamicSql nvarchar(2000)
DECLARE #T2 BIT = 0
SELECT top 1 #T2 = 1 FROM sys.tables WHERE [TYPE] = 'U' AND name = #hist
if #T2=0 begin
set #DynamicSql = N'CREATE TABLE [' + #hist + '] ( kulcs int, szoveg varchar(40), modtip varchar(40), datum datetime default getdate())'
Exec sp_executesql #DynamicSql
end
set #DynamicSql = N'INSERT INTO ' + #hist + ' (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''delete''
FROM #del
INSERT INTO ' + #hist + ' (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''insert''
FROM #ins'
Exec sp_executesql #DynamicSql
Try refreshing intellisense. Ctrl+Shift+R see if that might help. Or do a database table refresh.
If you have SQL Server enterprise (check your version) Then better way will be to enable CDC.
https://msdn.microsoft.com/en-us/library/cc645937(v=sql.110).aspx

How can I create a SQL Server view that will return the results of a complex SQL that uses cursors or from a stored procedure?

I have a table that contains schedules and each interval is one record in that table (i.e. 8:00 to 5:00. Monday through Friday would be 5 records in that table). I have been working on a way to easily detect duplicates in a SQL query that can be created as a view. I have a SQL query that does the work using Cursors and a temporary table, but Cursors don't work in Views.
I've even created a Stored Procedure to populate the temporary table, but what I would really like to do is call the stored procedure from a View and show the results.
If I execute the stored procedure, it does return the expected results, however I need to be able to see the results from a Select statement. I cannot give my users the ability to execute stored procedures directly.
Here's the code for the SP.
CREATE PROCEDURE Report_Duplicate_Schedules
AS
BEGIN
DECLARE #Number1 AS INT, #Count1 AS INT, #ST1 AS INT, #ET1 AS INT
DECLARE #Number2 AS INT, #Count2 AS INT, #ST2 AS INT, #ET2 AS INT
DECLARE #Count3 AS INT
CREATE TABLE #SchedulesDuplicates (ScheduleName NVARCHAR(100), ScheduleNumber INT, DuplicateSchedule NVARCHAR(100), DuplicateNumber INT, StartTime INT, StopTime INT);
DECLARE Step1 CURSOR
FOR (SELECT DISTINCT [Number], COUNT(*) AS [Count] FROM [Schedules] GROUP BY [Number])
OPEN Step1
FETCH NEXT FROM Step1 INTO #Number1, #Count1
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE Step2 CURSOR
FOR (SELECT [Number], COUNT(*) AS [Count] FROM [Schedules] WHERE [Number] > #Number1 GROUP BY [Number])
OPEN Step2
FETCH NEXT FROM Step2 INTO #Number2, #Count2
WHILE ##FETCH_STATUS = 0
BEGIN
IF #Count1 = #Count2
BEGIN
--PRINT CAST(#Number1 AS VARCHAR(50)) + N' - ' + CAST(#Count1 AS VARCHAR(50)) + N' - ' + CAST(#Number2 AS VARCHAR(50)) + N' - ' + CAST(#Count2 AS VARCHAR(50))
SELECT #Count3 = COUNT(*) FROM
(SELECT [Number], [StartTime], [StopTime] FROM [Schedules] WHERE [Number] = #Number1) AS z,
(SELECT [Number], [StartTime], [StopTime] FROM [Schedules] WHERE [Number] = #Number2) AS y
WHERE z.[StartTime] = y.[StartTime] AND z.[StopTime] = y.[StopTime]
IF #Count1 = #Count3
BEGIN
--PRINT CAST(#Number1 AS VARCHAR(50)) + N' - ' + CAST(#Count1 AS VARCHAR(50)) + N' - ' + CAST(#Number2 AS VARCHAR(50)) + N' - ' + CAST(#Count2 AS VARCHAR(50)) + N' - ' + CAST(#Count3 AS VARCHAR(50))
INSERT INTO #SchedulesDuplicates ([ScheduleName], [ScheduleNumber], [DuplicateSchedule], [DuplicateNumber], [StartTime], [StopTime]) (SELECT DISTINCT u.[Name], u.[Number], v.[Name], v.[Number], v.[StartTime], v.[StopTime] FROM (SELECT [Name], [Number], [StartTime], [StopTime] FROM [Schedules] WHERE [Number] = #Number1) AS u, (SELECT [Name], [Number], [StartTime], [StopTime] FROM [Schedules] WHERE [Number] = #Number2) AS v)
END
END
FETCH NEXT FROM Step2 INTO #Number2, #Count2
END
CLOSE Step2
DEALLOCATE STEP2
FETCH NEXT FROM Step1 INTO #Number1, #Count1
END
CLOSE Step1
DEALLOCATE Step1
SELECT * FROM #SchedulesDuplicates
DROP TABLE #SchedulesDuplicates
END
GO
You may try the following trick:
SELECT *
FROM OPENQUERY(MyServerName, 'SET FMTONLY OFF EXEC database.dbo.procedure')
Few notes:
DATAACCESS option should be enabled on the server
SET FMTONLY is mostly required for correct columns metadata retrieval
Procedure should have SET NOCOUNT ON statement at the beginning, without it
metadata retrieval fails
However I would advise you to rewrite this row by row processing logic to set based code, which would easily fit into ordinary view. Table-valued user defined function would also do the job.

MERGE without specifying column names in SQL Server 2008

I was looking at the MERGE command which seems cool but still it requires the columns to be specified. I'm looking for something like:
MERGE INTO target AS t
USING source AS s
WHEN MATCHED THEN
UPDATE SET
[all t.fields = s.fields]
WHEN NOT MATCHED THEN
INSERT ([all fields])
VALUES ([all s.fields])
Is it possible?
I'm lazy... this is a cheap proc I wrote that will spit out a general MERGE command for a table. It queries information_schema.columns for column names. I ripped out my source database name - so, you have to update the proc to work with your database (look for #SourceDB... I said it was cheap.) Anyway, I know others could write it much better - it served my purpose well. (It makes a couple assumptions that you could put logic in to handle - namely turning IDENTITY_INSERT OFF - even when a table doesn't have identity columns.) It updates the table in your current context. It was written against sql server 2008 to sync up some tables. Use at your own risk, of course.
CREATE PROCEDURE [dbo].[GenerateMergeSQL]
#TableName varchar(100)
AS
BEGIN
SET NOCOUNT ON
declare #sql varchar(5000),#SourceInsertColumns varchar(5000),#DestInsertColumns varchar(5000),#UpdateClause varchar(5000)
declare #ColumnName varchar(100), #identityColName varchar(100)
declare #IsIdentity int,#IsComputed int, #Data_Type varchar(50)
declare #SourceDB as varchar(200)
-- source/dest i.e. 'instance.catalog.owner.' - table names will be appended to this
-- the destination is your current db context
set #SourceDB = '[mylinkedserver].catalog.myDBOwner.'
set #sql = ''
set #SourceInsertColumns = ''
set #DestInsertColumns = ''
set #UpdateClause = ''
set #ColumnName = ''
set #isIdentity = 0
set #IsComputed = 0
set #identityColName = ''
set #Data_Type = ''
DECLARE #ColNames CURSOR
SET #ColNames = CURSOR FOR
select column_name, COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as IsIdentity ,
COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsComputed') as IsComputed , DATA_TYPE
from information_schema.columns where table_name = #TableName order by ordinal_position
OPEN #ColNames
FETCH NEXT FROM #ColNames INTO #ColumnName, #isIdentity, #IsComputed, #DATA_TYPE
WHILE ##FETCH_STATUS = 0
BEGIN
if #IsComputed = 0 and #DATA_TYPE <> 'timestamp'
BEGIN
set #SourceInsertColumns = #SourceInsertColumns +
case when #SourceInsertColumns = '' THEN '' ELSE ',' end +
'S.' + #ColumnName
set #DestInsertColumns = #DestInsertColumns +
case when #DestInsertColumns = '' THEN '' ELSE ',' end +
#ColumnName
if #isIdentity = 0
BEGIN
set #UpdateClause = #UpdateClause +
case when #UpdateClause = '' THEN '' ELSE ',' end
+ #ColumnName + ' = ' + 'S.' + #ColumnName + char(10)
END
if #isIdentity = 1 set #identityColName = #ColumnName
END
FETCH NEXT FROM #ColNames INTO #ColumnName, #isIdentity, #IsComputed, #DATA_TYPE
END
CLOSE #ColNames
DEALLOCATE #ColNames
SET #sql = 'SET IDENTITY_INSERT ' + #TableName + ' ON;
MERGE ' + #TableName + ' AS D
USING ' + #SourceDB + #TableName + ' AS S
ON (D.' + #identityColName + ' = S.' + #identityColName + ')
WHEN NOT MATCHED BY TARGET
THEN INSERT(' + #DestInsertColumns + ')
VALUES(' + #SourceInsertColumns + ')
WHEN MATCHED
THEN UPDATE SET
' + #UpdateClause + '
WHEN NOT MATCHED BY SOURCE
THEN DELETE
OUTPUT $action, Inserted.*, Deleted.*;
SET IDENTITY_INSERT ' + #TableName + ' OFF'
Print #SQL
END
Not everything you wanted, but partially:
WHEN NOT MATCHED THEN
INSERT([all fields])
VALUES (field1, field2, ...)
(The values list has to be complete, and match the order of the fields in your table's definition.)
Simple alternative to merge without naming any fields or having to update statement whenever table design changes. This is uni-directional from source to target, but it can be made bi-directional. Only acts on changed records, so it is very fast even with linked servers on slower connection.
--Two statement run as transaction batch
DELETE
C
FROM
productschina C
JOIN
(select * from productschina c except select * from productsus) z
on c.productid=z.productid
INSERT into productschina select * from productsus except select * from productschina
Here is code to setup tables to test above:
--Create a target table
--drop table ProductsUS
CREATE TABLE ProductsUS
(
ProductID INT PRIMARY KEY,
ProductName VARCHAR(100),
Rate MONEY
)
GO
--Insert records into target table
INSERT INTO ProductsUS
VALUES
(1, 'Tea', 10.00),
(2, 'Coffee', 20.00),
(3, 'Muffin', 30.00),
(4, 'Biscuit', 40.00)
GO
--Create source table
--drop table productschina
CREATE TABLE ProductsChina
(
ProductID INT PRIMARY KEY,
ProductName VARCHAR(100),
Rate MONEY
)
GO
--Insert records into source table
INSERT INTO ProductsChina
VALUES
(1, 'Tea', 10.00),
(2, 'Coffee', 25.00),
(3, 'Muffin', 35.00),
(5, 'Pizza', 60.00)
GO
SELECT * FROM ProductsUS
SELECT * FROM ProductsChina
GO
I think this answer deserves a little more love. It's simple, elegant and works. However, depending on the tables in question, it may be a little bit slow because the except clause is evaluating every column.
I suspect you can save a little bit of time by just joining on the primary key and the last modified date (if one exists).
DELETE
C
FROM
productschina C
JOIN
(select primary_key, last_mod_date from productschina c except select primary_key, last_mod_date from productsus) z
on c.productid=z.productid
INSERT into productschina select * from productsus except select * from productschina

How to copy a row with every column except identity column (SQL Server 2005)

My code:
SELECT * INTO #t FROM CTABLE WHERE CID = #cid --get data, put into a temp table
ALTER TABLE #t
DROP COLUMN CID -- remove primary key column CID
INSERT INTO CTABLE SELECT * FROM #t -- insert record to table
DROP TABLE #t -- drop temp table
The error is:
Msg 8101,
An explicit value for the identity column in table 'CTABLE' can only
be specified when a column list is used and IDENTITY_INSERT is ON.
And I did set
SET IDENTITY_INSERT CTABLE OFF
GO
DECLARE
#cid INT,
#o INT,
#t NVARCHAR(255),
#c NVARCHAR(MAX),
#sql NVARCHAR(MAX);
SELECT
#cid = 10,
#t = N'dbo.CTABLE',
#o = OBJECT_ID(#t);
SELECT #c = STRING_AGG(QUOTENAME(name), ',')
FROM sys.columns
WHERE [object_id] = #o
AND is_identity = 0;
SET #sql = 'SELECT ' + #c + ' INTO #t
FROM ' + #t + ' WHERE CID = #cid;
INSERT ' + #t + '('+ #c + ')
SELECT ' + #c + ' FROM #t;'
PRINT #sql;
-- exec sp_executeSQL #sql,
-- N'#cid int',
-- #cid = #cid;
However it seems much easier to just build the following SQL and avoid the #temp table altogether:
SET #sql = 'INSERT ' + #t + '(' + #c + ')
SELECT ' + #c + ' FROM ' + #t + '
WHERE CID = #cid;';
PRINT #sql;
-- exec sp_executeSQL #sql,
-- N'#cid int',
-- #cid = #cid;
Try this:
SELECT * INTO #t FROM CTABLE WHERE CID = #cid
ALTER TABLE #t
DROP COLUMN CID
INSERT CTABLE --Notice that INTO is removed here.
SELECT top(1) * FROM #t
DROP TABLE #t
Test Script(Tested in SQL 2005):
CREATE TABLE #TestIDNT
(
ID INT IDENTITY(1,1) PRIMARY KEY,
TITLE VARCHAR(20)
)
INSERT #TestIDNT
SELECT 'Cybenate'
Try specifying the columns:
INSERT INTO CTABLE
(col2, col3, col4)
SELECT col2, col3, col4
FROM #t
Seems like it might be thinking you are trying to insert into the PK field since you are not explicitly defining the columns to insert into. If Identity insert is off and you specify the non-pk columns then you shouldn't get that error.
Here's an example to dynamically build a list of columns - excluding the primary key columns - and execute the INSERT
declare #tablename nvarchar(100), #column nvarchar(100), #cid int, #sql nvarchar(max)
set #tablename = N'ctable'
set #cid = 1
set #sql = N''
declare example cursor for
select column_name
from information_schema.columns
where table_name = #tablename
and column_name not in (
select column_name
from information_schema.key_column_usage
where constraint_name in (select constraint_name from information_schema.table_constraints)
and table_name = #tablename
)
open example
fetch next from example into #column
while ##fetch_status = 0
begin
set #sql = #sql + N'[' + #column + N'],'
fetch next from example into #column
end
set #sql = substring(#sql, 1, len(#sql)-1)
close example
deallocate example
set #sql = N'insert into ' + #tablename + '(' + #sql + N') select top(1) ' + #sql + ' from #t'
--select #sql
exec sp_executesql #sql
If using SQL Server Management Studio and your problems you have too many fields to type them all out except the identity column, then right click on the table and click "Script table as" / "Select To" / "New Query Window".
This will provide a list of fields that you can copy & paste into your own query and then just remove the identity column.
Try invoking the INSERT statement with EXEC:
SELECT * INTO #t FROM CTABLE WHERE CID = #cid
ALTER TABLE #t
DROP COLUMN CID
EXEC('INSERT INTO CTABLE SELECT top(1) * FROM #t')
DROP TABLE #t
You can't do this:
INSERT INTO CTABLE SELECT top(1) * FROM #t
Because the column listings aren't the same. You've dropped the PK column from #t, so you have 1 less column in #t than in CTABLE. This is the equivalent of the following:
INSERT INTO CTABLE(pk, col1, col2, col3, ...)
select top(1) col1, col2, col3, ...
from #t
This wouldn't work for obvious reasons. Similarly, you aren't going to be able to specify the * wildcard to do the insert if you're not inserting all of the columns. The only way to do the insert without including the PK is to specify every column. You can generate a list of columns through dynamic sql, but you'll have to specify them one way or another.

Resources