Skipping already updated row - sql-server

I would like to update a table column with a counter stored in a another table that is incremented on each update. All is working fine. However, if I reset the counter to a new number that happens to already exist in the table to update, for example, 7; during the update, it will find the 7 we updated in the first loop and update it along with other values of 7.
Is there a way to skip already updated values and only update those that are not? I am contemplating adding a column with a flag to track already updated rows; which I delete after the update. However, I feel there could be a better way of doing this. Any ideas?
This is what I currently have:
/**counter table**/
create table ctl (cid int)
insert into ctl values (1)
/**table to update**/
create table tbl1(tid int)
insert into tbl1 (tid)
values(1),(1),(1),(1),(2),(3),(3),(3),(3),(3),(4),(5),(6),(7),(7)
/**temp table**/
select tid into #tmptbl from tbl1
declare #tidNum int
declare #cctl int
select #cctl = (select cid from ctl)
while exists (select tid from #tmptbl)
begin
select #tidNum = (select top 1 tid from #tmptbl order by tid asc)
update tbl1 set tid=#cctl where tid=#tidNum
select #cctl=#cctl+1
update ctl set cid=#cctl
delete #tmptbl where tid=#tidNum
end
select * from tbl1
drop table #tmptbl

/**counter table**/
create table ctl (cid int)
insert into ctl values (1)
/**table to update**/
create table tbl1(tid int)
insert into tbl1 (tid)
values(1),(1),(1),(1),(2),(3),(3),(3),(3),(3),(4),(5),(6),(7),(7)
go
begin transaction
declare #foo table(cid int);
declare #base int;
update ctl
set cid = cid + (select count(distinct tid) from tbl1)
output deleted.cid into #foo(cid);
select #base = cid
from #foo;
update t
set tid = addme + thebase
from
(
select tid, dense_rank() over(order by tid)-1 as addme, #base as thebase
from tbl1
) as t
--error handling goes here
commit transaction
go
select *
from tbl1;
update ctl
set cid = 4;
go
begin transaction
declare #foo table(cid int)
declare #base int
update ctl
set cid = cid + (select count(distinct tid) from tbl1)
output deleted.cid into #foo(cid);
select #base = cid
from #foo
update t
set tid = addme + thebase
from
(
select tid, dense_rank() over(order by tid)-1 as addme, #base as thebase
from tbl1
) as t
--error handling goes here
commit transaction
go
select *
from tbl1;
go

Related

Update in Batches Never Finishes

as a follow up on my question original question posted here
UPDATE in Batches Does Not End and Remaining Data Does Not Get Updated
If you use the logic below you'll see that update never finishes. Let me know if you have any ideas why...
Table 1
IF OBJECT_ID('tempdb..#Table2') IS NOT NULL
BEGIN
DROP TABLE #Table2;
END
CREATE TABLE #Table2 (ID INT);
DECLARE #Count int = 0;
WHILE (select count(*) from #Table2) < 10000 BEGIN
INSERT INTO #Table2 (ID)
VALUES (#Count)
-- Make sure we have a unique id for the test, else we can't identify 10 records
set #Count = #Count + 1;
END
Table 2
IF OBJECT_ID('tempdb..#Table1') IS NOT NULL
BEGIN
DROP TABLE #Table1;
END
CREATE TABLE #Table1 (ID INT);
DECLARE #Count int = 0;
WHILE (select count(*) from #Table1) < 5000 BEGIN
INSERT INTO #Table1 (ID)
VALUES (#Count)
-- Make sure we have a unique id for the test, else we can't identify 10 records
set #Count = #Count + 1;
END
/****************** UPDATE ********************/
select count (*) from #Table2 t2 where Exists (select * from #Table1 t1 where t1.ID = t2.ID)
select count (*) from #Table2 where ID = 0
select count (*) from #Table1 where ID = 0
-- While exists an 'un-updated' record continue
WHILE exists (select 1 from #Table2 t2 where Exists (select * from #Table1 t1 where t1.ID = t2.ID) )
BEGIN
-- Update any top 10 'un-updated' records
UPDATE t2
SET ID = 0
FROM #Table2 t2
WHERE ID IN (select top 10 id from #Table2 where Exists (select * from #Table1 t1 where t1.ID = t2.ID) )
END
Your UPDATE statement is referencing the wrong instance on #Table2. You want the following:
UPDATE t2 SET
ID = 0
FROM #Table2 t2
WHERE ID IN (
SELECT TOP 10 ID
-- note this alias is t2a, and is what the `exists` needs to reference
-- not the table being updated (`t2`)
FROM #Table2 t2a
WHERE EXISTS (SELECT 1 FROM #Table1 t1 WHERE t1.ID = t2a.ID)
)
Note: For testing ensure that #Count starts from 1 not 0 else you do still end up with an infinite loop.

I need to insert rows in bulk like in 1000's using triggers having different value in one column

Here is the trigger
CREATE TRIGGER [dbo].[Teacher]
ON [dbo].[Teacher]
After INSERT
AS
Declare #fid int, #PR NVARCHAR(MAX),#Mycounter as INT
Select top 1 #fid = eid from human where TypeID = 2
order by NewID()
Select top 1 #PR = Pid from [dbo].[Program] Where Depid = 1
order by NewID()
Set #Mycounter =1
While #Mycounter <5
BEGIN
Insert Into HeadofDep(SessionID,fid,pid,name,createddate)
Select SessionID, #fid,#PR,NULL,null from INSERTED
Where eid in (Select eid from human where TypeID = 3)
set #MyCounter = #MyCounter + 1;
END
I need to insert 1000's of rows in HeadofDep table when any row is inserted in Teacher table. I have done by applying looping but all rows that get inserted in HeadofDep table have same #PR. Need it different against each row.
Also need sessionid incremented.
How can I achieve that?
Just, increment the SessionID then and put the other stuff in the loop:
Declare #fid int, #PR NVARCHAR(MAX),#Mycounter as INT
Set #Mycounter =1
While #Mycounter <5
BEGIN
Select top 1 #fid = eid from human where TypeID = 2
order by NewID()
Select top 1 #PR = Pid from [dbo].[Program] Where Depid = 1
order by NewID()
Insert Into HeadofDep(SessionID,fid,pid,name,createddate)
Select SessionID, #fid,#PR,NULL,null from INSERTED
Where eid in (Select eid from human where TypeID = 3)
set #MyCounter = #MyCounter + 1;
END
Also, doing such LOOPs in triggers is bad.In this case, you can change 1000 inserts with one like this:
Insert Into HeadofDep(SessionID,fid,pid,name,createddate)
Select SessionID + N, #fid,#PR,NULL,null
from INSERTED
CROSS APPLY
(
SELECT TOP (1000) -1+row_number() over(order by t1.number) as N
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
) DS
Where eid in (Select eid from human where TypeID = 3)

Creating duplicates with a different ID for test in SQL

I have a table with 1000 unique records with one of the field as ID. For testing purpose, my requirement is that To update the last 200 records ID value to the first 200 records ID in the same table. Sequence isn't mandatory.
Appreciate help on this.
Typically I charge for doing other ppls homework, don't forget to cite your source ;)
declare #example as table (
exampleid int identity(1,1) not null
, color nvarchar(255) not null
);
insert into #example (color)
select 'black' union all
select 'green' union all
select 'purple' union all
select 'indigo' union all
select 'yellow' union all
select 'pink';
select *
from #example;
declare #max int = (select max(exampleId) from #example);
declare #min int = #max - 2
;with cte as (
select top 2 color
from #example
)
update #example
set color = a.color
from cte a
where exampleid <= #max and exampleid > #min;
select *
from #example
This script should solve the issue and will cover scenarios even if the id column is not sequential.I have included the comments to help you understand the joins and the flow of the script.
declare #test table
(
ID int not null,
Txt char(1)
)
declare #counter int = 1
/*******This variable is the top n or bottom n records in question it is 200 ,
for test purpose setting it to 20
************/
declare #delta int = 20
while(#counter <= 50)
begin
Insert into #test values(#counter * 5,CHAR(#counter+65))
set #counter = #counter + 1
end
/************Tag the records with a row id as we do not know if ID's are sequential or random ************/
Select ROW_NUMBER() over (order by ID) rownum,* into #tmp from #test
/************Logic to update the data ************/
/*Here we first do a self join on the tmp table with first 20 to last 20
then create another join to the test table based on the ID of the first 20
*/
update t
set t.ID = tid.lastID
--Select t.ID , tid.lastID
from #test t inner join
(
Select f20.rownum as first20 ,l20.rownum as last20,f20.ID as firstID, l20.ID lastID
from #tmp f20
inner join #tmp l20 on (f20.rownum + #delta) = l20.rownum
)tid on tid.firstID = t.ID and tid.first20 < = #delta
Select * from #test
drop table #tmp

Updating Next_ID column

I have the following table:
VehicleID Reg_ID Next_RegID EntryDate
330034 9111 NULL 2010-12-06 00:00:00
330034 9113 NULL 2010-12-09 00:00:00
On the first row I need to update the Next_RegId column with the Reg_ID of the second row where VehicleId or (VIN/ChassisNumber) is the same. The Next_RegID column on the last entry should remain Null.
I've created a while loop procedure which works perfectly, but with millions of records in the table it takes ages to complete. Therefore, I was wondering if any of you dealt with this kind of a problem and have a solution for it.
Here's the procedure I wrote, and thanks in advance for all your help:
Declare #i as integer;
Declare #x as integer;
Declare #y as integer
Set #i= (Select Max(RID) from TempRegistration)
Set #x= 0
Set #y= 1
Declare #curChassis as nvarchar(100)
Declare #nextChassis as nvarchar(100)
While (#x <= #i)
Begin
set #curChassis = (Select ChassisNumber from TempRegistration where RID = #x)
set #nextChassis = (Select ChassisNumber from TempRegistration where RID = #y)
If (#curChassis = #nextChassis)
Begin
Update Registration set NextRegistrationId = (Select RegistrationId from TempRegistration where RID = #y)
Where RegistrationId = (Select RegistrationId from TempRegistration where RID = #x)
End
Set #x = #x + 1
Set #y = #y + 1
Print(#x)
End
TempRegistration is a temporary table I've created to assign a row_id which guides the while loop to assign the Reg_ID to the Next_RegId on the previous row.
This can be done with one UPDATE query. You haven't mentioned your RDBMS so...
For MSSQL:
Update Registration as t1
set NextRegistrationId = (Select TOP 1 RegistrationId
from Registration
where RID = t1.RID
and EntryDate>t1.EntryDate
order by EntryDate DESC)
For MySQL
Update Registration as t1
set NextRegistrationId = (Select RegistrationId
from Registration
where RID = t1.RID
and EntryDate>t1.EntryDate
order by EntryDate DESC
LIMIT 1)
If RID's are increasing with EntryDate then
Update Registration as t1
set NextRegistrationId = (Select MIN(RegistrationId)
from Registration
where RID = t1.RID
and EntryDate>t1.EntryDate
)
Tested and it seems to be working but this version uses a CTE (SQL Server)
with RegDetails as
(
select VehicleID, Reg_ID, ROW_NUMBER() OVER(PARTITION BY VehicleID ORDER BY EntryDate) AS ROWNUMBER
FROM dbo.Vehicle)
UPDATE a SET a.Next_RegID = b.Reg_ID
FROM RegDetails b
INNER JOIN dbo.Vehicle a ON (a.VehicleID = b.VehicleID)
WHERE b.ROWNUMBER = 2 and a.Next_RegID IS NULL and a.Reg_ID != b.Reg_ID

T-SQL UPDATE using self join for a table variable

Imagine there is a table:
declare #tab table (id int, val int)
insert into #tab(id, val)
values (1,10),(2,20),(1,15)
there is a need to update the table and set for every id the sum of all values with same ids in the table
update #tab
set val = (select sum(val) from #tab tab where tab.id = id)
The where clause of the last query is always true and therefore the every row would contain the sum of all values in the table.
If the table was real (not table variable) I would reference it using the table name:
update realtab
set val = (select sum(val) from #tab tab where tab.id = realtab.id)
It is possible to make such an update for table variables?
Try using UPDATE ... FROM
update t
set val = (select sum(val) from #tab tab where tab.id = t.id)
FROM #tab t
UPDATE realtab
SET val = (select sum(val) from #tab tab where tab.id = realtab.id)
FROM #tab realtab
You can also use a CTE:
WITH q AS
(
SELECT *,
SUM(val) OVER (PARTITION BY id) AS sum_val
FROM #tab
)
UPDATE q
SET val = sum_val

Resources