Refactoring SQL to avoid using TABLOCKX - sql

I have two tables like this:
Table1 Table2
----------------------------------
Table1Id IDENTITY Table2Id
Table2Id NOT NULL SomeStuff
SomeOtherStuff
With a foreign key constraint on Table2Id between them. It goes without saying (yet I'm saying it anyway) that a Table2 row needs to be inserted before its related Table1 row. The nature of the procedure that loads both tables does so in bulk set operations, meaning I have a whole bunch of Table1 and Table2 data in a #temp table that was created with an IDENTITY column to keep track of things. I am currently doing the inserts like this (transaction and error handling omitted for brevity):
DECLARE #currentTable2Id INT
SET #currentTable2Id = IDENT_CURRENT('dbo.Table2')
INSERT INTO dbo.Table2 WITH (TABLOCKX)
( SomeStuff,
SomeOtherStuff
)
SELECT WhateverStuff,
WhateverElse
FROM #SomeTempTable
ORDER BY SomeTempTableId
INSERT INTO dbo.Table1
( Table2Id )
SELECT #currentTable2Id + SomeTempTableId
FROM #SomeTempTable
ORDER BY SomeTempTableId
This works fine, all of the relationships are sound after the inserts. However, due to the TABLOCKX, we are running into constant situations where people are waiting for each other's queries to finish, whether it be this "load" query, or other UPDATES and INSERTS (I'm using NOLOCK on selects). The nature of the project calls for a lot of data to be loaded, so there are times when this procedure can run for 20-30 minutes. There's nothing I can do about this performance. Trust me, I've tried.
I cannot use SET IDENTITY_INSERT ON, as the DBAs do not allow users to issue this command in production, and I think using IDENTITY_INSERT would require a TABLOCKX anyways. Is there any way I can do this sort of insert without using a TABLOCKX?

Make sure you have a ID field in #SomeTempTable. Create a new column TempID in Table2. Insert the ID from #SomeTempTable to TempID when you add rows to Table2. Use column TempID in a join when you insert into Table1 to fetch the auto incremented Table2ID.
Something like this:
alter table Table2 add TempID int
go
declare #SomeTempTable table(ID int identity, WhateverStuff int, WhateverElse int)
insert into #SomeTempTable values(1, 1)
insert into #SomeTempTable values(2, 2)
insert into Table2(SomeStuff, SomeOtherStuff, TempID)
select WhateverStuff, WhateverElse, ID
from #SomeTempTable
insert into Table1(Table2Id)
select Table2ID
from #SomeTempTable as S
inner join Table2 as T2
on S.ID = T2.TempID
go
alter table Table2 drop column TempID
Instead of add and drop of the TempID column you can have it in there but you need to clear it before every run so old values from previous runs don't mix up your joins.

I assume that you're using tablockx in an attempt to prevent anything else from inserting into Table2 (and thus incrementing the identity value) for the duration of your process. Try this instead
DECLARE #t TABLE (Table2Id int), #currentTable2Id int
INSERT INTO dbo.Table2
( SomeStuff,
SomeOtherStuff
)
OUTPUT INSERTED.Table2Id into #t
SELECT WhateverStuff,
WhateverElse
FROM #SomeTempTable
ORDER BY SomeTempTableId
SELECT #currentTable2Id = Table2Id FROM #t
INSERT INTO dbo.Table1
( Table2Id )
SELECT #currentTable2Id + SomeTempTableId
FROM #SomeTempTable
ORDER BY SomeTempTableId
DELETE #t

Related

How to Lock multiple tables for Insert command in Sql

