Increase performance of trigger in SQL Server - sql-server

I'm not a database expert but I am in need of some help making sure a trigger we are using to track an update on a table is the best way to handle our situation and is performing as it should. After loading the trigger we noticed some slow performance on the actual business system (user side).
Background: we are trying to capture the date/time of a transaction that happens so it can be referenced on a customer portal for our website.
The theory: the trigger monitors for a Update to a column to 'PI' and if that happens, it writes data to a table giving some basic information from 2 other tables that are related to to update.
Table 1 columns
RH.kbranch, RH.kordnum, RH.kcustnum, RH.custsnum, RH.[program]
Table 2 columns
RD.kbranch, RD.kordnum, RD.kpart
Table 3 columns (where trigger is attached)
EQ.kequipnum, EQ.eqpstatus
Trigger
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[PICKUPTrigger]
ON [TEST].[dbo].[equip]
FOR UPDATE
AS
IF (SELECT eqpstatus FROM inserted) = 'PI'
BEGIN
SET NOCOUNT ON
INSERT INTO [Workfiles].[dbo].[PickupAudit] ([HHBranch],[HHOrder],[HHCustomer], [HHShipTo], [EquipID], [EQStatus], [PickupNo], [StatusDate])
SELECT
RH.kbranch, RH.kordnum, RH.kcustnum, RH.custsnum,
RD.kpart, EQ.eqpstatus, RH.[program], GETDATE()
FROM
TEST.dbo.renthead RH
JOIN
TEST.dbo.rentdetl RD ON RH.kbranch = RD.kbranch
AND RH.kordnum = RD.kordnum
AND RH.program NOT LIKE 'OPSS%'
JOIN
TEST.dbo.equip EQ ON EQ.kequipnum = RD.kpart
WHERE
RD.kpart = (SELECT kequipnum FROM inserted);
END
The trigger works, but it appears to be causing problems and slowing down the actual user experience. Any help in tweaking what we have done is appreciated and if you have any questions, feel free to ask. Thanks.

You should use explicit joins:
INSERT INTO [Workfiles].[dbo].[PickupAudit]
([HHBranch],[HHOrder],[HHCustomer],[HHShipTo],[EquipID],[EQStatus],[PickupNo],[StatusDate])
SELECT RH.kbranch, RH.kordnum, RH.kcustnum, RH.custsnum, RD.kpart, EQ.eqpstatus, RH.[program], GETDATE()
FROM TEST.dbo.renthead RH JOIN
TEST.dbo.rentdetl RD
ON RH.kbranch = RD.kbranch AND
RH.kordnum = RD.kordnum AND
RH.program NOT LIKE 'OPSS%' JOIN
TEST.dbo.equip EQ
ON EQ.kequipnum = RD.kpart JOIN
inserted i
ON RD.kpart = i.kequipnum;
For performance, you want indexes on the columns used in the JOINs, in this order:
TEST.dbo.rentdetl(kpart, kbanch, kordnum)
TEST.dbo.equip(kequipnum)
TEST.dbo.renthead(kbranch, kbanch, program)

The slow is caused by th join statements, as I think you join heavy tables with data or tables under load sometimes at the trigger operation time
The solution to get better performance is to create "indexed view" not just "view"
And use it in trigger, and you will see drastically affect

Related

prevent duplicate value to submit using stored procedure in sql server

I want to prevent the same #coupon_value in sp to submit and return
any message for validation using csharp but I am not able to how to
make changes in stored procedure.
CREATE PROCEDURE [dbo].[USP_REBATE_CAMPAIGN_RULE_DETAIL_VALIDATE]
#rebate_campaign_seq INT,
#coupon_value Varchar(50)='',
#Type varchar(50)='SERIES'
AS
SET NOCOUNT ON
BEGIN
SELECT rcrd.rebate_campaign_rule_detail_seq AS id,Type_value AS NAME,
'SERIES' AS type,
rcrd.amount_per_range AS Amount
FROM rebate_campaign_rule_detail rcrd (nolock)
INNER JOIN rebate_campaign_rule rcr
ON rcr.rebate_campaign_rule_seq = rcrd.rebate_campaign_rule_seq
INNER JOIN rebate_campaign rc
ON rc.rebate_campaign_seq = rcr.rebate_campaign_seq
WHERE rc.rebate_campaign_seq = #rebate_campaign_seq
AND rcrd.active_flag = 'Y' AND rcrd.type = #Type
AND rcrd.type_value=#coupon_value
End
The only way to prevent duplicate in database data is to add a UNIQUE CONSTRAINT. Everything else will fail, especially any solution coded with a procedural program, because of concurrency (imagine for a moment that two users launch the same procedure with the same values at the same time...).
To have a NULLbale UNIQUE constraint, you can add a UNIQUE filtered INDEX like this one :
CREATE INDEX X_UNIQUE_COUPON_RCRD
ON rebate_campaign_rule_detail (type_value)
WHERE type_value IS NOT NULL;

