Related
The stored procedure is running very slow in SQL Server 2016 as compared to SQL Server 2019. It's doing some common validation from a temporary staging table and inserting data in primary tables and mapping tables.
The mapping tables have clustered indexes on primary keys {Table1Id, Table2Id, Table3Id}. The procedure needs to process around 4 million rows. It finishes execution in 2 hours in SQL Server 2019, but takes probably 6-8 hours in SQL Server 2016 with the same data file.
Description of the stored proc -
There is a dbo.HouseholdTemp table with all rows to be processed its doing
Slice each row and insert one portion in dbo.Locations and another
potion in dbo.Individuals table if LocationGroupId or IndividualGroupId
does not exists or is empty.
Save the generated ID from those two tables and map them in dbo.Households table {LocationId, IndividualId}.
Select an existing MailingId from dbo.Mailings table with a JOIN query.
Insert another row in dbo.ListTypes table if the combination of Type and Code does not exists and get the ListTypes Id.
Insert a row in the dbo.HouseholdMailings table with MailingId,
LocationId, IndividualId , ListTypeId.
Execution plan is attached.
FYI, The SQL installation is on C drive where as the database files are on D drive. Please help!
Execution plan can be found here : https://www.brentozar.com/pastetheplan/?id=BybjNm5a8
CREATE PROCEDURE [dbo].[usp_ProcessHouseholdDatav2]
AS
BEGIN TRY
-- Declare variables
DECLARE #DataIngestionLogId int = 0;
DECLARE #HouseholdCount bigint = 0;
DECLARE #n int = 0;
DECLARE #householdrecords int = 0;
DECLARE #householdrecordsexistscount int = 0;
DECLARE #householdmailingsrecords int = 0;
DECLARE #householdmailingsrecordsexistscount int = 0;
DECLARE #listtyperecords int = 0;
DECLARE #tempid int = 0;
DECLARE #locationid int = 0;
DECLARE #individualid int = 0;
DECLARE #mailingid int = 0;
DECLARE #jobnumber nvarchar(6) = N'';
DECLARE #mergedpanelcode nvarchar(10) = N'';
DECLARE #locationgroupid nvarchar(10) = N'';
DECLARE #individualgroupid nvarchar(10) = N'';
DECLARE #listtype varchar(3) = '';
DECLARE #listcode varchar(4) = '';
DECLARE #findernumber nvarchar(50) = N'';
DECLARE #modelrank nvarchar(2) = N'';
DECLARE #listtypeid int = 0;
DECLARE #starttime datetime = GETDATE();
INSERT INTO dbo.DataIngestionLogs (FileName,
Message,
CreatedDate)
VALUES ('usp_ProcessHouseholdData', 'PROCESS STARTED ' + CAST(#HouseholdCount AS nvarchar(10)) + ' records to be processed', GETDATE());
SET #DataIngestionLogId = SCOPE_IDENTITY();
WHILE EXISTS (SELECT TOP 1 1 FROM dbo.HouseholdTemp ht WHERE ht.ProcessedDate IS NULL /*AND #n < 1000000*/ )
BEGIN
-- Retrieve Id, JobNumber and MergedPanelCode from HouseholdTemp table
SELECT TOP 1
#tempid = ht.Id,
#jobnumber = ht.JobNumber,
#mergedpanelcode = ht.MergedPanelCode,
#locationgroupid = ht.LocationGroupID,
#individualgroupid = ht.IndividualGroupID,
#listtype = ht.ListType,
#listcode = ht.ListCode,
#findernumber = ht.FinderNumber,
#modelrank = ht.ModelRank
FROM dbo.HouseholdTemp ht
WHERE ht.ProcessedDate IS NULL;
IF (#locationgroupid IS NULL OR #locationgroupid = '')
BEGIN
INSERT INTO dbo.Locations (LocationGroupId,
Address1,
Address2,
City,
State,
Zip10,
ZipCode,
DeliveryPointCode,
FIPSCounty,
CountyName,
AddressType,
PrimaryNumber,
PreDirectional,
StreetName,
StreetSuffix,
PostDirectional,
UnitDesignator,
UnitNumber,
PMBNumber,
CarrierRoute,
PenetrationCode,
MetroName,
CreatedDate,
ModifiedDate)
SELECT TOP 1
ht.LocationGroupID AS LocationGroupID,
ht.Address1 AS Address1,
ht.Address2 AS Address2,
ht.City AS City,
ht.State AS State,
ht.Zip10 AS Zip10,
ht.ZipCode AS ZipCode,
ht.DeliveryPointCode AS DeliveryPointCode,
ht.FIPSCounty AS FIPSCounty,
ht.CountyName AS CountyName,
ht.AddressType AS AddressType,
ht.PrimaryNumber AS PrimaryNumber,
ht.PreDirectional AS PreDirectional,
ht.StreetName AS StreetName,
ht.StreetSuffix AS StreetSuffix,
ht.PostDirectional AS PostDirectional,
ht.UnitDesignator AS UnitDesignator,
ht.UnitNumber AS UnitNumber,
ht.PMBNumber AS PMBNumber,
ht.CarrierRoute AS CarrierRoute,
ht.PenetrationCode AS PenetrationCode,
ht.MetroName AS MetroName,
GETDATE(),
GETDATE()
FROM dbo.HouseholdTemp ht
WHERE ht.Id = #tempid;
SET #locationid = SCOPE_IDENTITY();
END;
ELSE
BEGIN
-- Check if record exists with LocationGroupId in Locations table
IF (NOT EXISTS (SELECT TOP 1
1
FROM dbo.Locations l
WHERE l.LocationGroupId = RTRIM(LTRIM(#locationgroupid))))
BEGIN
-- PRINT 'Location does not exists with LocationGroupId : ' + #locationgroupid + '--> Insert in Locations table....'
INSERT INTO dbo.Locations (LocationGroupId,
Address1,
Address2,
City,
State,
Zip10,
ZipCode,
DeliveryPointCode,
FIPSCounty,
CountyName,
AddressType,
PrimaryNumber,
PreDirectional,
StreetName,
StreetSuffix,
PostDirectional,
UnitDesignator,
UnitNumber,
PMBNumber,
CarrierRoute,
PenetrationCode,
MetroName,
CreatedDate,
ModifiedDate)
SELECT TOP 1
ht.LocationGroupID AS LocationGroupID,
ht.Address1 AS Address1,
ht.Address2 AS Address2,
ht.City AS City,
ht.State AS State,
ht.Zip10 AS Zip10,
ht.ZipCode AS ZipCode,
ht.DeliveryPointCode AS DeliveryPointCode,
ht.FIPSCounty AS FIPSCounty,
ht.CountyName AS CountyName,
ht.AddressType AS AddressType,
ht.PrimaryNumber AS PrimaryNumber,
ht.PreDirectional AS PreDirectional,
ht.StreetName AS StreetName,
ht.StreetSuffix AS StreetSuffix,
ht.PostDirectional AS PostDirectional,
ht.UnitDesignator AS UnitDesignator,
ht.UnitNumber AS UnitNumber,
ht.PMBNumber AS PMBNumber,
ht.CarrierRoute AS CarrierRoute,
ht.PenetrationCode AS PenetrationCode,
ht.MetroName AS MetroName,
GETDATE(),
GETDATE()
FROM dbo.HouseholdTemp ht
WHERE ht.Id = #tempid;
SET #locationid = SCOPE_IDENTITY();
END;
ELSE
BEGIN
SELECT TOP 1
#locationid = Id
FROM dbo.Locations l
WHERE l.LocationGroupId = RTRIM(LTRIM((#locationgroupid)));
END;
END;
IF (#individualgroupid IS NULL OR #individualgroupid = '')
BEGIN
INSERT INTO dbo.Individuals (IndividualGroupId,
FirstName,
LastName,
DateOfBirth,
EmailAddress,
Gender,
MaritalStatus,
CreatedDate,
ModifiedDate)
SELECT ht.IndividualGroupID AS IndividualGroupId,
ht.FirstName AS FirstName,
ht.LastName AS LastName,
CAST(ht.DateOfBirth AS date) AS DateOfBirth,
ht.EmailAddress AS EmailAddress,
ht.Gender AS Gender,
ht.MaritalStatus AS MaritalStatus,
GETDATE(),
GETDATE()
FROM dbo.HouseholdTemp ht
WHERE ht.Id = #tempid;
SET #individualid = SCOPE_IDENTITY();
END;
ELSE
BEGIN
-- Check if record exists with IndividualGroupId in Individuals table
IF (NOT EXISTS (SELECT TOP 1
1
FROM dbo.Individuals i
WHERE i.IndividualGroupId = RTRIM(LTRIM((#individualgroupid)))))
BEGIN
INSERT INTO dbo.Individuals (IndividualGroupId,
FirstName,
LastName,
DateOfBirth,
EmailAddress,
Gender,
MaritalStatus,
CreatedDate,
ModifiedDate)
SELECT ht.IndividualGroupID AS IndividualGroupId,
ht.FirstName AS FirstName,
ht.LastName AS LastName,
CAST(ht.DateOfBirth AS date) AS DateOfBirth,
ht.EmailAddress AS EmailAddress,
ht.Gender AS Gender,
ht.MaritalStatus AS MaritalStatus,
GETDATE(),
GETDATE()
FROM dbo.HouseholdTemp ht
WHERE ht.Id = #tempid;
SET #individualid = SCOPE_IDENTITY();
END;
ELSE
BEGIN
SELECT TOP 1
#individualid = Id
FROM dbo.Individuals i
WHERE i.IndividualGroupId = RTRIM(LTRIM(#individualgroupid));
END;
END;
IF (#locationid > 0 AND #individualid > 0)
BEGIN
BEGIN TRY
INSERT INTO dbo.Households (LocationId,
IndividualId,
CreatedDate)
VALUES (#locationid, #individualid, GETDATE());
SET #householdrecords = #householdrecords + 1;
END TRY
BEGIN CATCH
END CATCH;
-- Get Mailing Id from JobNumber and MergedPanelCode
SELECT TOP 1
#mailingid = m.Id
FROM dbo.Mailings m
INNER JOIN dbo.Jobs j ON j.Id = m.JobId
INNER JOIN dbo.MergedPanels mp ON mp.Id = m.MergedPanelId
WHERE j.Number = #jobnumber
AND mp.Code = #mergedpanelcode;
IF (#mailingid > 0)
BEGIN
IF ((#listtype IS NOT NULL
OR #listtype != '')
AND (#listcode IS NOT NULL
OR #listcode != ''))
BEGIN
IF NOT EXISTS (SELECT TOP 1
1
FROM dbo.ListTypes lt
WHERE lt.Type = #listtype
AND lt.Code = #listcode)
BEGIN
INSERT INTO dbo.ListTypes (Type,
Code,
CreatedDate,
ModifiedDate)
VALUES (#listtype, #listcode, GETDATE(), GETDATE());
SET #listtypeid = SCOPE_IDENTITY();
SET #listtyperecords = #listtyperecords + 1;
END;
ELSE
BEGIN
SELECT TOP 1
#listtypeid = Id
FROM dbo.ListTypes lt
WHERE lt.Type = #listtype
AND lt.Code = #listcode;
END;
END;
IF (#listtypeid > 0)
BEGIN TRY
INSERT INTO dbo.HouseholdMailings (MailingId,
LocationId,
IndividualId,
ListTypeId,
FinderNumber,
ModelRank,
CreatedDate)
VALUES (#mailingid, #locationid, #individualid, #listtypeid, #findernumber, #modelrank, GETDATE());
SET #listtypeid = 0;
SET #householdmailingsrecords = #householdmailingsrecords + 1;
END TRY
BEGIN CATCH
END CATCH;
END;
ELSE
BEGIN
SET #householdmailingsrecordsexistscount = #householdmailingsrecordsexistscount + 1;
END;
END;
-- Update the ProcessedDate in HouseholdTemp table
UPDATE dbo.HouseholdTemp
SET ProcessedDate = GETDATE()
WHERE Id = #tempid;
SET #n = #n + 1;
SET #locationid = 0;
SET #individualid = 0;
SET #mailingid = 0;
END;
INSERT INTO dbo.DataIngestionLogs (FileName,
Message,
ParentId,
CreatedDate)
VALUES ('usp_ProcessHouseholdData', 'PROCESS COMPLETED ' + CAST(#n AS nvarchar(10)) + ' records processed.', #DataIngestionLogId, GETDATE());
END TRY
BEGIN CATCH
PRINT 'Error insert...';
INSERT INTO dbo.DataIngestionErrors
VALUES (SUSER_SNAME(), ERROR_NUMBER(), ERROR_STATE(), ERROR_SEVERITY(), ERROR_LINE(), ERROR_PROCEDURE(), ERROR_MESSAGE(), GETDATE(), #DataIngestionLogId);
END CATCH;
So I created a Stored Procedure in SQL that does 4 things
Create Order into Customer Order table
Create Order lines on Customer Order Line table
Update Auto number column by taking current value and adding 1
Update Purchase Order with Customer Order ID
Sends email to notify that an order has been created.
It works awesome if I execute each part separately or execute the whole Stored Procedure manually by sending command EXEC dbo.POIMPORT_UK #PO_NUM = 123456
But when I create a PO and it triggers the inserts, query 1,3,4,5 work but 2 doesn't for some reason. I can only get it to go if I fire off that procedure manually. Any thoughts?
USE [RIPTST]
GO
/****** Object: StoredProcedure [dbo].[POIMPORT_UK] Script Date: 8/20/2018 11:21:23 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER proc [dbo].[POIMPORT_UK]
#PO_NUM NVARCHAR(15)
AS
SET NOCOUNT ON
--For synchronization machine id placement
--Query 1
INSERT INTO CUSTOMER_ORDER(ID, CUSTOMER_ID, SITE_ID, SELL_RATE, BUY_RATE,
TERMS_NET_TYPE, TERMS_DISC_TYPE, FREIGHT_TERMS, BACK_ORDER, STATUS,
POSTING_CANDIDATE, MARKED_FOR_PURGE, CURRENCY_ID, CUSTOMER_PO_REF,
WAREHOUSE_ID, DESIRED_SHIP_DATE) SELECT (SELECT Next_number FROM
NEXT_NUMBER_GEN where rowid =
'72'),'ABECO','101RIPUS','1.0','1.0','A','A','P','N','F','N','N','(USD)
$',ID,'CROM',Getdate() FROM PURCHASE_ORDER WHERE ID = #PO_NUM
--Query 2
INSERT INTO CUST_ORDER_LINE
(CUST_ORDER_ID,LINE_NO,PART_ID,LINE_STATUS,ORDER_QTY,USER_ORDER_QTY,
SELLING_UM,UNIT_PRICE,SITE_ID,MISC_REFERENCE,PRODUCT_CODE,COMMODITY_CODE,
DRAWING_ID,DRAWING_REV_NO,MAT_GL_ACCT_ID,LAB_GL_ACCT_ID,BUR_GL_ACCT_ID,
SER_GL_ACCT_ID,GL_REVENUE_ACCT_ID,TOTAL_AMT_ORDERED,ENTERED_BY,WAREHOUSE_ID)
SELECT(select id from customer_order where CUSTOMER_PO_REF = #PO_NUM),
LINE_NO,PURC_ORDER_LINE.PART_ID,'A',ORDER_QTY,USER_ORDER_QTY,
PURCHASE_UM,PURC_ORDER_LINE.UNIT_PRICE,'101RIPUS',PART.DESCRIPTION,
PART.PRODUCT_CODE,PART.COMMODITY_CODE,PART.DRAWING_ID,PART.DRAWING_REV_NO,
PART_SITE.MAT_GL_ACCT_ID,PART_SITE.LAB_GL_ACCT_ID,PART_SITE.BUR_GL_ACCT_ID,
PART_SITE.SER_GL_ACCT_ID,
(SELECT REV_GL_ACCT_ID FROM PRODUCT WHERE CODE = PART.PRODUCT_CODE),
USER_ORDER_QTY*PURC_ORDER_LINE.UNIT_PRICE,'SYSADM',
PART_SITE.PRIMARY_WHS_ID From PURC_ORDER_LINE
Inner Join PART On PART.ID = PURC_ORDER_LINE.PART_ID
Inner Join PART_SITE On PART_SITE.PART_ID = PART.ID
WHERE PURC_ORDER_ID = #PO_NUM AND PART_SITE.sITE_ID = '101RIPUS'
--Query 3
Update NEXT_NUMBER_GEN Set Next_number = Next_number + 1 where rowid = '72'
--Query 4
Update PURCHASE_ORDER Set SALES_ORDER_ID = (select id from customer_order where CUSTOMER_PO_REF = #PO_NUM) where id = #PO_NUM
--Query 5
Declare #PO NVARCHAR(15) = #PO_NUM
Declare #CO NVARCHAR(15) = (select id from customer_order where CUSTOMER_PO_REF = #PO)
Declare #MYBODY VARCHAR(MAX) = 'Hello Test CSR,
A new internal order has been created for Ripley UK.
Test US Customer Order Number: ' + #CO +
'
Test UK Purchase Order Number: ' + #PO
EXEC msdb.dbo.sp_send_dbmail #profile_name='office365',
#recipients='test#test.com',
#subject='Test UK - Internal Order',
#body= #MYBODY
SET NOCOUNT OFF
Insert Trigger
USE [RIPTST]
GO
/****** Object: Trigger [dbo].[INSERT_PURCHASE_ORDER] Script Date: 8/20/2018 1:42:15 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER trigger [dbo].[INSERT_PURCHASE_ORDER] ON [dbo].[PURCHASE_ORDER] FOR INSERT AS
DECLARE
#nRcd INT,
#ID NVARCHAR(15),
#VENDOR_ID NVARCHAR(15),
#SITE_ID NVARCHAR(15),
#STATUS NCHAR,
#TOTAL_AMT_ORDERED DEC(15,2),
#TOTAL_AMT_RECVD DEC(15,2),
#SELL_RATE DEC(17,8),
#nCurrDigits INT,
#sEntityID NVARCHAR(15),
#ORDER_DATE DATETIME
SELECT #nRcd = 0
SET NOCOUNT ON
DECLARE PURCHASE_ORDER_INS CURSOR LOCAL FOR SELECT ID, VENDOR_ID, STATUS, TOTAL_AMT_ORDERED, TOTAL_AMT_RECVD, SELL_RATE, ORDER_DATE, SITE_ID FROM INSERTED
OPEN PURCHASE_ORDER_INS
FETCH PURCHASE_ORDER_INS INTO #ID, #VENDOR_ID, #STATUS, #TOTAL_AMT_ORDERED, #TOTAL_AMT_RECVD, #SELL_RATE, #ORDER_DATE, #SITE_ID
WHILE (#nRcd = 0 and ##FETCH_STATUS <> -1)
BEGIN
EXEC EXE_DETECT_EVENT 'P', #ID, NULL, NULL, 'I'
If #TOTAL_AMT_ORDERED + #TOTAL_AMT_RECVD != 0
SELECT #nRcd = 30881
If #nRcd = 0 And (#STATUS = 'R' Or #STATUS = 'F') And #TOTAL_AMT_ORDERED > #TOTAL_AMT_RECVD
BEGIN
select #nCurrDigits = c.i_curr_digits, #sEntityID = s.entity_id from CURRENCY c, ACCOUNTING_ENTITY ae, SITE s where s.id = #SITE_ID and ae.id = s.entity_id and ae.functional_currency_id = c.id
IF NOT EXISTS ( SELECT VENDOR_ID FROM VENDOR_ENTITY WHERE vendor_id = #VENDOR_ID and entity_id = #sEntityID )
INSERT INTO VENDOR_ENTITY ( VENDOR_ID, ENTITY_ID ) VALUES ( #VENDOR_ID, #sEntityID )
update VENDOR_ENTITY set
total_open_orders = total_open_orders + ROUND((#TOTAL_AMT_ORDERED - #TOTAL_AMT_RECVD) * #SELL_RATE, #nCurrDigits),
open_order_count = open_order_count + 1
where vendor_id = #VENDOR_ID and entity_id = #sEntityID
END
UPDATE VENDOR set last_order_date = #ORDER_DATE where id = #VENDOR_ID
FETCH PURCHASE_ORDER_INS INTO #ID, #VENDOR_ID, #STATUS, #TOTAL_AMT_ORDERED, #TOTAL_AMT_RECVD, #SELL_RATE, #ORDER_DATE, #SITE_ID
END
BEGIN
IF #VENDOR_ID = 'RIP01' AND #SITE_ID = '111RIPUK'
EXEC dbo.POIMPORT_UK #PO_NUM = #ID
END
DEALLOCATE PURCHASE_ORDER_INS
IF (#nRcd <> 0) RAISERROR('VMFG-%d error in trigger INSERT_PURCHASE_ORDER', 16, -1, #nRcd)
IF (#nRcd <> 0 Or ##ERROR <> 0) ROLLBACK TRANSACTION
I have a trigger "after insert/update/delete/". It is supposed to count Balance on Account table based on transactions in Transaction table. It is on Transaction table. I am getting Balance discrepancies rarely, so have decided to add some logging into it. It dumps inserted+deleted tables (they are combined into a table var) and tsql statement which fired it. Judging from my log, it looks like the trigger did not fire for some inserts into Transaction table. Can this happen ? Are there any TSQL statement which change table data without firing trigger (except truncate table etc)?
Here is the trigger :
CREATE TRIGGER [dbo].[trg_AccountBalance]
ON [dbo].[tbl_GLTransaction]
AFTER INSERT, UPDATE, DELETE
AS
set nocount on
begin try
declare #OldOptions int = ##OPTIONS
set xact_abort off
declare #IsDebug bit = 1
declare #CurrentDateTime datetime = getutcdate()
declare #TriggerMessage varchar(max), #TriggerId int
if #IsDebug = 1
begin
select #TriggerId = isnull(max(TriggerId), 0) + 1
from uManageDBLogs.dbo.tbl_TriggerLog
declare #dbcc_INPUTBUFFER table(EventType nvarchar(30), Parameters Int, EventInfo nvarchar(4000) )
declare #my_spid varchar(20) = CAST(##SPID as varchar(20))
insert #dbcc_INPUTBUFFER
exec('DBCC INPUTBUFFER ('+#my_spid+')')
select #TriggerMessage = replace(EventInfo, '''', '''''') from #dbcc_INPUTBUFFER
insert into uManageDBLogs.dbo.tbl_TriggerLog (TriggerId, "Message", CreateDate)
values (#TriggerId, #TriggerMessage, #CurrentDateTime)
end
declare #Oper int
select #Oper = 0
-- determine type of sql statement
if exists (select * from inserted) select #Oper = #Oper + 1
if exists (select * from deleted) select #Oper = #Oper + 2
if #IsDebug = 1
begin
select #TriggerMessage = '#Oper = ' + convert(varchar, #Oper)
insert into uManageDBLogs.dbo.tbl_TriggerLog (TriggerId, "Message", CreateDate)
values (#TriggerId, #TriggerMessage, #CurrentDateTime)
end
if #Oper = 0 return -- No data changed
declare #TomorrowDate date = dateadd(day, 1, convert(date, getdate()))
declare #CurrentDate date = convert(date, getdate())
-- transactions from both inserted and deleted tables
declare #tbl_Trans table (FirmId int, GLAccountId int,
AmountDebit money, AmountCredit money, "Status" char(1), TableType char(1))
declare #tbl_AccountCounters table (FirmId int, GLAccountId int, Balance money)
declare #IsChange bit = null
insert into #tbl_Trans (FirmId, GLAccountId, AmountDebit, AmountCredit, "Status", TableType)
select FirmId, GLAccountId, AmountDebit, AmountCredit, "Status", 'I'
from inserted
union
select FirmId, GLAccountId, AmountDebit, AmountCredit, "Status", 'D'
from deleted
if #IsDebug = 1
begin
select #TriggerMessage = (select * from #tbl_Trans for xml path ('tbl_Trans'))
insert into uManageDBLogs.dbo.tbl_TriggerLog (TriggerId, "Message", CreateDate)
values (#TriggerId, #TriggerMessage, #CurrentDateTime)
end
insert into #tbl_AccountCounters (FirmId, GLAccountId, Balance)
select FirmId, GLAccountId, 0
from #tbl_Trans
group by FirmId, GLAccountId
if #Oper = 1 or #Oper = 2 -- insert/delete
begin
update #tbl_AccountCounters
set Balance = cnt.TransSum
from #tbl_AccountCounters as ac join
(
select trans.FirmId, trans.GLAccountId,
isnull(sum((trans.AmountDebit - trans.AmountCredit) * iif(trans.TableType = 'I', 1, -1)), 0) as TransSum
from #tbl_Trans as trans
where trans.Status = 'A'
group by trans.FirmId, trans.GLAccountId
) as cnt on ac.FirmId = cnt.FirmId and ac.GLAccountId = cnt.GLAccountId
select #IsChange = 1
end
else
begin
if update(AmountDebit) or update(AmountCredit) or update(Status) or update(GLAccountId)
begin
update #tbl_AccountCounters
set Balance = cnt.TransBalance
from #tbl_AccountCounters as ac join
(select trans.FirmId, trans.GLAccountId, isnull(sum(trans.AmountDebit - trans.AmountCredit), 0) as TransBalance
from dbo.tbl_GLTransaction as trans
where trans."Status" = 'A' and exists (select 1 from #tbl_AccountCounters as ac
where ac.GLAccountId = trans.GLAccountId and ac.FirmId = trans.FirmId)
group by trans.FirmId, trans.GLAccountId) as cnt on
ac.FirmId = cnt.FirmId and ac.GLAccountId = cnt.GLAccountId
select #IsChange = 0
end
end
if #IsDebug = 1
begin
select #TriggerMessage = '#IsChange = ' + isnull(convert(varchar, #IsChange), 'null')
insert into uManageDBLogs.dbo.tbl_TriggerLog (TriggerId, "Message", CreateDate)
values (#TriggerId, #TriggerMessage, #CurrentDateTime)
select #TriggerMessage = (select * from #tbl_AccountCounters for xml path ('tbl_AccountCounters'))
insert into uManageDBLogs.dbo.tbl_TriggerLog (TriggerId, "Message", CreateDate)
values (#TriggerId, #TriggerMessage, #CurrentDateTime)
end
if #IsChange is not null
begin
update tbl_GLAccount
set tbl_GLAccount.Balance = iif(#IsChange = 1, cnt.Balance + acc.Balance, cnt.Balance),
tbl_GLAccount.LastUpdate = getutcdate(),
tbl_GLAccount.LastUpdatedBy = 1
from #tbl_AccountCounters as cnt join dbo.tbl_GLAccount as acc on
cnt.FirmId = acc.FirmId and cnt.GLAccountId = acc.GLAccountId
end
if (16384 & #OldOptions) = 16384 set xact_abort on
end try
begin catch
declare #ErrorLine varchar(max)
select #ErrorLine = uManageDb.dbo.udf_GetErrorInfo()
insert into uManageDb.dbo.tbl_TriggerError ("Name", "Message", CreateDate)
values ('AccountingDB..trg_AccountBalance', #ErrorLine, GETUTCDATE())
end catch
I think I've found it. I have this line:
select .. from inserted
union
select .. from deleted
and they inserted 5 trans for $300 and 4 trans $100. I've got 2 records (300 and 100) in my #tbl_Trans (it was in the log). That's probably was the bug. So log hellps and trigger run as it had to.
I'll replace union with union all.
I'm running SQL Server 2008 R2 and I have a trigger that inserts into a table where asql agent job then kicks off every 10 sec's to run a sp to send an email notification. The problem that I'm running into is it appears that when a large number of inserts happen simultaneously the notifications can hang up and not get sent out for several minutes (sometimes as long as an hour) after the insert happens.The tb_BatchEmail only receives a few inserts (2-3) at a time, it's the Orders tables that can have several dozen inserts happen at once. So my question is - Is this the best way to setup this type of trigger or is there a better way that is more efficient and won't lag behind when loads of table inserts happen at once?
Here is the trigger:
ALTER TRIGGER [dbo].[VoceraOeOrders] ON [dbo].[Orders]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.tb_BatchEmail (
BatchEmailID
,[To]
,Body
,[Subject]
,[Profile]
,OrderID
,OrderDateTime
,SentDateTime
)
SELECT '0'
,'some_email'
,CASE
WHEN RoomTreatmentID IS NULL
THEN 'No Room#'
ELSE RoomTreatmentID
END + '-' + OrderedProcedureName
,CASE
WHEN Category = 'US'
THEN 'Stat Ultra Sound'
WHEN Category = 'NUC'
THEN 'Stat Nuclear'
WHEN Category = 'ECHO'
THEN 'Stat Echo'
WHEN Category = 'CT'
THEN 'Stat C T'
WHEN Category = 'MRI'
THEN 'Stat M R I'
WHEN Category = 'XRAY'
THEN 'Stat Xray'
ELSE 'Stat Order Alerts'
END
,'Alert'
,OrderID
,OrderDateTime
,NULL
FROM inserted i
INNER JOIN dbo.Patients pat
ON pat.VisitID = i.VisitID
AND pat.SourceID = i.SourceID
WHERE Priority = 'STAT'
AND Category IN ('CT', 'MRI', 'XRAY', 'US', 'NUC', 'ECHO')
AND CurrentLocationID = 'ED'
END
And here is the sp that is set to kick off every 10 seconds via server agent job:
ALTER PROCEDURE [dbo].[sp_SendVoceraMail]
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #Id INT
DECLARE #To VARCHAR(250)
DECLARE #Body VARCHAR(250)
DECLARE #Subject VARCHAR(50)
DECLARE #ProfileName VARCHAR(20)
WHILE (
SELECT count(*)
FROM tb_BatchEmail
WHERE BatchEmailID = 0
) > 0
BEGIN
SELECT TOP 1 #Id = Id
,#To = [To]
,#Body = Body
,#Subject = [Subject]
,#ProfileName = [Profile]
FROM tb_BatchEmail
WHERE BatchEmailID = 0
EXEC msdb.dbo.sp_send_dbmail #recipients = #To
,#body = #Body
,#subject = #Subject
,#profile_name = #ProfileName
UPDATE tb_BatchEmail
SET BatchEmailID = 1
,SentDateTime = GETDATE()
WHERE Id = #Id
END
END
Let's say I have the following simple table variable:
declare #databases table
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into #databases
Is declaring and using a cursor my only option if I wanted to iterate through the rows? Is there another way?
First of all you should be absolutely sure you need to iterate through each row — set based operations will perform faster in every case I can think of and will normally use simpler code.
Depending on your data it may be possible to loop using just SELECT statements as shown below:
Declare #Id int
While (Select Count(*) From ATable Where Processed = 0) > 0
Begin
Select Top 1 #Id = Id From ATable Where Processed = 0
--Do some processing here
Update ATable Set Processed = 1 Where Id = #Id
End
Another alternative is to use a temporary table:
Select *
Into #Temp
From ATable
Declare #Id int
While (Select Count(*) From #Temp) > 0
Begin
Select Top 1 #Id = Id From #Temp
--Do some processing here
Delete #Temp Where Id = #Id
End
The option you should choose really depends on the structure and volume of your data.
Note: If you are using SQL Server you would be better served using:
WHILE EXISTS(SELECT * FROM #Temp)
Using COUNT will have to touch every single row in the table, the EXISTS only needs to touch the first one (see Josef's answer below).
Just a quick note, if you are using SQL Server (2008 and above), the examples that have:
While (Select Count(*) From #Temp) > 0
Would be better served with
While EXISTS(SELECT * From #Temp)
The Count will have to touch every single row in the table, the EXISTS only needs to touch the first one.
This is how I do it:
declare #RowNum int, #CustId nchar(5), #Name1 nchar(25)
select #CustId=MAX(USERID) FROM UserIDs --start with the highest ID
Select #RowNum = Count(*) From UserIDs --get total number of records
WHILE #RowNum > 0 --loop until no more records
BEGIN
select #Name1 = username1 from UserIDs where USERID= #CustID --get other info from that row
print cast(#RowNum as char(12)) + ' ' + #CustId + ' ' + #Name1 --do whatever
select top 1 #CustId=USERID from UserIDs where USERID < #CustID order by USERID desc--get the next one
set #RowNum = #RowNum - 1 --decrease count
END
No Cursors, no temporary tables, no extra columns.
The USERID column must be a unique integer, as most Primary Keys are.
Define your temp table like this -
declare #databases table
(
RowID int not null identity(1,1) primary key,
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into #databases
Then do this -
declare #i int
select #i = min(RowID) from #databases
declare #max int
select #max = max(RowID) from #databases
while #i <= #max begin
select DatabaseID, Name, Server from #database where RowID = #i --do some stuff
set #i = #i + 1
end
Here is how I would do it:
Select Identity(int, 1,1) AS PK, DatabaseID
Into #T
From #databases
Declare #maxPK int;Select #maxPK = MAX(PK) From #T
Declare #pk int;Set #pk = 1
While #pk <= #maxPK
Begin
-- Get one record
Select DatabaseID, Name, Server
From #databases
Where DatabaseID = (Select DatabaseID From #T Where PK = #pk)
--Do some processing here
--
Select #pk = #pk + 1
End
[Edit] Because I probably skipped the word "variable" when I first time read the question, here is an updated response...
declare #databases table
(
PK int IDENTITY(1,1),
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into #databases
--/*
INSERT INTO #databases (DatabaseID, Name, Server) SELECT 1,'MainDB', 'MyServer'
INSERT INTO #databases (DatabaseID, Name, Server) SELECT 1,'MyDB', 'MyServer2'
--*/
Declare #maxPK int;Select #maxPK = MAX(PK) From #databases
Declare #pk int;Set #pk = 1
While #pk <= #maxPK
Begin
/* Get one record (you can read the values into some variables) */
Select DatabaseID, Name, Server
From #databases
Where PK = #pk
/* Do some processing here */
/* ... */
Select #pk = #pk + 1
End
If you have no choice than to go row by row creating a FAST_FORWARD cursor. It will be as fast as building up a while loop and much easier to maintain over the long haul.
FAST_FORWARD
Specifies a FORWARD_ONLY, READ_ONLY cursor with performance optimizations enabled. FAST_FORWARD cannot be specified if SCROLL or FOR_UPDATE is also specified.
This will work in SQL SERVER 2012 version.
declare #Rowcount int
select #Rowcount=count(*) from AddressTable;
while( #Rowcount>0)
begin
select #Rowcount=#Rowcount-1;
SELECT * FROM AddressTable order by AddressId desc OFFSET #Rowcount ROWS FETCH NEXT 1 ROWS ONLY;
end
Another approach without having to change your schema or using temp tables:
DECLARE #rowCount int = 0
,#currentRow int = 1
,#databaseID int
,#name varchar(15)
,#server varchar(15);
SELECT #rowCount = COUNT(*)
FROM #databases;
WHILE (#currentRow <= #rowCount)
BEGIN
SELECT TOP 1
#databaseID = rt.[DatabaseID]
,#name = rt.[Name]
,#server = rt.[Server]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY t.[DatabaseID], t.[Name], t.[Server]
) AS [RowNumber]
,t.[DatabaseID]
,t.[Name]
,t.[Server]
FROM #databases t
) rt
WHERE rt.[RowNumber] = #currentRow;
EXEC [your_stored_procedure] #databaseID, #name, #server;
SET #currentRow = #currentRow + 1;
END
You can use a while loop:
While (Select Count(*) From #TempTable) > 0
Begin
Insert Into #Databases...
Delete From #TempTable Where x = x
End
Lightweight, without having to make extra tables, if you have an integer ID on the table
Declare #id int = 0, #anything nvarchar(max)
WHILE(1=1) BEGIN
Select Top 1 #anything=[Anything],#id=#id+1 FROM Table WHERE ID>#id
if(##ROWCOUNT=0) break;
--Process #anything
END
I really do not see the point why you would need to resort to using dreaded cursor.
But here is another option if you are using SQL Server version 2005/2008
Use Recursion
declare #databases table
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
--; Insert records into #databases...
--; Recurse through #databases
;with DBs as (
select * from #databases where DatabaseID = 1
union all
select A.* from #databases A
inner join DBs B on A.DatabaseID = B.DatabaseID + 1
)
select * from DBs
-- [PO_RollBackOnReject] 'FININV10532'
alter procedure PO_RollBackOnReject
#CaseID nvarchar(100)
AS
Begin
SELECT *
INTO #tmpTable
FROM PO_InvoiceItems where CaseID = #CaseID
Declare #Id int
Declare #PO_No int
Declare #Current_Balance Money
While (Select ROW_NUMBER() OVER(ORDER BY PO_LineNo DESC) From #tmpTable) > 0
Begin
Select Top 1 #Id = PO_LineNo, #Current_Balance = Current_Balance,
#PO_No = PO_No
From #Temp
update PO_Details
Set Current_Balance = Current_Balance + #Current_Balance,
Previous_App_Amount= Previous_App_Amount + #Current_Balance,
Is_Processed = 0
Where PO_LineNumber = #Id
AND PO_No = #PO_No
update PO_InvoiceItems
Set IsVisible = 0,
Is_Processed= 0
,Is_InProgress = 0 ,
Is_Active = 0
Where PO_LineNo = #Id
AND PO_No = #PO_No
End
End
It's possible to use a cursor to do this:
create function [dbo].f_teste_loop
returns #tabela table
(
cod int,
nome varchar(10)
)
as
begin
insert into #tabela values (1, 'verde');
insert into #tabela values (2, 'amarelo');
insert into #tabela values (3, 'azul');
insert into #tabela values (4, 'branco');
return;
end
create procedure [dbo].[sp_teste_loop]
as
begin
DECLARE #cod int, #nome varchar(10);
DECLARE curLoop CURSOR STATIC LOCAL
FOR
SELECT
cod
,nome
FROM
dbo.f_teste_loop();
OPEN curLoop;
FETCH NEXT FROM curLoop
INTO #cod, #nome;
WHILE (##FETCH_STATUS = 0)
BEGIN
PRINT #nome;
FETCH NEXT FROM curLoop
INTO #cod, #nome;
END
CLOSE curLoop;
DEALLOCATE curLoop;
end
I'm going to provide the set-based solution.
insert #databases (DatabaseID, Name, Server)
select DatabaseID, Name, Server
From ... (Use whatever query you would have used in the loop or cursor)
This is far faster than any looping techique and is easier to write and maintain.
I prefer using the Offset Fetch if you have a unique ID you can sort your table by:
DECLARE #TableVariable (ID int, Name varchar(50));
DECLARE #RecordCount int;
SELECT #RecordCount = COUNT(*) FROM #TableVariable;
WHILE #RecordCount > 0
BEGIN
SELECT ID, Name FROM #TableVariable ORDER BY ID OFFSET #RecordCount - 1 FETCH NEXT 1 ROW;
SET #RecordCount = #RecordCount - 1;
END
This way I don't need to add fields to the table or use a window function.
I agree with the previous post that set-based operations will typically perform better, but if you do need to iterate over the rows here's the approach I would take:
Add a new field to your table variable (Data Type Bit, default 0)
Insert your data
Select the Top 1 Row where fUsed = 0 (Note: fUsed is the name of the field in step 1)
Perform whatever processing you need to do
Update the record in your table variable by setting fUsed = 1 for the record
Select the next unused record from the table and repeat the process
DECLARE #databases TABLE
(
DatabaseID int,
Name varchar(15),
Server varchar(15),
fUsed BIT DEFAULT 0
)
-- insert a bunch rows into #databases
DECLARE #DBID INT
SELECT TOP 1 #DBID = DatabaseID from #databases where fUsed = 0
WHILE ##ROWCOUNT <> 0 and #DBID IS NOT NULL
BEGIN
-- Perform your processing here
--Update the record to "used"
UPDATE #databases SET fUsed = 1 WHERE DatabaseID = #DBID
--Get the next record
SELECT TOP 1 #DBID = DatabaseID from #databases where fUsed = 0
END
Step1: Below select statement creates a temp table with unique row number for each record.
select eno,ename,eaddress,mobno int,row_number() over(order by eno desc) as rno into #tmp_sri from emp
Step2:Declare required variables
DECLARE #ROWNUMBER INT
DECLARE #ename varchar(100)
Step3: Take total rows count from temp table
SELECT #ROWNUMBER = COUNT(*) FROM #tmp_sri
declare #rno int
Step4: Loop temp table based on unique row number create in temp
while #rownumber>0
begin
set #rno=#rownumber
select #ename=ename from #tmp_sri where rno=#rno **// You can take columns data from here as many as you want**
set #rownumber=#rownumber-1
print #ename **// instead of printing, you can write insert, update, delete statements**
end
This approach only requires one variable and does not delete any rows from #databases. I know there are a lot of answers here, but I don't see one that uses MIN to get your next ID like this.
DECLARE #databases TABLE
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into #databases
DECLARE #CurrID INT
SELECT #CurrID = MIN(DatabaseID)
FROM #databases
WHILE #CurrID IS NOT NULL
BEGIN
-- Do stuff for #CurrID
SELECT #CurrID = MIN(DatabaseID)
FROM #databases
WHERE DatabaseID > #CurrID
END
Here's my solution, which makes use of an infinite loop, the BREAK statement, and the ##ROWCOUNT function. No cursors or temporary table are necessary, and I only need to write one query to get the next row in the #databases table:
declare #databases table
(
DatabaseID int,
[Name] varchar(15),
[Server] varchar(15)
);
-- Populate the [#databases] table with test data.
insert into #databases (DatabaseID, [Name], [Server])
select X.DatabaseID, X.[Name], X.[Server]
from (values
(1, 'Roger', 'ServerA'),
(5, 'Suzy', 'ServerB'),
(8675309, 'Jenny', 'TommyTutone')
) X (DatabaseID, [Name], [Server])
-- Create an infinite loop & ensure that a break condition is reached in the loop code.
declare #databaseId int;
while (1=1)
begin
-- Get the next database ID.
select top(1) #databaseId = DatabaseId
from #databases
where DatabaseId > isnull(#databaseId, 0);
-- If no rows were found by the preceding SQL query, you're done; exit the WHILE loop.
if (##ROWCOUNT = 0) break;
-- Otherwise, do whatever you need to do with the current [#databases] table row here.
print 'Processing #databaseId #' + cast(#databaseId as varchar(50));
end
This is the code that I am using 2008 R2. This code that I am using is to build indexes on key fields (SSNO & EMPR_NO) n all tales
if object_ID('tempdb..#a')is not NULL drop table #a
select 'IF EXISTS (SELECT name FROM sysindexes WHERE name ='+CHAR(39)+''+'IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+char(39)+')'
+' begin DROP INDEX [IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+'] ON '+table_schema+'.'+table_name+' END Create index IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+ ' on '+ table_schema+'.'+table_name+' ('+COLUMN_NAME+') ' 'Field'
,ROW_NUMBER() over (order by table_NAMe) as 'ROWNMBR'
into #a
from INFORMATION_SCHEMA.COLUMNS
where (COLUMN_NAME like '%_SSNO_%' or COLUMN_NAME like'%_EMPR_NO_')
and TABLE_SCHEMA='dbo'
declare #loopcntr int
declare #ROW int
declare #String nvarchar(1000)
set #loopcntr=(select count(*) from #a)
set #ROW=1
while (#ROW <= #loopcntr)
begin
select top 1 #String=a.Field
from #A a
where a.ROWNMBR = #ROW
execute sp_executesql #String
set #ROW = #ROW + 1
end
SELECT #pk = #pk + 1
would be better:
SET #pk += #pk
Avoid using SELECT if you are not referencing tables are are just assigning values.