Deleting 'equivalent' data from two tables - sql-server

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)

Related

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

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.

How to autoincrement the id without identity?

I'm trying do to a bulk insert from another table in sql server. My query is currently like that :
INSERT INTO Table1(Id, Value)
SELECT ??, Value
FROM Table2;
Now, my problem is obviously by what I replace ??. Id is an integer column without an identity property. I would like that for each inserted row, Id take the current max(Id) + 1.
Can I do that directly in my insert command?
If you were using a newer version of SQL Server (2008+) you could try ROW_NUMBER():
DECLARE #BASE INT
SET #BASE = (SELECT IsNull(MAX(ID),0) FROM Table1)
INSERT INTO Table1(Id, Value)
SELECT
#BASE + ROW_NUMBER() OVER (ORDER BY Value) ID,
Value
FROM Table2;
SQL Fiddle
Since you are using SQL Server 2000, you could try like bellow:
DECLARE #BASE INT
SET #BASE = (SELECT IsNull(MAX(ID),0) FROM Table1)
INSERT INTO Table1(Id, Value)
SELECT
#BASE + (SELECT COUNT(*) FROM Table2 AS i2 WHERE i2.Value <= a.Value),
a.Value
FROM Table2 a
But it will only works if Value in Table2 is unique
SQL Fiddle
If Table2 has a primary key (field PK), then you could use:
INSERT INTO Table1(Id, Value)
SELECT
#BASE + (SELECT COUNT(*) FROM Table2 AS i2 WHERE i2.PK <= a.PK),
a.Value
FROM Table2 a
Here is one wicked way.
We create a temp table with identity to generate new ids. This way we avoid the while loop.
DECLARE #CurrentMaxID INT,
#DynamicQuery NVARCHAR(MAX)
--TODO : Acquired table lock here on table1
SELECT #FirstNextID = ISNULL(MAX(Id), 0)
FROM Table1 --WITH(TABLOCK)
CREATE TABLE #TempTableWithID( Table2Id INT,
Table1FuturId INT IDENTITY(1, 1))
INSERT INTO #TempTableWithID(Table2Id)
SELECT Id --Here we use identity to generate table1 futur id
FROM Table2
INSERT INTO Table1(Id, value)
SELECT Temp.Table1FuturId + #FirstNextID,
Table2.Value
FROM Table2
INNER JOIN #TempTableWithID AS Temp ON Table2.Id = Temp.Table2Id
--TODO : release table lock here on table1
DROP TABLE #TempTableWithID
If I'm understanding you correctly, this should work.
CREATE TABLE #tbl1 (ID int, Value float)
CREATE TABLE #tbl2 (ID int, Value float)
INSERT INTO #tbl2 values (4, 2.0)
INSERT INTO #tbl2 values (8, 3.0)
INSERT INTO #tbl2 values (6, 4.0)
INSERT INTO #tbl1 values (1,1.0)
INSERT INTO #tbl1 values (3,3)
INSERT INTO #tbl1 values (9,3)
/*meat and potatoes start*/
INSERT INTO #tbl1(Id, Value)
SELECT (SELECT MAX(ID) FROM #tbl1) + ROW_NUMBER() OVER (ORDER BY Value) ID, Value
FROM #tbl2;
/*meat and potatoes end*/
Select * From #tbl1
drop table #tbl1
drop table #tbl2
Why not IDENT_CURRENT() ?
SELECT IDENT_CURRENT('yourtablename')
It gives you the next ID reference. But this only works if the ID column has IDENTITY turned on.
OR you can try a SEQUENCE and the NEXT VALUE FOR.
i.e.
CREATE TABLE Test.TestTable
(CounterColumn int PRIMARY KEY,
Name nvarchar(25) NOT NULL) ;
GO
INSERT Test.TestTable (CounterColumn,Name)
VALUES (NEXT VALUE FOR Test.CountBy1, 'Syed') ;
GO
SELECT * FROM Test.TestTable;
GO

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

Matching Multiple Rows from a SubQuery into the main query

I would like to match multiple rows (exclusively) from a table to another table, and only return the rows that match ALL. Here is my example:
DECLARE #T1 TABLE (Gr int, Dim char(1), Val int)
INSERT INTO #T1 VALUES (1,'A',10)
INSERT INTO #T1 VALUES (1,'B',200)
INSERT INTO #T1 VALUES (1,'B',201)
INSERT INTO #T1 VALUES (1,'B',202)
INSERT INTO #T1 VALUES (1,'C',22)
INSERT INTO #T1 VALUES (1,'C',23)
INSERT INTO #T1 VALUES (1,'C',24)
DECLARE #T2 TABLE (eDim char(1), eVal int)
INSERT INTO #T2 VALUES ('B', 200)
INSERT INTO #T2 VALUES ('C', 29)
SELECT T1.Gr, T1.Dim, T1.Val FROM #T1 T1
WHERE EXISTS (SELECT * FROM #T2 T2 WHERE T2.eDim = T1.Dim AND T2.eVal = T1.Val)
In the example above, the query returns one row from T1 corresponding to the B-200 value pair. What I really need is to either return two records when both B and C values match T2, or return nothing if one or more of the records to not match. Something like "WHERE EXISTS ALL" but this is not recognized by SQL SERVER.
One to do it
SELECT Gr, eDim Dim, eVal Val
FROM
(
SELECT T1.Gr
FROM #T1 t1 JOIN #T2 t2
ON t1.dim = t2.edim
AND t1.val = t2.eval
GROUP BY t1.gr
HAVING COUNT(*) =
(
SELECT COUNT(*) FROM #T2
)
) q CROSS JOIN #T2
Here is SQLFiddle demo Try to uncomment last INSERT INTO #T1 and run query again
or
SELECT gr, dim, val
FROM
(
SELECT gr, dim, val, COUNT(*) OVER (PARTITION BY gr) cnt
FROM #T1 t1 JOIN #T2 t2
ON t1.dim = t2.edim
AND t1.val = t2.eval
GROUP BY gr, dim, val
) q
WHERE cnt =
(
SELECT COUNT(*) FROM #T2
)
Here is SQLFiddle demo Try to uncomment last INSERT INTO #T1 and run query again

Update between two tables when there is no relation between them

I need a SQL statement which fills the null values from the second column of #T1 table with values from #T2(C1).
There is no foreign key or match between the columns of those two tables.
Sample:
T1 (C1, T2C1)
A1, 1
A2, null
A3, null
A4, 4
A5, null
-------------
T2 (C1)
a
b
After update, the T1 will look like:
A1, 1
A2, a
A3, b
A4, 4
A5, null
I found two approaches:
Using CTE
create table #T1 (C1 varchar(10), T2C1 varchar(10))
create table #T2 (C1 varchar(10))
insert into #T1 values ('A1', '1')
insert into #T1 values ('A2', null)
insert into #T1 values ('A3', null)
insert into #T1 values ('A4', '4')
insert into #T1 values ('A5', null)
insert into #T2 values ('a')
insert into #T2 values ('b')
;with t2 as
(
select C1, row_number() over (order by C1) as Index2
from #T2
)
,t1 as
(
select T2C1, row_number() over (order by C1) as Index1
from #T1
where T2C1 is null
)
update t1
set t1.T2C1 = t2.C1
from t2
where t1.Index1 = t2.Index2
select * from #T1
drop table #T1
drop table #T2
With Derived Tables
create table #T1 (C1 varchar(10), T2C1 varchar(10))
create table #T2 (C1 varchar(10))
insert into #T1 values ('A1', '1')
insert into #T1 values ('A2', null)
insert into #T1 values ('A3', null)
insert into #T1 values ('A4', '4')
insert into #T1 values ('A5', null)
insert into #T2 values ('a')
insert into #T2 values ('b')
update #T1
set T2C1 = cj.C1
from #T1
join (select T2C1, row_number() over (order by C1) as Index1, C1
from #T1
where T2C1 is null) ci on ci.C1 = #T1.C1
join (select C1, row_number() over (order by C1) as Index2
from #T2) cj on ci.Index1 = cj.Index2
select * from #T1
drop table #T1
drop table #T2
My question is, can I achieve this without using windowing functions and with no cursors?
Update
#Damien_The_Unbeliever correctly points that to do this kind of update it is not possible without defining an ordering on tables, actually I think exactly said is without properly identify and link the rows from target table.
#Bogdan Sahlean has found another way, using table variables and IDENTITY column, which I'm happy with this solution, it's another way
However, in the real application I will still use the windowing functions
Thanks all
1.I suppose you have a pk in target table (#T1).
2.Instead of ROW_NUMBER this solution uses IDENTITY(1,1) columns and two table variables.
3.I didn't tested this solution.
DECLARE #t2_count INT = (SELECT COUNT(*) FROM #T2);
DECLARE #Target TABLE
(
MyId INT IDENTITY(1,1) PRIMARY KEY
,T1_pk INT NOT NULL UNIQUE
);
INSERT #Target (T1_pk)
SELECT TOP(#t2_count) pk
FROM #T1
WHERE T2C1 IS NULL;
DECLARE #Source TABLE
(
MyId INT IDENTITY(1,1) PRIMARY KEY
,C1 VARCHAR(10) NOT NULL
);
INSERT #Source (C1)
SELECT C1
FROM #T2;
UPDATE #T1
SET T2C1 = src.C1
FROM #T1 t
INNER JOIN #Target trg ON t.pk = trg.T1_pk
INNER JOIN #Source src ON trg.MyId = src.MyId;
I agree with Damien, anyway when you will rely on the engine and make a premise that the table is ordered by the C1 column (which depends only on the DB and you can not rely on that ) the you could issue an update statement which will update all the rows
declare #a int
set #a = 0
update #t1
set t2c1 = #a,
#a = #a+1
But I wouldn't do that.
I would use a CTE along with ROW_NUMBER:
WITH cte_t1 AS
(
SELECT
T2C1
,ROW_NUMBER() OVER (ORDER BY C1) AS id
FROM T1
WHERE T2C1 IS NULL
)
,cte_t2 AS
(
SELECT
C1
,ROW_NUMBER() OVER (ORDER BY C1) AS id
FROM T2
)
UPDATE t1
SET t1.T2C1 = t2.C1
FROM cte_t1 AS t1
INNER JOIN cte_t2 AS t2
ON t1.id = t2.id
;
The ROW_NUMBER creates an identifier based on the order the columns are in (which I am assuming is what you are looking for, but note that SQL does not have an order instead this query is relying on the physical order of the records which is not a good idea but it looks like that is what you are dealing with). We then join on this identifier and update the records in T1 with the values in T2.
I have placed this solution on SQL Fiddle.
If you can look into create new tables with some type of identifier that links them together, then you would not have to rely on the physical order of the records.

Resources