I'm trying to insert a lot of records to a table.
This is the scenario:
SQL Server 2008 (DB is 2005)
The destination table has a Clustered Index (PK). This field should be an Identity, but the developer of the DB (we couldn't change it, as it will affect the program) create it as an Integer. Everytime the program needs to add a row to the table, look at the max id (historyno on this case) and sum one.
This affect our performance when we need to insert a lot of records at the same time, so we create a process to insert rows from a temporary table (AKT_ES_CampTool_TempHist) out of production hours.
The problem is that, in one hour, it only inserts 8K rows. Considering that we need to insert more than 120K, we run out of hours.
The code we use is the following. Please, if someone has any idea to improve it, it will be appreciate.
DECLARE #HistNo AS INT
WHILE EXISTS (SELECT * FROM AKT_ES_CampTool_TempHist WHERE Inserted = 0)
BEGIN
SELECT #HistNo=MIN(HistoryNo) FROM AKT_ES_CampTool_TempHist WHERE Inserted = 0
INSERT INTO NOVADB.dbo.niHist (
HistoryNo,ObjectType,ObjectNo,SubNo,ReferenceNo,
Time,Type,Priority,Collector,Code,
Action,RemainingAmount,Obliterated,SubType,ActSegment,
Data,FreetextData,quantity
)
SELECT
(SELECT max(historyNo)+1
FROM NOVADB..niHist),ObjectType,ObjectNo,SubNo,ReferenceNo,
Time,Type,Priority,Collector,Code,
Action,RemainingAmount,Obliterated,SubType,ActSegment,
Data,FreetextData,quantity
FROM AKT_ES_CampTool_TempHist
WHERE HistoryNo=#HistNo
UPDATE AKT_ES_CampTool_TempHist
SET Inserted=1
WHERE HistoryNo=#HistNo
END
obviously the proper answer is to change that historyNo column to an identity, but as you can't do that why not use ROW_NUMBER over the entire set to get an incrementing number to add to the prev max historyNo?
Then you could alter the insert to just
DECLARE #OldMaxHistNo AS INT
SELECT #OldMaxHistNo = MAX(historyNo) FROM NOVADB..niHist
INSERT INTO NOVADB.dbo.niHist (
HistoryNo,ObjectType,ObjectNo,SubNo,ReferenceNo,
Time,Type,Priority,Collector,Code,
Action,RemainingAmount,Obliterated,SubType,ActSegment,
Data,FreetextData,quantity
)
SELECT
#OldMaxHistNo+ ROW_NUMBER() OVER(ORDER BY ObjectNo)
FROM NOVADB..niHist),ObjectType,ObjectNo,SubNo,ReferenceNo,
Time,Type,Priority,Collector,Code,
Action,RemainingAmount,Obliterated,SubType,ActSegment,
Data,FreetextData,quantity
FROM AKT_ES_CampTool_TempHist
WHERE Inserted = 0
UPDATE AKT_ES_CampTool_TempHist
SET Inserted=1
Might have to lock the tables inside a transaction whilst doing it though
You could select the data which should be inserted into an temporary table with a new HistoryNo generated by Rownumber() and changed with max(historyNo) FROM NOVADB..niHist.
SELECT ROW_NUMBER() OVER (Order by ID) as NEW_HistoryNo , *
into #tmp
FROM AKT_ES_CampTool_TempHist
WHERE Inserted = 0
ORDER BY HistoryNo
Update #tmp set NEW_HistoryNo=NEW_HistoryNo + (SELECT max(historyNo) FROM NOVADB..niHist)
INSERT INTO NOVADB.dbo.niHist (
HistoryNo,ObjectType,ObjectNo,SubNo,ReferenceNo,
Time,Type,Priority,Collector,Code,
Action,RemainingAmount,Obliterated,SubType,ActSegment,
Data,FreetextData,quantity ) )
SELECT
NEW_HistoryNo,ObjectType,ObjectNo,SubNo,ReferenceNo,
Time,Type,Priority,Collector,Code,
Action,RemainingAmount,Obliterated,SubType,ActSegment,
Data,FreetextData,quantity
from #tmp
Update AKT_ES_CampTool_TempHist set Inserted = 1
from #tmp
Where #tmp.HistoryNo=AKT_ES_CampTool_TempHist.HistoryNo and AKT_ES_CampTool_TempHist.Inserted = 0
Drop Table #tmp
You should never use the max+1 strategy you are using for assigning an index. Assuming you can't use identity and the main table and you are not using the lastest version of sql server -- Create a shadow table based on a identity field and use that to generate sequence numbers
i.e.
create table AKT_ES_CampTool_Shadow
(
id int identity(1234,1) not null -- replacing 1234 with a value based on max+1
, dummy varchar(1) null
)
Then to gen an id -- less expensive than max+1 -- no locking problems
create proc AKT_ES_CampTool_idgen(#newid output)
(
declare #newid int
begin tran
insert into dbo.AKT_ES_CampTool_Shadow (dummy) values ('')
select #newid = scope_id()
rollback
)
You don't say how big AKT_ES_CampTool_TempHist is. If it is large, you may have performance issues there (esp. if there is no index on the field "inserted")
You could start by created a table var containing the relevant columns.
declare #TempHist table
(
HistNo int
, inserted int
, etc.
primary key(...)
)
Then populate #TempHist with a single insert query. If you don't have an appropriate PK for this table, used use a generated RowID s the PK
Now, you can loop through this table without causing lock contention. Just select top 1 from #TempHist and the delete the corresponsding row from #TempHist when you are done processing it.
You won't have use a cursor nor have a large Batch operation
Related
I am using a trigger to insert rows into a table using INSERT statement as below but when doing this the RECORD_ID number increments by 1 digit so all the records inserted have the same number..
This is what i'm using to increment the records from the trigger.
, ISNULL((
SELECT MAX([PROGRESS-RECID]) FROM [DBAdmin].[dbo].[ReTncyTransStatement]
),0) + 1 AS [PROGRESS-RECID]
This is what i'm using to load the data
;WITH TestTrans (
[ORG-CODE]
,[TNCY-SYS-REF]
,[TRANS-NO]
,[POSTING-YEAR]
,[POSTING-WEEK]
,[TRANS-YEAR]
,[TRANS-WEEK]
,[TRANS-DATE]
,[ACCOUNT-TYPE]
,[ACCOUNT-CODE]
,[COMMENT]
,[TRANS-AMT]
,[SOURCE]
,[CREATED-USER]
,[CREATED-DATE]
,[CREATED-TIME]
,[UPDATED-USER]
,[UPDATED-DATE]
,[UPDATED-TIME]
,[BATCH-NO]
,[BATCH-NO-TYPE]
,[SUSPENSE-REF]
,[REFERENCE]
,[MGT-AREA]
,[ANALYSIS-CODE]
)
AS (SELECT
[ORG-CODE]
,[TNCY-SYS-REF]
,[TRANS-NO]
,[POSTING-YEAR]
,[POSTING-WEEK]
,[TRANS-YEAR]
,[TRANS-WEEK]
,[TRANS-DATE]
,[ACCOUNT-TYPE]
,[ACCOUNT-CODE]
,[COMMENT]
,[TRANS-AMT]
,[SOURCE]
,[CREATED-USER]
,[CREATED-DATE]
,[CREATED-TIME]
,[UPDATED-USER]
,[UPDATED-DATE]
,[UPDATED-TIME]
,[BATCH-NO]
,[BATCH-NO-TYPE]
,[SUSPENSE-REF]
,[REFERENCE]
,[MGT-AREA]
,[ANALYSIS-CODE] from [SQLViewsPro2Live].[dbo].[RE-TNCY-TRANS] where [TRANS-DATE] between '2019-05-16 00:00:00.000' and '2019-05-17 00:00:00.000'
)
INSERT INTO [SQLViewsPro2Test].[dbo].[RE-TNCY-TRANS]
SELECT
[ORG-CODE]
,[TNCY-SYS-REF]
,[TRANS-NO]
,[POSTING-YEAR]
,[POSTING-WEEK]
,[TRANS-YEAR]
,[TRANS-WEEK]
,[TRANS-DATE]
,[ACCOUNT-TYPE]
,[ACCOUNT-CODE]
,[COMMENT]
,[TRANS-AMT]
,[SOURCE]
,[CREATED-USER]
,[CREATED-DATE]
,[CREATED-TIME]
,[UPDATED-USER]
,[UPDATED-DATE]
,[UPDATED-TIME]
,[BATCH-NO]
,[BATCH-NO-TYPE]
,[SUSPENSE-REF]
,[REFERENCE]
,[MGT-AREA]
,[ANALYSIS-CODE]
FROM TestTrans;
GO
Any fixes appreciated
Thanks,
Full description of problem available here: T-SQL : create trigger to copy new columns from one table to another and increment no
Make PROGRESS-RECID an IDENTITY column and it will auto-increment.
Based on the linked question, you can rewrite your trigger as following:
CREATE TRIGGER AddReTncyTransStatement
ON [SQLViewsPro2EOD].[dbo].[RE-TNCY-TRANS]
AFTER UPDATE, INSERT
AS
BEGIN
DECLARE #ORG_CODE INT,
#TNCY_SYS_REF INT,
#TRANS_NO INT;
DECLARE C CURSOR FAST_FORWARD FOR(
SELECT Inserted.[ORG-CODE],
Inserted.[TNCY-SYS-REF],
Inserted.[TRANS-NO]
FROM Inserted);
OPEN C;
FETCH NEXT FROM C
INTO #ORG_CODE,
#TNCY_SYS_REF,
#TRANS_NO;
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO [DBAdmin].[dbo].[ReTncyTransStatement]
(
[ORG-CODE],
[TNCY-SYS-REF],
[TRANS-NO],
[PROGRESS-RECID]
)
SELECT
#ORG_CODE,
#TNCY_SYS_REF,
#TRANS_NO,
ISNULL((SELECT MAX([PROGRESS-RECID]) FROM [DBAdmin].[dbo].[ReTncyTransStatement]),0) + 1 AS RECID;
FETCH NEXT FROM C
INTO #ORG_CODE,
#TNCY_SYS_REF,
#TRANS_NO
END;
CLOSE C;
DEALLOCATE C;
END;
Root of your problem:
When you use INSERT INTO ... SELECT(The one outside the trigger), trigger will be called once and the inserted table will contain all the records to be inserted. so the query inside the trigger will be run once, furthermore the SELECT MAX([PROGRESS-RECID]) will be calculated once. This means that if the inserted table contains 10 records, that are being inserted, then MAX(...) will be same for all of them!
How I Solved it:
Inside the trigger I used Cursor to iterate through the all records that are being inserted(For example 10 records), then in each iteration I insert one record to ReTncyTransStatement so the MAX(...) will be calculated and executed as expected.
I am moving a small database from MS Access into SQL Server. Each year, the users would create a new Access database and have clean data, but this change will put data across the years into one pot. The users have relied on the autonumber value in Access as a reference for records. That is very inaccurate if, say, 238 records are removed.
So I am trying to accommodate them with an id column they can control (somewhat). They will not see the real primary key in the SQL table, but I want to give them an ID they can edit, but still be unique.
I've been working with this trigger, but it has taken much longer than I expected.
Everything SEEMS TO work fine, except I don't understand why I have the same data in my INSERTED table as the table the trigger is on. (See note in code.)
ALTER TRIGGER [dbo].[trg_tblAppData]
ON [dbo].[tblAppData]
AFTER INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #NewUserEnteredId int = 0;
DECLARE #RowIdForUpdate int = 0;
DECLARE #CurrentUserEnteredId int = 0;
DECLARE #LoopCount int = 0;
--*** Loop through all records to be updated because the values will be incremented.
WHILE (1 = 1)
BEGIN
SET #LoopCount = #LoopCount + 1;
IF (#LoopCount > (SELECT Count(*) FROM INSERTED))
BREAK;
SELECT TOP 1 #RowIdForUpdate = ID, #CurrentUserEnteredId = UserEnteredId FROM INSERTED WHERE ID > #RowIdForUpdate ORDER BY ID DESC;
IF (#RowIdForUpdate IS NULL)
BREAK;
-- WHY IS THERE A MATCH HERE? HAS THE RECORD ALREADY BEEN INSERTED?
IF EXISTS (SELECT UserEnteredId FROM tblAppData WHERE UserEnteredId = #CurrentUserEnteredId)
BEGIN
SET #NewUserEnteredId = (SELECT Max(t1.UserEnteredId) + 1 FROM tblAppData t1);
END
ELSE
SET #NewUserEnteredId = #CurrentUserEnteredId;
UPDATE tblAppData
SET UserEnteredId = #NewUserEnteredId
FROM tblAppData a
WHERE a.ID = #RowIdForUpdate
END
END
Here is what I want to accomplish:
When new record(s) are added, it should increment values from the Max existing
When a user overrides a value, it should check to see the existence of that value. If found restore the existing value, otherwise allow the change.
This trigger allows for multiple rows being added at a time.
It is great for this to be efficient for future use, but in reality, they will only add 1,000 records a year.
I wouldn't use a trigger to accomplish this.
Here is a script you can use to create a sequence (op didn't tag version), create the primary key, use the sequence as your special id, and put a constraint on the column.
create table dbo.test (
testid int identity(1,1) not null primary key clustered
, myid int null constraint UQ_ unique
, somevalue nvarchar(255) null
);
create sequence dbo.myid
as int
start with 1
increment by 1;
alter table dbo.test
add default next value for dbo.myid for myid;
insert into dbo.test (somevalue)
select 'this' union all
select 'that' union all
select 'and' union all
select 'this';
insert into dbo.test (myid, somevalue)
select 33, 'oops';
select *
from dbo.test
insert into dbo.test (somevalue)
select 'oh the fun';
select *
from dbo.test
--| This should error
insert into dbo.test (myid, somevalue)
select 3, 'This is NO fun';
Here is the result set:
testid myid somevalue
1 1 this
2 2 that
3 3 and
4 4 this
5 33 oops
6 5 oh the fun
And at the very end a test, which will error.
1-Using SQL Server 2014, how can I edit this code?
2-If the condition is not met Return to condition check again
3-If the check condition is correct - go to Next
4-When you finish code Return to first condition check again
I want Run the infinite loop
Please see the picture for clarificationenter image description here
if not exists (
select top 1 1
from sms.dbo.m_link
where link_check = 0
)RETURN
WAITFOR DELAY '00:00:05'
INSERT INTO sms.dbo.M_Out (MessageTo ,MessageType ,Gateway ,UserId)
select top 1 1 link_MessageTo ,link_MessageType ,link_Gateway ,link_UserId
from sms.dbo.m_link
where link_check = 0
WAITFOR DELAY '00:00:10'
With your image and the update to your question, it looks like you are queuing records into the m_link table, and wanting to insert one record at a time into the m_out table? I'm not sure this is the best approach but to answer your question, you could use a While Loop which is often a dirty word in SQL Server.
The code logic you currently have is off, to sql it reads:
if not exists (no record to be inserted)
wait a few seconds
insert the first record from the table that doesn't have any records into
the outgoing table. (illogical)
wait a few more seconds
on the other hand, if a record does exists SQL does nothing, you haven't told it what you want to do
My suggestion of using the while loop is based on being able to complete the task as you have presented it, I am hesitant to suggest it as I doubt it is the best approach, however with out additional information the following code should accomplish what it seems you are trying to do.
without further ado:
set nocount on;
--| Supporting Tables
declare #delete as table (
m_linkID int not null
);
declare #m_Out as table (
m_OutID int identity(73,1) not null primary key clustered
, m_linkID int not null
, link_MessageTo nvarchar(255) not null
, link_MessageType int not null
, link_Gateway int not null
, link_UserID int not null
);
declare #m_link as table (
m_linkID int identity(1,1) not null primary key clustered
, link_MessageTo nvarchar(255) not null
, link_MessageType int not null
, link_Gateway int not null
, link_UserID int not null
);
--| populate the m_link table with example records
insert into #m_link (link_MessageTo, link_MessageType, link_Gateway, link_UserID)
select 'Message One', 1, 42, 666 union all
select 'Message Two', 1, 57, 99;
--| show the inserted records
select *
from #m_link
--| evaluate the table for records (Start - While Loop)
while exists (
select 1
from #m_link
)
begin
--| 'move' records into the m_out table
insert into #m_out (m_linkID, link_MessageTo, link_MessageType, link_Gateway, link_UserID)
output inserted.m_linkID
into #delete
select top 1 m_linkID
, link_MessageTo
, link_MessageType
, link_Gateway
, link_UserID
from #m_link
--| remove the record from the m_link table
delete
from #m_link
where m_linkID in (
select m_linkID
from #delete
)
end;
select *
from #m_Out
select *
from #m_link
Previous Answer prior to Ops Question Update
you might tag with the version of SQL Server you're using, and explain in more detail the over all scenario and goal.
You could try this:
if not exists (
select 1
from sms.dbo.m_link
where link_check = 0
)
My concern is you are trying to implement a RBAR solution in a set based world. If you are trying to do X based on a row having Y, the approach is wrong.
I have to do an SQL Server Statement that have to return an empty row when is null, and data otherwhise.
I am trying to do a Select from (if exisits) but have an error on parent table.
I Simplify it. But the meaning, is to retrieve a couple of fields when condition is null and other fields when it is not null.
It Works fine when I do not clouse it in another select.... I need to retrieve it as a table to do an inner Join with other clouse.
How can i resolved it?
Here is my code..
select * from
(
if exists(select isnull(SECTOR_ID_DESTINO_BAD,-1)
from workflow_Compras_detalle w
where w.id=2)
begin
select null as Sector,null as sector_id_origen
end
else
begin
select top 1 isnull(ws.sector,'') sector, wd.sector_id_origen
from workflow_Compras_detalle wd
where orden < 10
end
)Table
you should try to insert the data into a temporary table or Table Variable, then get the data from that table, here is an example with a Table Variable, if you need something more persistent you may use a #Temp Table, i recommend you take a look to this: difference between var table and #Temp Table
DECLARE #VAR_TABLE AS TABLE(
Sector varchar(25),
sector_id_origen int
)
if exists(select isnull(SECTOR_ID_DESTINO_BAD,-1)
from workflow_Compras_detalle w
where w.id=2)
begin
INSERT INTO #VAR_TABLE
Select null as Sector,null as sector_id_origen
End
Else
begin
INSERT INTO #VAR_TABLE
select top 1 isnull(ws.sector,'') sector, wd.sector_id_origen
from workflow_Compras_detalle wd
where orden < 10
End
SELECT * FROM #VAR_TABLE
GOAL:I'm creating after insert trigger that should insert new record to OrderSuspendRule table based on rule in this table that was related with Promotion of which new version was created.
PROBLEM
I cannot set value to #SUS_ID. Select returns value but it isn't set to variable.
Sample insert:
INSERT INTO PromotionHeader (Guid,CreatedAt,UpdatedAt,IsActive,CompanyId,UpdatedById,CreatedById,Name,[Description],ValidFrom,ValidTo,BusinessUnitId,OfferId,[Version],StatusId,PreviousId)
select newid(),CreatedAt,UpdatedAt,1,CompanyId,UpdatedById,CreatedById,Name,[Description],ValidFrom,ValidTo,BusinessUnitId,OfferId,[Version]+1,StatusId,916 FROM PromotionHeader WHERE Id=916
Where PreviousId points to older version of promotion.
CREATE TRIGGER TRIG1 ON DBO.PromotionHeader
AFTER INSERT
AS
DECLARE #SUS_ID INT
SET #SUS_ID = (
SELECT Max(id)
FROM OrderSuspendRule
WHERE PromotionHeaderId = (
SELECT PreviousId
FROM inserted
WHERE ID = SCOPE_IDENTITY()
)
AND ISACTIVE=1
)
IF (#SUS_ID IS NOT NULL) --**VARIABLE IS ALWAYS NULL NO MATTER WHAT**
BEGIN
INSERT INTO OrderSuspendRule (
Guid
,CreatedAt
,UpdatedAt
,IsActive
,CompanyId
,UpdatedById
,CreatedById
,SuspendFrom
,SuspendTo
,PromotionHeaderId
,SuspendTypeId
,OfferItemId
)
SELECT NEWID()
,GETDATE()
,GETDATE()
,1
,CompanyId
,UpdatedById
,CreatedById
,SuspendFrom
,SuspendTo
,SCOPE_IDENTITY()
,SuspendTypeId
,OfferItemId
FROM OrderSuspendRule
WHERE id = #SUS_ID
END
Inside a for insert trigger, you can assume that all rows in the inserted table were inserted. There is no need to double check this with scope_identity().
To explain why scope_identity() is null, remember that scope_identity() returns the last inserted identity in the current scope. Since your trigger runs in its own scope, this will always be null, unless the trigger itself performs an insert.
Also, be aware that your trigger can be run for an insert of multiple rows. That means you can't expect only a single #sus_id, there might be many.