How to get id's of parent ids for inserting children - sql-server

I have Parent and Child table.
The goal is to duplicate the records, except with new primary keys.
Original Tables
Parent(id)
1
Child(id,parentId, data)
1,1
2,1
After insert:
Parent
1
2
Child
1,1
2,1
3,2
4,2
How do I do that? The part I am having trouble with is getting the new parent key for use with the child records.
This is what I have come up with so far.
--DECLARE VARS
declare #currentMetadataDocumentSetId int = 1, --Ohio
#newMetadataDocumentSetid int = 3; --PA
--CLEANUP
IF OBJECT_ID('tempdb..#tempFileRowMap') IS NOT NULL
/*Then it exists*/
DROP TABLE #tempFileRowMap
--Remove existing file row maps.
delete from file_row_map where metadata_document_set_id = #newMetadataDocumentSetid;
--Create a temptable to hold data to be copied.
Select [edi_document_code],
[functional_group],
[description],
3 as [metadata_document_set_id],
[document_name],
[incoming_file_row_subtype],
[metadata_document_id],
[document_subcode],
[outgoing_file_row_subtype],
[asi_type_code],
[asi_action_code],
[metadata_document_set],
file_row_map_id as orig_file_row_map_id
into #tempFileRowMap
from file_row_map fileRowMap
where metadata_document_set_id = #currentMetadataDocumentSetId;
--Select * from #tempFileRowMap;
Insert into file_row_map select
[edi_document_code],
[functional_group],
[description],
[metadata_document_set_id],
[document_name],
[incoming_file_row_subtype],
[metadata_document_id],
[document_subcode],
[outgoing_file_row_subtype],
[asi_type_code],
[asi_action_code],
[metadata_document_set]
from #tempFileRowMap
--Show Results
Select * from file_row_map fileRowMap where fileRowMap.metadata_document_set_id = #newMetadataDocumentSetid
--Update Detail
Select
[file_row_map_id],
[file_row_column],
[element_code],
[element_metadata_id],
[col_description],
[example],
[translate],
[is_used],
[is_mapped],
[page_num],
[subcode],
[qualifier],
[loop_code],
[loop_subcode],
[default_value],
[delete_flag]
into #tempFileRowMapDetail
from [dbo].[file_row_map_detail] d
left join #tempFileRowMap m
on m.orig_file_row_map_id = d.file_row_map_id
select * from #tempFileRowMapDetail

Simply use OUTPUT clause for getting exact Parent Table Primary Key values.
Lets build Example Schema for your case
--For Capturing inserted ID
CREATE TABLE #ID_CAPTURE (PARENT_ID INT,ORDER_NME VARCHAR(20));
--Your Intermidiate Data To insert into Actual Tables
CREATE TABLE #DUMMY_TABLE (ORDER_NME VARCHAR(20), ITEM_NME VARCHAR(20));
--Actual Tables
CREATE TABLE #ORDER_PARENT (ORDER_ID INT IDENTITY,ORDER_NME VARCHAR(20))
CREATE TABLE #ORDER_CHILD (CHILD_ID INT IDENTITY ,ORDER_ID INT, ORDER_NME VARCHAR(20))
INSERT INTO #DUMMY_TABLE
SELECT 'BILL1','Oil'
UNION ALL
SELECT 'BILL1', 'Gas'
UNION ALL
SELECT 'BILL2', 'Diesel'
Now do Inserts in Parent & Child Tables
INSERT INTO #ORDER_PARENT
OUTPUT inserted.ORDER_ID, inserted.ORDER_NME into #ID_CAPTURE
SELECT DISTINCT ORDER_NME FROM #DUMMY_TABLE
INSERT INTO #ORDER_CHILD
SELECT C.PARENT_ID, ITEM_NME FROM #DUMMY_TABLE D
INNER JOIN #ID_CAPTURE C ON D.ORDER_NME = C.ORDER_NME
SELECT * FROM #ID_CAPTURE
SELECT * FROM #ORDER_CHILD
There are other ways to get Inserted Identity values.
See documentation ##IDENTITY (Transact-SQL) , SCOPE_IDENTITY