This is my table Structure
When I insert Data into the first table, it will have multiple entries in table 2
I am Using code
To Get ID
Select MAX(ID)+1 From Table1
To Insert Data
Insert Into Table1 Values('1','abc','add1');
Insert into table2 values('1','med','english');
Insert into table2 values('1','eng','english');
Code is working fine for single computer but when we used in application in multiple terminals it is inserting wrong data i.e. data of another id in table2
You need ensure that the ID column of table1 is an identity column and then do the following:
DECLARE #ID INT
INSERT table1 ([columns])
VALUES (...)
SELECT #ID = SCOPE_IDENTITY()
INSERT table2 (table1ID, [columns])
VALUES (#ID, ...)
You can read more about SCOPE_IDENTITY() here.
MAX(ID) will include ID values created by other processes, which is why your second insert is mixing up data.

Joining multiple table Sql trigger

Hi I am newbie to SQL trigger. since I tried and searched on online and I dont find any clear outcome.
so here is my problem.
I have three tables:
TABLE1 :
ID NAME (columns )
1 prabhu
TABLE2 :
Id COUNTRY (columns )
1 India
I want this to send to log table if anything like insert/update happen in table2
The SQL(DB2) trigger has to do the following and the result should be in log table like this
LOGTABLE:
ID NAME COUNTRY
1 prabhu India
Your help really appreciated.
Try this,
-- Create tables
create table table1(id int, empName varchar(20));
create table table2(id int, country varchar(20));
create table logtable(id int, empName varchar(20), country varchar(20));
-- Create trigger
CREATE TRIGGER logtableAfterInsert ON table2
after INSERT,DELETE,UPDATE
AS
BEGIN
declare #empid int;
declare #empname2 varchar(20);
declare #empcountry varchar(20);
select #empid=i.id from inserted i;
select #empcountry=i.country from inserted i;
select #empname2=tbl1.empName from table1 tbl1 where tbl1.id=#empid;
insert into logtable values(#empid,#empname2,#empcountry);
PRINT 'Inserted'
END
GO
After that insert the values,
insert into table1 values(1, 'prabhu');
insert into table2 values (1, 'India');
Check the results,
select * from table1;
select * from table2;
select * from logtable;
Hope this resolves...
BTW, You need to add the foreign key constraint.
CREATE OR REPLACE TRIGGER logtableAfterUpdate
AFTER UPDATE ON table2
REFERENCING NEW AS NAUDIT OLD AS OAUDIT
FOR EACH ROW MODE DB2SQL
--BEGIN --ATOMIC
insert into logtable
values(
(select id from table2 tbl2 where tbl2.id =OAUDIT.id),
(select empName from table1 tbl1 where tbl1.id=(select id from table2 tbl2 where tbl2.id =OAUDIT.id)),
(select country from table2 tbl2 where tbl2.id =OAUDIT.id)
);
--END;

Optimizing deletes from table using UDT (tsql)

SQL Server.
I have a proc that takes a user defined table (readonly) and is about 7500 records large. Using that UDT, I run about 15 different delete statements:
delete from table1
where id in (select id from #table)
delete from table2
where id in (select id from #table)
delete from table3
where id in (select id from #table)
delete from table4
where id in (select id from #table)
....
This operation, as expected, does take a while (about 7-10 minutes). These columns are indexed. However, I suspect there is a more efficient way to do this. I know deletes are traditionally slower, but I wasn't expecting this slow.
Is there a better way to do this?
You can test/try "exists" instead of "IN". I really don't like IN clauses for anything besides casual lookup-queries. (Some people will argue about IN until they are blue in the face)
Delete deleteAlias
from table1 deleteAlias
where exists ( select null from #table vart where vart.Id = deleteAlias.Id )
You can populate a #temp table instead of a #variableTable. Again, over the years, this has been trial and test it out. #variable vs #temp , most of the time, doesn't make that big of a different. But in about 4 situations I had, going to a #temp table made a big impact.
You can also experiment with putting an index on the #temp table (the "joining" column, 'Id' in this example )
IF OBJECT_ID('tempdb..#Holder') IS NOT NULL
begin
drop table #Holder
end
CREATE TABLE #Holder
(ID INT )
/* simulate your insert */
INSERT INTO #HOLDER (ID)
select 1 union all select 2 union all select 3 union all select 4
/* CREATE CLUSTERED INDEX IDX_TempHolder_ID ON #Holder (ID) */
/* optional, create an index on the "join" column of the #temp table */
CREATE INDEX IDX_TempHolder_ID ON #Holder (ID)
Delete deleteAlias
from table1 deleteAlias
where exists ( select null from #Holder holder where holder.Id = deleteAlias.Id )
IF OBJECT_ID('tempdb..#Holder') IS NOT NULL
begin
drop table #Holder
end
IMHO, there is not clear cut answer, sometimes you gotta experiment a little.
And "how your tempdb is setup' is a huge fork in the road that can affect #temp table performance. But try the suggestions above first.
And one last experiment
Delete deleteAlias
from table1 deleteAlias
where exists ( select 1 from #table vart where vart.Id = deleteAlias.Id )
change the null to "1".... once I saw this affect something. Weird, right?

Updating Values of a table from same table without using a select query

My Requirement
Updating Values of a table from same table without using a select query
this query won't effect any rows.
My aim : Update val2 of #table where slno=1 with the value of val2 of slno=2
Is there any other way without doing this method
Declare #val2 nvarchar(50)
select #val2=val2 from #table where slno=2
update #table set val2=#val2 where slno=1
create table #table
(
slno int identity(1,1),
val nvarchar(50),
val2 nvarchar(50)
)
insert into #table(val,val2)values('1',newID())
insert into #table(val,val2)values('1',newID())
insert into #table(val,val2)values('1',newID())
select * from #table
update #table set val2=T.val2
from #table T where slno=1 and T.slno=2
drop table #table
I have lot of records in the table.
So If i am selecting and update it may effect the performance.
Please, provide more info.
Do you have only 2 rows in your table?
Why do you need this kind of update?
I suppose, that your db structure is wrong, but I can't tell exactly, until you explain why do you need this.
Anyway I can suggest a poor way to do this without using select. You can self join the table. It would be better to have addition column, but if you don't have it, how's you should do
UPDATE T1
SET T1.val2 = T2.val2
FROM #table T1 INNER JOIN #table T2
ON T1.slno = 1 AND T2.slno = 2

Alternative to row level triggers?

MS SQL Server doesn't have row level triggers, correct? If I needed to insert a row from within a trigger and then insert another row, based on the result of the first insert, would a cursor be the best solution?
For example, is there a better way to do this:
CREATE TABLE t1 (foo int)
CREATE TABLE t2 (id int IDENTITY, foo int)
CREATE TABLE t3 (t2_id int)
GO
CREATE TRIGGER t1_insert_trg ON t1 FOR INSERT AS
DECLARE c CURSOR FOR
SELECT foo FROM inserted
DECLARE #foo int
OPEN c
FETCH NEXT FROM c INTO #foo
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO t2 (foo) VALUES (#foo)
INSERT INTO t3 (t2_id) VALUES (##IDENTITY)
FETCH NEXT FROM c INTO #foo
END
CLOSE c
DEALLOCATE c
I assume you are on 2005 or better? If so, look into the OUTPUT clause, you shouldn't need row-level triggers. For example:
USE tempdb;
GO
CREATE TABLE t1 (foo int);
CREATE TABLE t2 (id int IDENTITY, foo int);
CREATE TABLE t3 (t2_id int);
GO
CREATE TRIGGER t1_insert ON t1
FOR INSERT AS
BEGIN
DECLARE #new_rows TABLE(new_id INT, old_foo INT);
INSERT t2(foo)
OUTPUT inserted.id, inserted.foo
INTO #new_rows
SELECT foo
FROM inserted;
INSERT t3 SELECT new_id FROM #new_rows;
END
GO
INSERT t1(foo) SELECT 1 UNION ALL SELECT 5;
SELECT * FROM t1;
SELECT * FROM t2;
SELECT * FROM t3;
GO
DROP TABLE t1,t2,t3;
You could also manage this by having a trigger on T1 that inserts into T2, then a trigger on T2 that inserts into T3. This isn't going to be as efficient IMHO, and is not easier to manage, but I will submit that it is easier to follow (and may be your only option if you are stuck on 2000). Both could be set-based and wouldn't need cursors or any other row-by-row processing method.
USE tempdb;
GO
CREATE TABLE t1 (foo int);
CREATE TABLE t2 (id int IDENTITY, foo int);
CREATE TABLE t3 (t2_id int);
GO
CREATE TRIGGER t1_insert ON t1
FOR INSERT AS
BEGIN
INSERT t2(foo)
SELECT foo FROM inserted;
END
GO
CREATE TRIGGER t2_insert ON t2
FOR INSERT AS
BEGIN
INSERT t3(t2_id)
SELECT id FROM inserted;
END
GO
INSERT t1(foo) SELECT 1 UNION ALL SELECT 5;
SELECT * FROM t1;
SELECT * FROM t2;
SELECT * FROM t3;
GO
DROP TABLE t1,t2,t3;
(BTW, if you are going for identity values, use SCOPE_IDENTITY(), not ##IDENTITY.)
You might be able to avoid a cursor or the need to know what identity was inserted using the following inserts.
Insert INTO t2 (foo) Select foo from inserted
Insert into t3 (t2_id) Select t2.id from t2
inner join inserted i on t2.foo = i.foo
Why not cascade the triggers - Use an INSERT trigger on T2 to perform the insert on T3. Then you can avoid the cursor within t1_insert_trg and just use inserted - as in:
CREATE TRIGGER t1_insert_trg ON t1 FOR INSERT AS
INSERT INTO t2
SELECT foo FROM inserted -- fires t2 INSERTED trigger
CREATE TRIGGER t2_insert_trg ON t2 FOR INSERT AS
INSERT INTO t3
SELECT id FROM inserted