Update in Batches Never Finishes - sql-server

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.

Related

Skipping already updated row

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

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)

Concurrent create or select statement failing with deadlock error

Where running the following SQL statement currently (maybe 30 or 40 times at the same time) and is giving us deadlock errors but we're not sure which statements are interfering with each other. There are no indexes on the Things table besides a PK and a Fk to ThingTypes table. We're also wondering if adding an index to ThingTypeId and HourId will help resolve the issue. Lastly it's also safe for us to assume that {#thingTypeID and #hourID} are unique for all the concurrent queries and the if is only there for if this was re-run at a later period in time.
Transaction (Process ID 237) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction
Code:
IF NOT EXISTS(select top(1) id from [eddsdbo].[Things] (UPDLOCK)
where [ThingTypeID] = #thingTypeID and [HourID] = #hourID)
BEGIN
INSERT INTO [eddsdbo].[Things]
([ThingTypeID]
,[HourID])
VALUES
(#thingTypeID
,#hourID)
SELECT m.*, mt.SampleType
FROM [eddsdbo].[Things] as m
inner join [eddsdbo].[ThingTypes] as mt on mt.Id = m.ThingTypeID
WHERE m.ID = ##IDENTITY
END
ELSE
BEGIN
SELECT m.*, mt.SampleType
FROM [eddsdbo].[Things] as m
inner join [eddsdbo].[ThingTypes] as mt on mt.Id = m.ThingTypeID
where [ThingTypeID] = #thingTypeID and [HourID] = #hourID
END
We've been chasing down this issue for a while so any help is appreciated.
Not sure this will fix it
But do you you really need the WHERE m.ID = ##IDENTITY
IF NOT EXISTS(select top(1) id from [eddsdbo].[Things] (UPDLOCK)
where [ThingTypeID] = #thingTypeID and [HourID] = #hourID)
BEGIN
INSERT INTO [eddsdbo].[Things]
([ThingTypeID], [HourID])
VALUES (#thingTypeID, #hourID)
END
SELECT m.*, mt.SampleType
FROM [eddsdbo].[Things] as m
inner join [eddsdbo].[ThingTypes] as mt
on mt.Id = m.ThingTypeID
and [ThingTypeID] = #thingTypeID
and [HourID] = #hourID
A single statement is a transaction
I think this will have less overhead
DECLARE #t AS TABLE (id int identity primary key, thingTypeID int, hourID int);
declare #thingTypeID int = 1, #hourID int = 2;
insert into #t (thingTypeID, hourID)
values (#thingTypeID, #hourID);
select *
from #T
where thingTypeID = #thingTypeID and hourID = #hourID;
insert into #t (thingTypeID, hourID)
select #thingTypeID, #hourID
where not exists (select 1 from #t where thingTypeID = #thingTypeID and hourID = #hourID);
select *
from #T
where thingTypeID = #thingTypeID and hourID = #hourID;
set #thingTypeID = 1;
set #hourID = 3;
insert into #t (thingTypeID, hourID)
select #thingTypeID, #hourID
where not exists (select 1 from #t where thingTypeID = #thingTypeID and hourID = #hourID);
select *
from #T
where thingTypeID = #thingTypeID and hourID = #hourID;
select *
from #T
order by id;

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

SQL Server : check if all rows exists in other table

I need to know if all rows from one table exists in other:
declare #Table1 table (id int)
declare #Table2 table (id int)
insert into #Table1(id) values (1)
insert into #Table1(id) values (4)
insert into #Table1(id) values (5)
insert into #Table2(id) values (1)
insert into #Table2(id) values (2)
insert into #Table2(id) values (3)
if exists (select id from #Table1 where id in (select id from #Table2))
select 'yes exists'
else
select 'no, doesn''t exist'
This query returns yes exists but should return no, doesn't exist because only 1 exists in #Table2, values 4 and 5 don't.
What should I change in my query? Thanks!
IF NOT EXISTS (
SELECT ID FROM #Table1
EXCEPT
SELECT ID FROM #Table2
)
SELECT 'yes exists'
ELSE SELECT 'no, doesn''t exist'
You could use EXCEPT to get the set difference of both tables. If any ID's are returned, both tables are not equal:
SELECT ID
FROM #Table1
EXCEPT
SELECT ID
FROM #Table2
EXCEPT returns any distinct values from the left query that are not also found on the right query.
So, to get your "no, doesnt exist":
;WITH diff AS(
SELECT ID
FROM #Table1
EXCEPT
SELECT ID
FROM #Table2
)
SELECT CASE WHEN COUNT(diff.ID) = 0
THEN 'yes exists'
ELSE 'no, doesnt exist'
END AS Result
FROM diff
select case when count(*) > 0 then 'no' else 'yes' end as AllExist
from #Table1 t1
left outer join #Table2 t2 on t1.id = t2.id
where t2.id is null
This would work as long as both id columns are unique (which they should be if they are id's)
DECLARE #totalRows int;
SET #totalRows = SELECT count(*) from Table1;
RETURN (#totalRows == SELECT count(*) from Table1 JOIN Table2 on Table1.id = Table2.id)

Resources