Try following approach:
DECLARE #Table1 TABLE (
ID INT NOT NULL PRIMARY KEY,
ParentID INT NULL, -- FK
[Desc] VARCHAR(50) NOT NULL
);
INSERT #Table1 (ID, ParentID, [Desc])
VALUES
(1, NULL, 'A'),
(2, 1, 'AA.1'),
(3, 1, 'AA.2'),
(4, NULL, 'B'),
(5, 4, 'BB.1'),
(6, 4, 'BB.2'),
(7, 4, 'BB.3'),
(8, 7, 'BBB.1');
DECLARE #ParentID INT = 4;
DECLARE #LastID INT = (SELECT TOP(1) ID FROM #Table1 x ORDER BY x.ID DESC)
IF #LastID IS NULL
BEGIN
RAISERROR('Invalid call', 16, 1)
--RETURN ?
END
SELECT #LastID AS LastID;
/*
LastID
-----------
8
*/
DECLARE #RemapIDs TABLE (
OldID INT NOT NULL PRIMARY KEY,
[NewID] INT NOT NULL UNIQUE
);
WITH CteRecursion
AS (
SELECT 1 AS Lvl, crt.ID, crt.ParentID --, crt.[Desc]
FROM #Table1 crt
WHERE crt.ID = #ParentID
UNION ALL
SELECT cld.Lvl + 1 AS Lvl, crt.ID, crt.ParentID --, crt.[Desc]
FROM #Table1 crt
JOIN CteRecursion cld ON crt.ParentID = cld.ID
)
INSERT #RemapIDs (OldID, [NewID])
SELECT r.ID, #LastID + ROW_NUMBER() OVER(ORDER BY r.Lvl) AS [NewID]
FROM CteRecursion r;
--INSERT #Table1 (ID, ParentID, [Desc])
SELECT nc.[NewID] AS ID, np.[NewID] AS ParentID, o.[Desc]
FROM #Table1 o -- old
JOIN #RemapIDs nc /*new child ID*/ ON o.ID = nc.OldID
LEFT JOIN #RemapIDs np /*new parent ID*/ ON o.ParentID = np.OldID
/*
ID ParentID Desc
----------- ----------- --------------------------------------------------
9 NULL B
10 9 BB.1
11 9 BB.2
12 9 BB.3
13 12 BBB.1
*/
Note: with some minor changes should work w. many ParentIDs values.

Related

SQL Server 2008 update table and switch values query optimization

