Cascade copy of rows in sql - sql

I found this thread here: http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=16836
I have exactly the same problem. Quote:
Rob Pearmain writes "I have 3 tables that hold questions.
Table 1 : Question
Field : ID (Unique) Field : Name (Text)
Table 2 : Question Text (References Table1-ID)
Field : ID (Unique) Field : QuestionID (integer ref to Table1 ID)
Field : Text
Table 3 : Options
Field : ID (Unique) Field : QuestionTextID (integer ref to Table2 ID)
Field : Text
Say for example, I create a question with 2 Question text records and
5 option records. If I wanted to duplicate that question to a new
question, and copy over the Question Text records to new ID's, and all
the related options, how can I do this easily (As the duplicate
question will have a new ID, each of the duplicated question text's
will have new ID's as will each of the options)."
The suggested solution is:
create procedure CopyQuestion
#idtocopy int
AS
declare #tempquestionid
declare #tempquestiontextid
declare #questiontextid
insert into question (name)
select name from question where id = #idtocopy
select #tempquestionid = ##identity
declare question_cursor cursor for
select id from [question text] where id = #idtocopy
open question_cursor
fetch next from question_cursor into #questiontextid
while ##fetch_status = 0
begin
insert into [question text] (questionid, text)
select #tempquestionid, text from [question text] where id = #questiontextid
select #tempquestiontextid = ##identity
insert into [options] (questiontextid, text)
select #tempquestiontextid, text from [options] where questiontextid = #questiontextid
fetch next from question_cursor into #questiontextid
end
close question_cursor
deallocate question_cursor
Is there a better solution to this problem? I will use an insert trigger.
Thanks!

You can use the merge statement with the output clause to get a match between the old and new id in questionText. This is described in this question Using merge..output to get mapping between source.id and target.id.
In your case the code would look something like this. The code is not tested so there might be any number of typos in there but it shows what you can do.
create procedure CopyQuestion
#idtocopy int
as
declare #QuestionID int
insert into question
select Name
from question
where ID = #idtocopy
select #QuestionID = scope_identity()
declare #IDs table (NewQID int, OldQID int)
merge questionText as T
using (select ID, #QuestionID as QuestionID, Field
from questionText
where QuestionID = #idtocopy) as S
on 0=1
when not matched then
insert (QuestionID, Field) values (QuestionID, Field)
output inserted.ID, S.ID into #IDs;
insert into options
select
I.NewQID,
O.Field
from options O
inner join #IDs as I
on O.QuestionTextID = I.OldQID

This is another way to do the same thing a little bit more set based. In my below example I used a temp table to map the IDs between the two new tables. Also please remove spaces from your table names (just because you can doesn't mean you should).
CREATE PROCEDURE udf_COPY_QUESTION
#ID_TO_COPY int
as
BEGIN TRANSACTION
BEGIN TRY
DECLARE #NEW_QUESTION_ID INT, #MAX_ID INT
insert into question (name)
select name from question where id = #ID_TO_COPY
SET #NEW_QUESTION_ID = SCOPE_IDENTITY()
SET #MAX_ID =IDENT_CURRENT( 'question text' )
select #NEW_QUESTION_ID AS questionid,
Text,
ROW_NUMBER() OVER (ORDER NAME) + #MAX_ID as new_text_id,
id as old_text_id
INTO #TEMP from [question text]
where questionid = #ID_TO_COPY
insert into [question text] (QuestionID,Text)
select questionid,Text from #TEMP
order by new_text_id
insert into Options (questiontextid, text)
select t.new_text_id,o.Text from options o
inner join #temp t on t.old_text_id = o.questiontextid
COMMIT TRANSACTION
END TRY
BEGIN CATCH
RAISERROR('COPY FAILED',10,1)
ROLLBACK TRANSACTION
END CATCH

Related

Update Trigger For Multiple Rows

I am trying to Insert data in a table named "Candidate_Post_Info_Table_ChangeLogs" whenever a record is updated in another table named "Candidate_Personal_Info_Table". my code works fine whenever a single record is updated but when i try to updated multiple rows it gives error:
"Sub query returned more then 1 value".
Following is my code :
ALTER TRIGGER [dbo].[Candidate_PostInfo_UPDATE]
ON [dbo].[Candidate_Post_Info_Table]
AFTER UPDATE
AS
BEGIN
IF ##ROWCOUNT = 0
RETURN
DECLARE #Candidate_Post_ID int
DECLARE #Candidate_ID varchar(50)
DECLARE #Action VARCHAR(50)
DECLARE #OldValue VARCHAR(MAX)
DECLARE #NewValue VARCHAR(MAX)
DECLARE #Admin_id int
IF UPDATE(Verified)
BEGIN
SET #Action = 'Changed Verification Status'
SET #Candidate_Post_ID = (Select ID From inserted)
SET #Candidate_ID = (Select Identity_Number from inserted)
SET #NewValue = (Select Verified From inserted)
SET #OldValue = (Select Verified From deleted)
IF(#NewValue != #OldValue)
BEGIN
INSERT INTO Candidate_Post_Info_Table_ChangeLogs(Candidate_Post_ID, Candidate_ID, Change_DateTime, action, NewValue, OldValue, Admin_ID)
VALUES(#Candidate_Post_ID, #Candidate_ID, GETDATE(), #Action, #NewValue, #OldValue, '1')
END
END
END
i have searched stack overflow for this issue but couldn't get any related answer specific to this scenario.
When you insert/update multiple rows into a table, the Inserted temporary table used by the system holds all of the values from all of the rows that were inserted or updated.
Therefore, if you do an update to 6 rows, the Inserted table will also have 6 rows, and doing something like this:
SET #Candidate_Post_ID = (Select ID From inserted)
Will return an error, just the same as doing this:
SET #Candidate_Post_ID = (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6)
From the looks of things, you tried to do this with an iterative approach. Set-based is better. Maybe consider doing it like this in the body of your TRIGGER (without all of the parameters...):
IF UPDATE(Verified)
BEGIN
INSERT INTO Candidate_Post_Info_Table_ChangeLogs
(
Candidate_Post_ID
,Candidate_ID
,Change_DateTime
,action
,NewValue
,OldValue
,Admin_ID
)
SELECT
I.ID
,I.Identity_Number
,GETDATE()
,'Changed Verification Status'
,I.Verified
,O.Verified
,'1'
FROM Inserted I
INNER JOIN Deleted O
ON I.ID = O.ID -- Check this condition to make sure it's a unique join per row
WHERE I.Verified <> O.Verified
END
A similar case was solved in the following thread using cursors.... please check it
SQL Server A trigger to work on multiple row inserts
Also the below thread gives the solution based on set based approach
SQL Server - Rewrite trigger to avoid cursor based approach
*Both the above threads are from stack overflow...

Insert Query to insert multiple rows in a table via select and output clause. SQL Server 2008

I have a created a stored procedure (please ignore syntax errors)
alter proc usp_newServerDetails
(#appid int, #envid int, #serType varchar(20), #servName varchar(20))
as
declare #oTbl_sd table (ID int)
declare #outID1
declare #oTbl_cd table (ID int)
declare #outID2
begin Transaction
insert into server_details(envid, servertype, servername)
output inserted.serverid into #oTbl_sd(ID)
values(#envid, #serType, #servName)
select #outID1 = ID from #oTbl_sd
insert into configdetails(serverid, servertype, configpath, configtype)
output inserted.configid into #oTbl_cd(ID)
(select #outID1, cm.servertype, cm.configpath, cm.configtype
from configpthmaster cm
where cm.appid = #appid )
select #outID2 = ID from #oTbl_cd
insert into configkeydetails(confiid, keyname)
output inserted.Keyid into #oTbl_ckd(ID)
(select #outID2, cm.key
from configpthmaster cm
where cm.appid = #appid)
begin
commit
end
server_details table has an identity column ID with is auto-generated ie. #outID1 and first insert query inserts only 1 row.
configpthmaster table is not related to any other table directly and has 2 unique data rows, which I want to fetch to insert data into other tables, one by one during insertion.
The second insert query fetch data from configpthmaster table
and insert 2 rows in configdetails while generating (auto-generated) ID ie. #outID2.
It also has a FK mapped to server_details.
The problem is "#outID2" giving last inserted ID only (ie. if two id generated 100,101 i am getting 101) which eventually on 3rd insertion, inserting 2 rows with same id 101 only but i want the insertion should be linear. i.e one for 100 and other for 101.
If zero rows affected while insertion how to rollback the transaction?
How can I achieve these requirements? Please help.
Change your procedure like below,and try again.
ALTER PROCEDURE usp_newServerDetails(#appid int, #envid int,#serType varchar(20),#servName varchar(20))
AS
BEGIN
BEGIN TRY
DECLARE #Output TABLE (ID int,TableName VARCHAR(50),cmKey VARCHAR(50)) --table variable for keeping Inserted ID's
BEGIN TRAN
IF EXISTS ( SELECT 1 FROM configpthmaster cm WHERE cm.appid = #appid )
AND ( SELECT 1 FROM configkeydetails ck WHERE ck.appid = #appid ) --add a conditon to satisfy the valid insertions
BEGIN
INSERT INTO server_detials(envid,servertype,servername)
OUTPUT inserted.serverid,'server_detials',NULL INTO #Output(ID,TableName,cmKey )
VALUES(#envid ,#serType ,#servName)
INSERT INTO configdetails(serverid,servertype,configpath,configtype)
OUTPUT inserted.configid,'configdetails',cm.Key INTO #Output(ID,TableName,cmKey )
SELECT t.ID,cm.servertype,cm.configpath,cm.configtype
FROM configpthmaster cm
CROSS APPLY (SELECT ID FROM #Output WHERE TableName='server_detials')t
WHERE cm.appid = #appid
INSERT INTO configkeydetails(configId,keyname)
SELECT ID,cmKey FROM #Output
WHERE TableName='configdetails'
END
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK
END CATCH
END
Could you try this solution?
alter proc usp_newServerDetails(#appid int, #envid int,#serType varchar(20),#servName varchar(20))
as
declare #oTbl_sd table (ID int)
declare #outID1
declare #oTbl_cd table (ID int)
declare #outID2
begin Transaction
insert into server_detials(envid,servertype,servername)
output inserted.serverid into #oTbl_sd(ID)
values(#envid ,#serType ,#servName)
select #outID1 = ID from #oTbl_sd
insert into configdetails(serverid,servertype,configpath,configtype)
output inserted.configid into #oTbl_cd(ID)
(select #outID1 ,cm.servertype,cm.configpath,cm.configtype from configpthmaster cm where cm.appid = #appid )
select #outID2 = ID from #oTbl_cd
insert into configkeydetails(confiid,keyname)
output inserted.Keyid into #oTbl_ckd(ID)
(select isnull(replace(stuff((SELECT inserted.configid FOR xml path('')), 1, 1, ''), '&', '&'), '') ,cm.key, from configpthmaster cm where cm.appid = #appid )
begin
commit
end
I just added STUFF in your code.
The STUFF function inserts a string into another string.
Do take note that using STUFF drastically slows the processing time of the code.
for more information about STUFF

SSMS Temp Table / Parameter Issue

I have a temp table with 2 columns, each column is a parameter I've declared. I've done so using this sql.
Declare
#SourceKey varchar(40) = '1109'
,#Department Key varchar(1500) = '14,55
The table is then populated using the following sql:
if OBJECT_ID('Tempdb..#Department','U') is not null
drop table #Department
CREATE TABLE #Department
(DepartmentKey int
,BaseTerm varchar(5))
INSERT INTO #Department
SELECT value
,skt.Key from YYY.ParseList(#Department,',')
join #SourceKeyTable skt
on skt.Key = skt.key
If I select * From #Department I get these results:
Department Key | SourceKey
14 | 1109
55 | 1109
Thats what I expect. I then join the temp table to my main query like so
JOIN #Department d
on Table.rKey = d.DepartmentKey
I need to have a temp table to allow for a multi-select in the visual studio report. However, with the department key equal to 14 AND 55 its skewing my results. I need 1 value passed 14 OR 55 not both. But the temp table is neccessary for the multi-select.
Any suggestions on how to pass only 1 value while still having set up mentioned previously?
I'll do my best to answer questions as I might not have explained this question well enough for some.
I reckon you need to parse your list into a temporary table or table variable and then do whatever needs to be done with it.
It's difficult to see from your code exactly what that would involve but the code below should illustrate the idea sufficiently.
I create a table variable. Insert the parsed list values and then cycle through them printing the values to output
--Create a table variable
DECLARE #Departments TABLE(DepartmentOrder int identity, RKey nvarchar(40) NOT NULL)
--Create variables to loop through table variable
DECLARE #DepartmentOrder int
DECLARE #RKey nvarchar(40)
--Populate table variable with the parsed list values
INSERT #Departments (RKey) SELECT RKey from YYY.ParseList(#Department,',')
--Get the first list entry
SELECT #DepartmentOrder = min(DepartmentOrder ) FROM #Departments
--While we've not reached the end
WHILE #DepartmentOrder IS NOT NULL
BEGIN
--Get the Department key for this entry
SET #RKey = (SELECT RKey FROM #Departments WHERE DepartmentOrder = #DepartmentOrder )
--Use the values
PRINT '#DepartmentOrder = '+CONVERT(nvarchar(9),#DepartmentOrder )
PRINT '#RKey = '''+#RKey +''''
--Get the next list entry
SET #DepartmentOrder = (SELECT MIN(DepartmentOrder ) FROM #Departments WHERE DepartmentOrder > #DepartmentOrder )
END

How do I only select records in a table that have all the values from another table?

This is a hard question to word, but is pretty simple when explained a little more.
I have two tables: Standard_Test{StandardID int, TestID int} and Test{TestID int}
Standard_Test: http://i51.tinypic.com/2u60ket.png
Test: http://i51.tinypic.com/2bbqxj.png
I need to select a list of StandardID's that have all of the TestID's from the Test table associated to it. In the example above, this query would only select StandardID 5 & 6 because they both have TestID's 1,2,3(all of the TestID's from Test) associated with it.
It sounds simple, but I have not been able to come up with the proper query. Thanks in advanced!
You could try this, it should work:
SELECT DISTINCT st.StandardId
FROM Standard_Test st JOIN Test t
ON st.TestId = t.TestId
GROUP BY st.StandardId
HAVING COUNT(st.TestId) = (SELECT COUNT(TestId) FROM Test)
This is relational division. Google for that term and you should find all the info you need.
This should work for any number of rows in Test. (It assumes that a given TestId only appears once in Test -- it is the primary key, right?)
SELECT st.StandardID
from Standard_Test st
inner join Test te
on te.TestID = st.TestID
group by st.StandardID
having count(te.TestId) = (select count(*) from Test)
The Below Code will Work To find your Requirement.
Check it out.
Here Tab1 = Standard_Test And tab2 = Test
Declare #Test Int
Declare #Tab table
(
Stand int
)
Declare #Cur Cursor
Set #Cur = CURSOR For
Select standardID From tab1
Group by standardID
Open #Cur
Fetch Next
From #Cur into #Test
WHILE ##FETCH_STATUS = 0
Begin
If Exists (Select 1
From tab2
Where estID not in (Select testId From tab1 Where StandardID = #Test))
Begin
insert into #Tab
values(#Test)
End
Fetch Next
From #Cur into #Test
End
Close #Cur
DEALLOCATE #Cur
Select * From #Tab

T SQL Looping on insert or update

I have two tables.
Table A and Table B. The columns are same.
create table TableA (
id int
, name varchar
, last datetime
)
create table TableB (
id int
, name varchar
, last datetime
)
I m populating table A with mass data. and i would like to either insert or update the data in table A into table B.
I d like to take the data from table A and either insert into table B if id and name doenst match or update if the id and name does match.
I tried some ETL tool but the result was very slow. I have indexing on id and name, I wanted to try this with SQL.
I have the following but not working correct:
SELECT #id = ID,
#name = name,
#LSDATE = LastSeen_DateTime
FROM DBO.A
IF EXISTS (SELECT ID, name FROM DBO.A
WHERE #ID = ID AND #name = Name)
begin
-- update
end
else
begin
--insert
end
i guess i need to put this in a loop and not quite sure how I can make this run.
Thanks.
Its probably faster to do it two statements one update and one insert rather than a loop
This statement updates all B rows using the data from A where the ID is the same but the name is different
Update
Update
tableB
SET
name = a.Name
From
tableB a
INNER JOIN tableA a
on b.ID = a.ID
and A.Name <> b.Name
This statement inserts all B rows into A where the id doesn't exist in A
INSERT
INSERT INTO
tableB
( ID,
Name
)
SELECT
a.ID
a.Name
FROM
tableA b
WHERE
not exists (Select A.ID From tableB a WHERE a.ID = b.ID)
Updated (reversed it from A into B rather than B into A)
If you were using SQL Server 2008 (or Oracle or DB2), then you could use a merge statement.
MERGE B
USING A AS source
ON (B.ID = source.ID and B.Name = source.Name)
WHEN MATCHED THEN
UPDATE SET Last = source.Last
WHEN NOT MATCHED BY TARGET THEN
INSERT (ID, Name, Last) VALUES (source.ID, source.Name, source.Last)
-- the following is optional, if you remove it, add a semicolon to the end of the above line.
OUTPUT $action,
inserted.ID AS SourceID, inserted.Name AS SourceName,
inserted.Last AS SourceLast,
deleted.ID AS TargetID, deleted.Name AS TargetName,
deleted.Last AS TargetLast ;
The bit with the "output $action" will display what rows are getting updated and what rows are getting updated.
weasel words: I recognize this isn't exactly what you were looking for, but since others may search this topic, it may be helpful for others in the future.
DECLARE #id int
DECLARE #name nvarchar
DECLARE #last datetime
DECLARE TableA_Cursor CURSOR FOR
select id
, name
, last
from TableA;
OPEN TableA_Cursor;
FETCH NEXT from TableA_Cursor
INTO #id, #name, #last;
WHILE ##FETCH_STATUS = 0
BEGIN
IF (EXISTS select 1 from TableB b where b.Id = #id)
update TableB
set Name = #name
, Last = #last
ELSE
insert into TableB (Id, Name, Last)
values (#id, #name, #last)
FETCH NEXT from TableA_Cursor
INTO #id, #name, #last
END
CLOSE TableA_Cursor;
DEALLOCATE TableA_Cursor;
There may be some syntax error, particularly around the IF condition, but you may get the point.