SQL Trigger Works in Play but not Production

I created an SQL trigger in my Play database and it worked great. When I moved it over to Production, it suddenly won't work. We want the trigger to kick off whenever someone edits one of two custom fields in our database. The company who created the software already set up a trigger that kicks of any time a change is made to the database object (it just didn't track the changes made to custom fields). If I let my new trigger create a new record, I wound up with two audit records, so I changed my trigger to update the audit record the software company's trigger created. Could anyone tell me what I have done wrong? Here is my trigger:
USE [TmsEPrd]
GO
/****** Object: Trigger [dbo].[tr_Biograph_Udef_Audit_tracking] Script Date: 11/23/2020 10:22:57 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[tr_Biograph_Udef_Audit_tracking] ON [dbo].[BIOGRAPH_MASTER] FOR UPDATE AS
BEGIN
IF EXISTS (SELECT 1 FROM deleted d
JOIN inserted i ON d.ID_NUM = i.ID_NUM
JOIN (SELECT ID_NUM, binary_checksum(UDEF_10A_1, UDEF_2A_4) AS inserted_checksum
FROM inserted) a ON i.ID_NUM = a.ID_NUM
JOIN (SELECT ID_NUM, binary_checksum(UDEF_10A_1, UDEF_2A_4) AS deleted_checksum
FROM deleted) b ON d.ID_NUM = b.ID_NUM
WHERE a.inserted_checksum <> b.deleted_checksum)
BEGIN
Update BIOGRAPH_HISTORY
set archive_job_name = 'UDEF_Change',
udef_2a_4 = i.udef_2a_4,
udef_2a_4_CHG = i.udef_2a_4_chg,
udef_10a_1 = i.udef_10a_1,
udef_10a_1_chg = i.udef_10a_1_chg
from
(select i.ID_NUM, SYSDATETIME()as job_time_a,
i.UDEF_10A_1, case when i.UDEF_10A_1 = d.UDEF_10A_1 then 0 when i.UDEF_10A_1 is null and d.UDEF_10A_1 is null then 0 else 1 end as UDEF_10A_1_CHG,
i.UDEF_2A_4, case when i.UDEF_2A_4 = d.UDEF_2A_4 then 0 when i.UDEF_2A_4 is null and d.UDEF_2A_4 is null then 0 else 1 end as UDEF_2A_4_CHG,
d.USER_NAME,d.JOB_NAME,d.JOB_TIME
FROM deleted d JOIN inserted i ON d.ID_NUM = i.ID_NUM) i
join BIOGRAPH_HISTORY b on i.ID_NUM = b.ID_NUM
where DATEDIFF(Minute, i.job_time_a, b.ARCHIVE_JOB_TIM) = 0
and b.ARCHIVE_JOB_NAME not like 'UDEF_Change%'
END;
END;
Try specifying #order = 'LAST' for your trigger. It might be that your trigger is executing first and not finding a record to update. In your test system, the trigger execution order might be reversed.
The order that triggers are created might affect trigger execution order, but this is not something to rely upon. When you think about it, this can be a headache. A test system that looks just like production can behave differently.
This is similar to relying upon a "natural" record order of a clustered index and not using a ORDER BY clause. A different execution plan can use a different index or go parallel resulting in a different or no order.

Set values on a new table from a historical table, before adding new table back into historical table

I would like to create a historical view of alerts in an application. To do this, I am grabbing all events and timestamping them, then uploading them into a MS SQL table. I would also like to be able to exempt certain objects from the total count by flagging either the finding (to exclude the finding across all systems) or the object in the finding (to exclude the object from all findings).
The idea is, I will have all previous alerts in the main table, then I will set an 'exemptobject' or 'exemptfinding' bit column in the row. When I re-run the script weekly, I will upload the results directly into a temporary table and then I would like to compare either the object or the finding for each object in the temporary table to the main database's 'object' or 'finding' and set the respective 'exemptobject' or 'exemptfinding' bit. Once all the temporary table's objects have any exemption bits set, insert the temporary table into the main table and drop the temporary table to keep a historical record.
This will give me duplicate findings and objects, so I am having difficulty with the merge command:
BEGIN TRANSACTION
MERGE INTO [dbo].[temp_table]
USING [dbo].[historical]
ON [dbo].[temp_table].[object] = [dbo].[historical].[object] OR
[dbo].[temp_table].[finding] = [dbo].[historical].[finding]
WHEN MATCHED THEN
UPDATE
SET [exemptfinding] = [dbo].[historical].[exemptfinding]
,[exemptobject] = [dbo].[historical].[exemptobject]
,[exemptdate] = [dbo].[historical].[exemptdate]
,[comments] = [dbo].[historical].[comments];
COMMIT
This seems to do what I want, but I see that the results are going to grow exponentially and I think it won't be sustainable for long.
BEGIN TRANSACTION
UPDATE [dbo].[temp]
SET [dbo].[temp].[exemptfinding] = [historical].[exemptfinding]
,[dbo].[temp].[exemptobject] = [historical].[exemptobject]
,[dbo].[temp].[exemptdate] = [historical].[exemptdate]
,[dbo].[temp].[comments] = [historical].[comments]
FROM [dbo].[temp] temp
INNER JOIN [dbo].[historical] historical
ON (
[temp].[finding] = [sci].[finding] OR
[temp].[object] = [sci].[object] OR
) AND
(
[historical].[exemptfinding] = 1 OR
[historical].[exemptobject] = 1
)
COMMIT
I feel like I need to normalize the database, but I can't think of a way to separate things out and be able to:
See a count of each finding based on date the script was run
Be able to drill down into each day and see all the findings, objects and recommendations for each
Control the count shown for each finding by removing 'exempted' findings OR objects.
I feel like there's something obvious I'm missing or I'm thinking about this incorrectly. Any help would be greatly appreciated!
EDIT - The following seems to do what I want, but as soon as I add an additional WHERE condition to the final result, the query time goes from 7 seconds to 90 seconds, so I fear it will not scale.
BEGIN TRANSACTION
UPDATE [dbo].[temp]
SET [dbo].[temp].[exemptrecommendation] = [historical].[exemptrecommendation]
,[dbo].[temp].[exemptfinding] = [historical].[exemptfinding]
,[dbo].[temp].[exemptobject] = [historical].[exemptobject]
,[dbo].[temp].[exemptdate] = [historical].[exemptdate]
,[dbo].[temp].[comments] = [historical].[comments]
FROM (
SELECT *
FROM historical h
WHERE EXISTS (
SELECT id
,recommendation
FROM temp t
WHERE (
t.id = s.id OR
t.recommendation = s.recommendation
)
)
) historical
WHERE [dbo].[temp].[recommendation] = [historical].[recommendation] OR
[dbo].[temp].[id] = [historical].[id]
COMMIT

checking if the same row exists in the table or not [duplicate]

I've got a table with data named energydata
it has just three columns
(webmeterID, DateTime, kWh)
I have a new set of updated data in a table temp_energydata.
The DateTime and the webmeterID stay the same. But the kWh values need updating from temp_energydata table.
How do I write the T-SQL for this the correct way?
Assuming you want an actual SQL Server MERGE statement:
MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target
USING dbo.temp_energydata AS source
ON target.webmeterID = source.webmeterID
AND target.DateTime = source.DateTime
WHEN MATCHED THEN
UPDATE SET target.kWh = source.kWh
WHEN NOT MATCHED BY TARGET THEN
INSERT (webmeterID, DateTime, kWh)
VALUES (source.webmeterID, source.DateTime, source.kWh);
If you also want to delete records in the target that aren't in the source:
MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target
USING dbo.temp_energydata AS source
ON target.webmeterID = source.webmeterID
AND target.DateTime = source.DateTime
WHEN MATCHED THEN
UPDATE SET target.kWh = source.kWh
WHEN NOT MATCHED BY TARGET THEN
INSERT (webmeterID, DateTime, kWh)
VALUES (source.webmeterID, source.DateTime, source.kWh)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
Because this has become a bit more popular, I feel like I should expand this answer a bit with some caveats to be aware of.
First, there are several blogs which report concurrency issues with the MERGE statement in older versions of SQL Server. I do not know if this issue has ever been addressed in later editions. Either way, this can largely be worked around by specifying the HOLDLOCK or SERIALIZABLE lock hint:
MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target
[...]
You can also accomplish the same thing with more restrictive transaction isolation levels.
There are several other known issues with MERGE. (Note that since Microsoft nuked Connect and didn't link issues in the old system to issues in the new system, these older issues are hard to track down. Thanks, Microsoft!) From what I can tell, most of them are not common problems or can be worked around with the same locking hints as above, but I haven't tested them.
As it is, even though I've never had any problems with the MERGE statement myself, I always use the WITH (HOLDLOCK) hint now, and I prefer to use the statement only in the most straightforward of cases.
I often used Bacon Bits great answer as I just can not memorize the syntax.
But I usually add a CTE as an addition to make the DELETE part more useful because very often you will want to apply the merge only to a part of the target table.
WITH target as (
SELECT * FROM dbo.energydate WHERE DateTime > GETDATE()
)
MERGE INTO target WITH (HOLDLOCK)
USING dbo.temp_energydata AS source
ON target.webmeterID = source.webmeterID
AND target.DateTime = source.DateTime
WHEN MATCHED THEN
UPDATE SET target.kWh = source.kWh
WHEN NOT MATCHED BY TARGET THEN
INSERT (webmeterID, DateTime, kWh)
VALUES (source.webmeterID, source.DateTime, source.kWh)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
If you need just update your records in energydata based on data in temp_energydata, assuming that temp_enerydata doesn't contain any new records, then try this:
UPDATE e SET e.kWh = t.kWh
FROM energydata e INNER JOIN
temp_energydata t ON e.webmeterID = t.webmeterID AND
e.DateTime = t.DateTime
Here is working sqlfiddle
But if temp_energydata contains new records and you need to insert it to energydata preferably with one statement then you should definitely go with the answer that Bacon Bits gave.
UPDATE ed
SET ed.kWh = ted.kWh
FROM energydata ed
INNER JOIN temp_energydata ted ON ted.webmeterID = ed.webmeterID
Update energydata set energydata.kWh = temp.kWh
where energydata.webmeterID = (select webmeterID from temp_energydata as temp)
THE CORRECT WAY IS :
UPDATE test1
INNER JOIN test2 ON (test1.id = test2.id)
SET test1.data = test2.data

SQL Server won't create trigger on table I can query

The command:
select * from dbo.hmg_cahplots
returns 9250 rows. However, when I try to create a trigger, it fails with:
Msg 8197, Level 16, State 6, Procedure LotUpdateTrigger_tdp, Line 1
The object 'dbo.hmg_cahplots' does not exist or is invalid for this
operation.
Trigger code is:
CREATE TRIGGER dbo.LotUpdateTrigger_tdp ON dbo.hmg_cahplots FOR UPDATE, INSERT
AS
BEGIN
update lot
set lot.hmg_planmodelname = model.hmg_modelname, lot.hmg_thermslotincentive = model.hmg_thermsincentive,
lot.hmg_thermslotincentive_base = model.hmg_thermsincentive_base, lot.hmg_kwlotincentive = model.hmg_kwincentive
from hmg_cahplots as lot inner join i
on lot.hmg_cahplotsid = i.hmg_cahplotsid
inner join hmg_pgecahp as proj
on proj.hmg_pgecahpid = lot.hmg_pgecahplots
left outer join hmg_pgecahpmodels as model
on model.hmg_pgecahpmodelsid = lot.hmg_cahpplanstolotsid
and model.hmg_pgecahpplansid = lot.hmg_pgecahplots
END
I doubt this is very hard to solve. I assume I need to specify a namespace or something. However, I'm new to SQL Server and I don't have any idea how to start on this.
Thanks -- Tim
Are you sure you are located in correct database, not master?
Are you sure your permissions are fine?
Are you sure this is a table, not a view?
If you are sure that this table exists and you are trying to create trigger in the same database, then remove coma just before from and after lot.hmg_kwlotincentive = model.hmg_kwincentive,.
Two problems:
Triggers are mostly Schema-locked.
You are using more than one.
use the same schema and ADD:
-- AT start add the code
USE [DATABASE] --switch with database name
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
--At the END add
END
GO

Resources