I have a table with values
ID Son Father
----------- ---------- ----------
1 Mark Gerard
2 Gerard Ivan
3 Leo Samuel
4 Samuel Johan
5 Ivan Carles
I need to change table like this:
ID Son Father
----------- ---------- ----------
1 Mark Carles
2 Gerard Carles
3 Leo Johan
4 Samuel Johan
5 Ivan Carles
The goal is to find a major 'Father' and update all 'Son' records with this value. Major 'Father' can be different.
My code is next:
DECLARE #CNT INT
DECLARE #CH_1 NVARCHAR(10)
DECLARE #CH_2 NVARCHAR(10)
CREATE TABLE #PPL (ID INT, Son NVARCHAR(10), Father NVARCHAR(10))
INSERT INTO #PPL VALUES (1, 'Mark', 'Gerard')
INSERT INTO #PPL VALUES (2, 'Gerard', 'Ivan')
INSERT INTO #PPL VALUES (3, 'Leo', 'Samuel')
INSERT INTO #PPL VALUES (4, 'Samuel', 'Johan')
INSERT INTO #PPL VALUES (5, 'Ivan', 'Carles')
SET #I = 1
SET #CNT = (SELECT COUNT(ID) FROM #PPL)
WHILE #I <= #CNT
BEGIN
SET #J = 1
WHILE #J <= #CNT
BEGIN
SET #CH_1 = (SELECT Son FROM #PPL WHERE ID = #J)
SET #CH_2 = (SELECT Father FROM #PPL WHERE ID = #J)
UPDATE #PPL SET Father = #CH_2 WHERE Father = #CH_1
SET #J = #J + 1
END;
SET #I = #I + 1
END;
SELECT * FROM #PPL
DROP TABLE #PPL
This code is working correct, but for the low number of records. How this code can be optimized?
Thanks!
Here is how you can do it with a recursive CTE.
CREATE TABLE #PPL (ID INT, Son NVARCHAR(10), Father NVARCHAR(10))
INSERT INTO #PPL VALUES (1, 'Mark', 'Gerard')
INSERT INTO #PPL VALUES (2, 'Gerard', 'Ivan')
INSERT INTO #PPL VALUES (3, 'Leo', 'Samuel')
INSERT INTO #PPL VALUES (4, 'Samuel', 'Johan')
INSERT INTO #PPL VALUES (5, 'Ivan', 'Carles')
;WITH CTE_FamilyGenealogy
AS
(
SELECT ID
,Son
,Father
,1 AS [Level]
FROM #PPL Ancor
UNION ALL
SELECT CTE_FamilyGenealogy.ID
,CTE_FamilyGenealogy.Son
,Fathers.Father AS Father
,CTE_FamilyGenealogy.[Level] + 1 AS [Level]
FROM #PPL Fathers
INNER JOIN CTE_FamilyGenealogy ON CTE_FamilyGenealogy.Father = Fathers.Son
),
CTE_MajorFathers
AS
(
SELECT ID
,Son
,Father
,ROW_NUMBER() OVER (PARTITION BY Son ORDER BY [Level] DESC) AS RowRank
FROM CTE_FamilyGenealogy
)
SELECT ID
,Son
,Father
FROM CTE_MajorFathers
WHERE RowRank = 1
ORDER BY ID
The Recursive CTE CTE_FamilyGenealogy finds all the father son combination and determine the level within the family tree. The CTE_MajorFathers
CTE uses ROW_NUMBER to rank the possible combinations based on the Level with in the FamilyGenealogy to determine the Major Father.
Try following approach base on recursion (see recursive Common Table Expressions) and HIERARCHYID (SQL2008+) data type. The basic idea is to build for every row one hierarchy value starting from the "first" father :-) and ending with "last" son :-). For example: for first row (1, 'Mark', 'Gerard') this node/family tree is /5/2/1/ where /5/ is "first" father ;-) and /1/ is the "last" son. Next it convert these values to hiearchyid values and it uses GetLevel and GetAncestor methods to compute the "first" father: Father1ID: Johan or Carles.
IF OBJECT_ID('tempdb.dbo.#Results') IS NOT NULL
BEGIN
DROP TABLE #Results;
END
CREATE TABLE #Results (ID INT NOT NULL PRIMARY KEY, Father1ID INT);
WITH CteRec
AS (
-- It returns Father only rows
SELECT l1.ID, l1.Son, l1.Father, CONVERT(VARCHAR(900), '/'+LTRIM(l1.ID)+'/') AS Node -- FamilyTree
FROM #PPL AS l1 -- First level
WHERE NOT EXISTS(SELECT * FROM #PPL p WHERE p.Son = l1.Father)
UNION ALL
-- It returns Son only and Son-Father rows
SELECT ln.ID, ln.Son, ln.Father, CONVERT(VARCHAR(900), prt.Node+LTRIM(ln.ID)+'/') AS Node -- FamilyTree
FROM #PPL AS ln -- Next level
JOIN CteRec AS prt ON prt.Son = ln.Father
)
INSERT #Results (ID, Father1ID)
SELECT ID,
Father1ID = CONVERT(INT,REPLACE(CONVERT(HIERARCHYID, Node).GetAncestor(CONVERT(HIERARCHYID, Node).GetLevel()-1).ToString(),'/',''))
FROM CteRec;
SELECT p.*, r.Father1ID, rp.Father AS Father1Name
FROM #PPL p
INNER JOIN #Results r ON p.ID = r.ID
INNER JOIN #PPL rp ON r.Father1ID = rp.ID
-- Also you ca use #Result with UPDATE statement but I would store this values within new column Father1
Recursive CTE's are overrated =)
This simple approach will run through just as fast (on normal data), will never complain about max recursion and is easy to read. The only downside I can see is that it might go into an eternal loop when the data is corrupt.
CREATE TABLE #PPL (ID INT, Son NVARCHAR(10), Father NVARCHAR(10))
INSERT INTO #PPL VALUES (1, 'Mark', 'Gerard')
INSERT INTO #PPL VALUES (2, 'Gerard', 'Ivan')
INSERT INTO #PPL VALUES (3, 'Leo', 'Samuel')
INSERT INTO #PPL VALUES (4, 'Samuel', 'Johan')
INSERT INTO #PPL VALUES (5, 'Ivan', 'Carles')
DECLARE #rowcount int = -1
WHILE #rowcount <> 0
BEGIN
UPDATE upd
SET Father = new.Father
FROM #PPL upd
JOIN #PPL new
ON new.Son = upd.Father
WHERE upd.Father <> new.Father
SELECT #rowcount = ##ROWCOUNT
END
SELECT * FROM #PPL
PS: it probably helps to have an index on the Son column when running on large datasets.

How to Execute a Stored Procedure in a Join Statement SQL Server

I want to call a stored procedure in a join statement of the Select Query.
For example,
Select *
from test
left join test1 on GetID(test.id)=test1.id
The idea is to match one to many relationship.
The structure of tables would be
Table: Test
ID Name
1 abc
2 te
Table: Test1
Id TestID Name
1 1 xxx
2 1 yyy
3 1 zzz
4 2 aaa
Stored procedure:
Create procedure GETID
#id int
as
begin
select top 1 id
from test1
where testid = #id
end
You can convert the stored procedure into an inline table-valued function or you can put the query inside an OUTER APPLY:
SELECT *
FROM test t
OUTER APPLY(
SELECT TOP 1 id
FROM test1
WHERE testid = t.testid
)x
Use a scalar function instead.
CREATE FUNCTION GETID
(
#id int
)
RETURNS int
AS
BEGIN
return (select top 1 id from test1 where testid=#id)
END
Or, review methods in post: Get top 1 row of each group
Use cross apply (or outer apply), which executes once on right side of query.
Or, use row_number() over partition to rank the group rows and select based on rank.
declare #test table (id int, name varchar(100))
insert into #test (id, name) values (1, 'abc')
insert into #test (id, name) values (1, 'te')
declare #test1 table (id int, testid int, name varchar(100))
insert into #test1 (id, testid, name) values (1, 1, 'xxx')
insert into #test1 (id, testid, name) values (2, 1, 'yyy')
insert into #test1 (id, testid, name) values (3, 1, 'zzz')
insert into #test1 (id, testid, name) values (4, 2, 'aaa')
Select * from #test t
cross apply (select top 1 * from #test1
where testid = t.id
order by id) -- change group order as needed
as t1

How to update table when rows of one table are different from the other

I have a table table1 and a temporary table temp2. Temp2 contains updated values which i want to update in table1. So, for any rows that are different i want to update the values from Temp2 to table 1. I tried something like this but its not working.
update Role_Master set Role_Desc=Role_Descc , Role_Version_Number =Role_Version_Number+1,Role_Dept=Role_Deptt,Role_All_Clients=Role_All_Clientss,
Role_Admin=Role_Adminn,Role_Super_Admin=Role_Super_Adminn,Role_Modified_Date = GETDATE(),Role_Modified_By = 'T6086' FROM #TEMP1 where Role_ID in
(SELECT #TEMP1.Role_IDD FROM #TEMP1 LEFT JOIN Role_Master ON (#TEMP1.Role_Descc = Role_Master.Role_Desc and #Temp1.Role_Deptt=Role_Master.Role_Dept)
WHERE Role_Master.Role_Desc is null and Role_Master.Role_Dept IS NULL)
hard to help you without knowing the schema of the two tables ... but it should be possible to join the two tables and decide by a where condition which rows to update ... check out this simple example ... maybe it helps
create table #temp1 (id int, val nvarchar(100))
create table #temp2 (id int, val nvarchar(100))
insert into #temp1 (id, val) values (1, 'eins')
insert into #temp1 (id, val) values (2, 'eins')
insert into #temp1 (id, val) values (3, 'eins')
insert into #temp2 (id, val) values (1, 'zwei')
insert into #temp2 (id, val) values (2, 'eins')
insert into #temp2 (id, val) values (3, 'eins')
update #temp1 set #temp1.val = b.val
from #temp1 a join #temp2 b on a.id = b.id
where a.val <> b.val
select ##rowcount -- returns 1 because 1 row was updated
select * from #temp1

How do I find records out of order - SQL?

Let's say I have a table with an ID Identity column, some data, and a datestamp. Like this:
1 data 5/1/2013 12:30
2 data 5/2/2013 15:32
3 data 5/2/2013 16:45
4 data 5/3/2013 9:32
5 data 5/5/2013 8:21
6 data 5/4/2013 9:36
7 data 5/6/2013 11:42
How do I write a query that will show me the one record that is timestamped 5/4? The table has millions of records. I've done some searching, but I don't know what to call what I'm searching for. :/
declare #t table(id int, bla char(4), timestamp datetime)
insert #t values
(1,'data','5/1/2013 12:30'),
(2,'data','5/2/2013 15:32'),
(3,'data','5/2/2013 16:45'),
(4,'data','5/3/2013 9:32'),
(5,'data','5/5/2013 8:21'),
(6,'data','5/4/2013 9:36'),
(7,'data','5/6/2013 11:42')
select timestamp
from
(
select rn1 = row_number() over (order by id),
rn2 = row_number() over (order by timestamp), timestamp
from #t
) a
where rn1 not in (rn2, rn2-1)
in 2008 r2, this would be a way
DECLARE #Table AS TABLE
(id INT , ladate DATETIME)
INSERT INTO #Table VALUES (1, '2013-05-01')
INSERT INTO #Table VALUES (2, '2013-05-02')
INSERT INTO #Table VALUES (3, '2013-05-03')
INSERT INTO #Table VALUES (4, '2013-05-05')
INSERT INTO #Table VALUES (5, '2013-05-04')
INSERT INTO #Table VALUES (6, '2013-05-06')
INSERT INTO #Table VALUES (7, '2013-05-07')
INSERT INTO #Table VALUES (8, '2013-05-08')
--I added the records in the sort order but if not just make sure you are sorted in the query
SELECT t2.ladate FROM #Table T1
INNER JOIN #Table T2 ON T1.Id = T2.Id + 1
INNER JOIN #Table t3 ON t2.id = t3.id + 1
WHERE t3.ladate < t2.ladate AND t2.ladate > t1.ladate
-- I made the assumption that your Id are all there, 1,2,3,4,5.... none missing... if there are rownumbers missing, you can use row_number()

Deleting 'equivalent' data from two tables

I need to perform the following pseudo logic in a SQL Server 2012 procedure, based around a table variable and a table declared as such:
DECLARE #tmp TABLE
(
ID int IDENTITY(1,1),
UserID int NOT NULL,
SgsID int NOT NULL
)
CREATE TABLE #Table1
(
ID int IDENTITY(1,1),
UserID int NOT NULL,
SgsID int NOT NULL
)
For each row of data in table variable #tmp
Delete rows from Table1 where UserID/SgsID combinations match UserID/SgsID in Table1
Delete those UserID/SgsID combinations from #tmp that have been deleted from Table1
I've been researching different approaches, such as using OUTPUT INTO and INTERSECT, but cannot write a query that deletes across two tables (in fact I don't think it is even possible).
I have achieved the above steps by using the following code, however, I was wondering if any T-SQL pro's may be able to suggest a more succinct/efficient approach?
See SQLFiddle for online version
CREATE TABLE #Table1
(
ID int IDENTITY(1,1),
UserID int NOT NULL,
SgsID int NOT NULL
)
INSERT INTO #Table1 (UserID, SgsID) VALUES (5, 99)
INSERT INTO #Table1 (UserID, SgsID) VALUES (10, 89)
INSERT INTO #Table1 (UserID, SgsID) VALUES (150, 79)
INSERT INTO #Table1 (UserID, SgsID) VALUES (200, 69)
INSERT INTO #Table1 (UserID, SgsID) VALUES (250, 59)
SELECT * FROM #Table1
DECLARE #tmp TABLE
(
ID int IDENTITY(1,1),
UserID int NOT NULL,
SgsID int NOT NULL
)
INSERT INTO #tmp (UserID, SgsID) VALUES (150, 79)
INSERT INTO #tmp (UserID, SgsID) VALUES (200, 69)
INSERT INTO #tmp (UserID, SgsID) VALUES (250, 59)
INSERT INTO #tmp (UserID, SgsID) VALUES (999, 49)
SELECT * FROM #tmp
DECLARE #tbl_commonRows TABLE (UserID int, SgsID int)
INSERT INTO #tbl_commonRows
(
UserID,
SgsID
)
SELECT
UserID,
SgsID
FROM
#Table1
INTERSECT
SELECT
UserID,
SgsID
FROM
#tmp
DELETE FROM
#Table1
WHERE
(ID IN (
SELECT
ID
FROM
#Table1 t1 INNER JOIN
#tbl_commonRows c ON c.UserID = t1.UserID AND c.SgsID = t1.SgsID))
DELETE FROM
#tmp
WHERE
(ID IN (
SELECT
ID
FROM
#tmp t2 INNER JOIN
#tbl_commonrows c ON c.UserID = t2.UserID AND c.SgsID = t2.SgsID))
SELECT * FROM #Table1
SELECT * FROM #tmp
DROP TABLE #Table1
Here's solution:
DECLARE #tmp_ids TABLE (
id1 INT,
id2 INT
)
INSERT INTO #tmp_ids (id1, id2)
SELECT
t1.id,
t2.id
FROM Table1 t1
INNER JOIN tmp t2
on (t1.UserID = t2.UserID AND t1.SgsID = t2.SgsID)
DELETE FROM Table1
WHERE id IN (SELECT id1 FROM #tmp_ids)
DELETE FROM tmp
WHERE id IN (SELECT id2 FROM #tmp_ids)
Keep in mind - i created physical tables tmp and Table1
You can take advantage of the fact that the OUTPUT command can take more than INSERTED and DELETED columns for deletes (but not inserts, sadly):
DECLARE #output TABLE (id int)
DELETE FROM tbl
OUTPUT tmp.ID INTO #output(id)
FROM #Table1 tbl
JOIN #tmp tmp
ON tbl.UserID = tmp.UserID
AND tbl.SgsID = tmp.SgsID
DELETE FROM tmp
FROM #tmp tmp
JOIN #Output outp ON tmp.id = outp.id
Have you looked into using MERGE for this? Might be another option, and the syntax is nice and easy to follow.
MERGE (Transact-SQL)

Resources