SQL Server 2017 trigger - declaring variable - sql-server

Today's the first time I have to work on SQL Server, usually I use MySQL.
I did some searching, but, I just don't get it.
Here's the code:
CREATE TRIGGER "PREMI_after_insert"
ON "PREMI"
AFTER INSERT
AS
DECLARE #umur INT
SELECT
#umur = DATEDIFF(YEAR, CAST(GETDATE() AS DATE),
(CASE
WHEN ISNUMERIC(SELECT NoTerdaftar FROM inserted) = 1
THEN (SELECT CAST(TglLahir AS DATE)
FROM KARYAWAN
WHERE NoPegawai = (SELECT NoTerdaftar FROM inserted))
ELSE (SELECT CAST(TglLahir AS DATE)
FROM KELUARGA
WHERE NoPeserta = (SELECT NoTerdaftar FROM inserted))
END))
DECLARE #plafond DECIMAL(20,0)
SELECT #plafond = (CASE
WHEN ISNUMERIC(SELECT NoTerdaftar FROM inserted) = 1
THEN (SELECT Plafond
FROM KARYAWAN
WHERE NoPegawai = (SELECT NoTerdaftar FROM inserted))
ELSE (SELECT Plafond
FROM KELUARGA
WHERE NoPeserta = (SELECT NoTerdaftar FROM inserted))
END)
DECLARE #rat DECIMAL(7,6)
SELECT #rat = RE.Rate
FROM RATES RE
WHERE RE.KodeAsuransi = (SELECT JenisAsuransi FROM ASURANSI
WHERE Id = (SELECT ID_Asuransi FROM inserted))
AND #umur BETWEEN RE.UsiaMin AND RE.UsiaMax
BEGIN
UPDATE PREMI
SET JumlahPremi = (#plafond * #rat)
WHERE Id = (SELECT Id FROM insterted)
END;
and this is the error
Please, don't bully me, that code is what I get after reading tons of other thread....
I just don't understand the structure of trigger in SQL Server.
Some use "GO" word, some don't even use BEGIN and END which is the key in MySQL....

Related

Using DECLARE while creating a VIEW?

The literature says that the declare statement is not compatible with creating a View. How do I get around it?
My declare statement looks like:
DECLARE #risk_5 TABLE (Code VARCHAR(100));
INSERT INTO #risk_5 (Code) VALUES ('AA'),('BB'),('CC');
and is then used within a select statement:
SELECT
id,
CASE
WHEN a.[10_2_1_Country] IN (SELECT Code from #risk_5)
THEN '3'
END AS Risk_Country5
FROM x
The recommendation is to pack the declare into a CTE or a stored procedure.
With both these recommendations though, I do not understand how to connect the two? What am I missing?
If you need to use variable try to use stored procedures, if you write a select query in the stored procedure you can get the data too. And you can use declare inside.
I use this way in my solution e.g.
CREATE PROCEDURE [dbo].[GetLoadSiteMass]( #month INT,
#year int,
#storageId int,
#parent nvarchar(50),
#materialSourceId nvarchar(100),
#complexIds nvarchar(50))
AS
BEGIN
DECLARE #MonthPrev int
DECLARE #YearPrev int
SET #MonthPrev = CASE WHEN #Month = 1 THEN 12 ELSE #Month - 1 END
SET #YearPrev = CASE WHEN #Month = 1 THEN #Year - 1 ELSE #Year END
declare #WagonLoadSiteId int
set #WagonLoadSiteId = (select top 1 CarriageLoadSiteId from CarriageLoadSite where LoadSiteType = 2);
DECLARE #loadSide nvarchar(10), #result decimal(18,3)
SET #loadSide=cast( #storageId as nvarchar(50));
WITH CarriageLoadSiteTreeView (
[CarriageLoadSiteId],RootId,RootName,[Code], Name, ParentID, [LoadSiteType],IsDelete,
CodeSAP,DepartmentId, Capacity, MinLimit, MaxLimit, LoadSitePlaceTypeId) AS
(
SELECT [CarriageLoadSiteId],
[CarriageLoadSiteId] RootId,
Name RootName,
[Code],
Name,
ParentID,
[LoadSiteType],
[IsDelete],
CodeSAP,
DepartmentId,
Capacity,
MinLimit,
MaxLimit,
LoadSitePlaceTypeId
FROM CarriageLoadSite WITH(NOLOCK)
WHERE ISNULL(ParentID,0) =isnull(#storageId,0) AND Isdelete!=1
UNION ALL
SELECT d.[CarriageLoadSiteId],
q.RootId RootId,
RootName RootName,
d.[Code],
d.Name,
d.ParentID,
d.[LoadSiteType],
d.[IsDelete],
d.CodeSAP,
d.DepartmentId,
d.Capacity,
d.MinLimit,
d.MaxLimit,
d.LoadSitePlaceTypeId
FROM CarriageLoadSite AS d WITH(NOLOCK)
INNER JOIN CarriageLoadSiteTreeView AS q ON d.ParentID = q.[CarriageLoadSiteId] WHERE d.IsDelete!=1
)
SELECT
ComplexId,
RootId Id,
cast(RootId as nvarchar(8))+'|Sclad'+IIF(RootId=max(R.CarriageLoadSiteId),'|finish','') [Uid],
RootName CarriageLoadSiteName,
ROUND(SUM(AMOUNT-movement-consumption)/1000,3) Amount,
cast(1 as bit) hasChildren,
T.FullPathId Path,
UparentId=IIF(#parent is null,'',#parent),
[Type]=0,
Petal = IIF(RootId=max(R.CarriageLoadSiteId),'|Sclad|finish','')
FROM (
SELECT
RootId
,RootName
,t.CarriageLoadSiteId
,t.MaterialId
,YEAR(t.Period) [Year]
,MONTH(t.Period) [Month]
,round( case when (t.Amount=0 or t.Amount is null) and (tt.Type=0 or [TypeAmountCarriage]=1 )then carr.[CertifNetto]else t.Amount end ,0 )[Amount]
,t.UnitId
, CarriageId
, tt.TurnoverTypeId
,round(dbo.GetMovementByTurnOverWithTempValue(t.turnoverid),5) movement
,dbo.GetConsumptionByTurnOver(t.turnoverid) consumption
,0 stockBegin
,round(t.Amount,0 ) CommingAmount
,case when (t.Amount=0 or t.Amount is null) and tt.Type=0 then 1 else 0 end [IsNotConfirmed]
,[TypeAmountCarriage]
,M.ComplexId
FROM Turnover t WITH(NOLOCK)
INNER JOIN TurnoverType tt ON tt.TurnoverTypeId = t.TurnoverTypeId
INNER JOIN CarriageLoadSiteTreeView l ON l.CarriageLoadSiteId = t.CarriageLoadSiteId
INNER JOIN [Carriages] carr on carr.[CarriagesID]=t.[CarriageId]
INNER JOIN Material M on M.MaterialID=t.MaterialId
WHERE YEAR(t.Period) = #Year AND
MONTH(t.Period) = #Month AND
l.LoadSiteType = 0 AND
tt.type in (0,5,4) AND
isclear=0 AND
M.MaterialSourceID in (select value from string_split(#materialSourceId, ','))
UNION ALL
SELECT RootId
,RootName
,s.CarriageLoadSiteId
,s.MaterialId
,#Year [Year]
,#Month [Month]
,round(s.Amount,0)
,s.UnitId
,CarriageId
,[TurnoverTypeId]
,round(dbo.GetMovementByStock(s.StockId),5) movement
,dbo.GetConsumptionByStock(s.StockId) consumption
,round(s.Amount,0)-s.spendStock
,0
,0 [IsNotConfirmed]
,[TypeAmountCarriage]
,M.ComplexId
FROM Stock s
INNER JOIN CarriageLoadSiteTreeView l ON l.CarriageLoadSiteId = s.CarriageLoadSiteId
INNER JOIN Material M on M.MaterialID=s.MaterialId
WHERE s.[Year] = #YearPrev AND
s.[Month] = #MonthPrev AND
s.[Type] = 0 AND
l.LoadSiteType = 0 AND
amount >0 AND
isclear=0 AND
M.MaterialSourceID in (select value from string_split(#materialSourceId, ','))
) as R
INNER JOIN CariageLoadSiteTree T on T.CarriageLoadSiteId=RootId
INNER JOIN string_split(#complexIds, ',') MM ON CAST(MM.value AS int) = R.ComplexId
WHERE AMOUNT-movement-consumption>10
GROUP BY RootName,RootId,ComplexId, T.FullPathId
ORDER BY RootName

SQL Server if condition based on date

I need help with, giving the if exists and if not exists condition.
I want to write an if condition, based on date, such that, if any row exists in table1 on current date TARGET_DT for a particular job, then do some action
else do other action.
IF EXISTS(SELECT 1 FROM COD_BLU_OPENING_VOLUME
WHERE TARGET_DT = CONVERT(DATETIME, GETDATE(), 101) AND JOBID = #OPENJOB)
BEGIN
IF(SELECT COUNT(1) FROM COD_BLU_INVENTORY WITH(NOLOCK)
WHERE (JOBID = #JOB AND STATUS = 'A' AND TARGETDT = CONVERT(DATETIME, #TARGETDT, 101))) = 0
BEGIN
INSERT INTO COD_BLU_INVENTORY(JOBID, TARGETDT, CARRYOVER, FRESHVOL, TOTALVOL, STATUS, UPDATEDBY, UPDATEDT, OPENINGVOL)
VALUES(#JOB, #TARGETDT, #CARRYOVERVOL, #FRESHVOL, #TOTALVOL, 'A', #EMPCODE, GETDATE(), #CARRYOVERVOL)
SELECT '1'
END
ELSE
BEGIN
SELECT'0'
END
END
ELSE IF NOT EXISTS(SELECT 1 FROM COD_BLU_OPENING_VOLUME
WHERE TARGET_DT = CONVERT(DATETIME, GETDATE(), 101)
AND TARGET_DT < CONVERT(DATETIME, GETDATE(), 101)
AND JOBID = #OPENJOB)
BEGIN
INSERT INTO COD_BLU_OPENING_VOLUME(TARGET_DT, JOBID, OPENING_COUNT, UPDATED_BY, UPDATED_DT, PRE_ASSIGNEDCOUNT)
VALUES(#OPENTARDT, #OPENJOB, #OPENCNT, #OPENEMPCODE, GETDATE(), #PREOPENCNT)
IF (SELECT COUNT(1) FROM COD_BLU_INVENTORY WITH(NOLOCK)
WHERE (JOBID = #JOB AND STATUS = 'A'
AND TARGETDT = CONVERT(DATETIME, #TARGETDT, 101))) = 0
BEGIN
INSERT INTO COD_BLU_INVENTORY(JOBID, TARGETDT, CARRYOVER, FRESHVOL, TOTALVOL, STATUS, UPDATEDBY, UPDATEDT, OPENINGVOL)
VALUES(#JOB, #TARGETDT, #CARRYOVERVOL, #FRESHVOL, #TOTALVOL, 'A', #EMPCODE, GETDATE(), #CARRYOVERVOL)
SELECT '1'
END
ELSE
BEGIN
SELECT '0'
END
END
but the problem is that if the TARGET_DT is given as future date, it executes the else condition, I want it to execute the if condition, the above is the code which I tired. Any help in solving this will be appreciated. Please help.
IF EXISTS(SELECT 1 FROM COD_BLU_OPENING_VOLUME WHERE ((TARGET_DT=CONVERT(DATETIME,GETDATE(),101) OR TARGET_DT>CONVERT(DATETIME,GETDATE(),101)) AND JOBID=#OPENJOB))
BEGIN
IF(SELECT COUNT(1) FROM COD_BLU_INVENTORY WITH(NOLOCK) WHERE (JOBID=#JOB AND STATUS='A' AND (TARGET_DT=CONVERT(DATETIME,GETDATE(),101) OR TARGET_DT>CONVERT(DATETIME,GETDATE(),101))))=0
BEGIN
INSERT INTO COD_BLU_INVENTORY(JOBID,TARGETDT,CARRYOVER,FRESHVOL,TOTALVOL,STATUS,UPDATEDBY,UPDATEDT,OPENINGVOL)
VALUES(#JOB,#TARGETDT,#CARRYOVERVOL,#FRESHVOL,#TOTALVOL,'A',#EMPCODE,GETDATE(),#CARRYOVERVOL)
SELECT '1'
END
ELSE
BEGIN
SELECT'0'
END
END
ELSE IF NOT EXISTS(SELECT 1 FROM COD_BLU_OPENING_VOLUME WHERE ((TARGET_DT=CONVERT(DATETIME,GETDATE(),101) OR TARGET_DT<CONVERT(DATETIME,GETDATE(),101)) AND JOBID=#OPENJOB))
BEGIN
INSERT INTO COD_BLU_OPENING_VOLUME(TARGET_DT,JOBID,OPENING_COUNT,UPDATED_BY,UPDATED_DT,PRE_ASSIGNEDCOUNT)
VALUES(#OPENTARDT,#OPENJOB,#OPENCNT,#OPENEMPCODE,GETDATE(),#PREOPENCNT)
IF (SELECT COUNT(1) FROM COD_BLU_INVENTORY WITH(NOLOCK) WHERE (JOBID=#JOB AND STATUS='A' AND TARGETDT = CONVERT(DATETIME,#TARGETDT,101)))=0
BEGIN
INSERT INTO COD_BLU_INVENTORY(JOBID,TARGETDT,CARRYOVER,FRESHVOL,TOTALVOL,STATUS,UPDATEDBY,UPDATEDT,OPENINGVOL)
VALUES(#JOB,#TARGETDT,#CARRYOVERVOL,#FRESHVOL,#TOTALVOL,'A',#EMPCODE,GETDATE(),#CARRYOVERVOL)
SELECT '1'
END
ELSE
BEGIN
SELECT'0'
END
END
END

How to make that a parameter reads more than one value on stored procedure?

I'm making a stored procedure to know the future dates in which a maintenance should be scheduled, but there are a lot of machines on the table but procedure only calls one.
The Declares that I used are for it to call the data of each machine and do the cycle to know the future dates in the range given
#name varchar(50),
#FechaHasta datetime,
#Result DATETIME = null OUTPUT)
AS
BEGIN
DECLARE #JOBNO NVARCHAR(50)
SELECT #JOBNO = JOBNO FROM PMMASTER INNER JOIN LABORMP
ON PMMASTER.PMID = LABORMP.PMID INNER JOIN PMMSCHEDULE
ON PMMASTER.PMID = PMMSCHEDULE.PMID
where EMPLOYEENAME = #name AND PMMSCHEDULE.CYCLETYPE =2 and PMMSCHEDULE.SETTING2 IS NOT NULL and PMMSCHEDULE.SETTING1 IS NOT NULL
DECLARE #DATELAST DATETIME
SELECT #DATELAST = DATELAST FROM PMMSCHEDULE INNER JOIN PMMASTER
ON PMMASTER.PMID = PMMSCHEDULE.PMID INNER JOIN LABORMP
ON PMMASTER.PMID = LABORMP.PMID
WHERE JOBNO in (#JOBNO) and EMPLOYEENAME =#name
DECLARE #SETTING1 INT
SELECT #SETTING1 = SETTING1 FROM PMMSCHEDULE INNER JOIN PMMASTER
ON PMMASTER.PMID = PMMSCHEDULE.PMID INNER JOIN LABORMP
ON PMMASTER.PMID = LABORMP.PMID
WHERE JOBNO in (#JOBNO) AND EMPLOYEENAME = #name
DECLARE #SETTING2 INT
SELECT #SETTING2 = SETTING2 FROM PMMSCHEDULE INNER JOIN PMMASTER
ON PMMASTER.PMID = PMMSCHEDULE.PMID INNER JOIN LABORMP
ON PMMASTER.PMID = LABORMP.PMID
WHERE JOBNO in (#JOBNO) AND EMPLOYEENAME = #name
DECLARE
#DateFirst INT
SELECT #DateFirst = ##DateFirst
/* Today */
DECLARE
#TodaysDate DATETIME
-- Finding today's date after resetting the time to midnight
SELECT #TodaysDate = CONVERT(DATETIME, CONVERT(VARCHAR, GETDATE(), 101), 101)
/* Todays WeekDay */
DECLARE
#TodaysWeekDay INT
SELECT #TodaysWeekDay = DATEPART(dw, #TodaysDate)
/* Selected WeekDay */
DECLARE
#WeekDay INT
SELECT #WeekDay = CHARINDEX('1', #Setting2)
/* Schedule Date */
DECLARE
#ScheduleDate DATETIME
-- Find the starting schedule date. If the schedule date is in a previous
-- week, adjust to the most recent week in the schedule
SELECT #ScheduleDate = #DATELAST
-- Adjust scheduled date to closest possible week day
SELECT #ScheduleDate = DATEADD(dd, #WeekDay - DATEPART(dw, #ScheduleDate), #ScheduleDate)
-- Add cycles until a possible date is met
WHILE (#ScheduleDate < #TodaysDate)
SELECT #ScheduleDate = DATEADD(wk, #Setting1, #ScheduleDate)
IF (#ScheduleDate <= #DATELAST)
SELECT #ScheduleDate = DATEADD(wk, #Setting1, #ScheduleDate)
SELECT #Result = #ScheduleDate
SELECT #Result AS RESULTADO
SET DATEFIRST #DateFirst
WHILE(#Result <#FechaHasta)
BEGIN
IF(#Result <#FechaHasta)
SELECT #Result = DATEADD(WK, #Setting1, #Result)
SET IDENTITY_INSERT [00TblFecha] ON
INSERT INTO dbo.[00TblFecha](idFecha,jobno,fecha)VALUES(3,#JOBNO,#Result)
SET IDENTITY_INSERT[00TblFecha] OFF
print #Result
end
You have few options: - Use table valued parameter, which is preferable: (https://msdn.microsoft.com/en-us/library/bb510489.aspx) - Use XML parameter - Use comma delimited list. - Do the loop over all of your "machines"
By the way, exclude usage of ##DateFirst in your SP. Anyway you do not use it.

Updating Next_ID column

I have the following table:
VehicleID Reg_ID Next_RegID EntryDate
330034 9111 NULL 2010-12-06 00:00:00
330034 9113 NULL 2010-12-09 00:00:00
On the first row I need to update the Next_RegId column with the Reg_ID of the second row where VehicleId or (VIN/ChassisNumber) is the same. The Next_RegID column on the last entry should remain Null.
I've created a while loop procedure which works perfectly, but with millions of records in the table it takes ages to complete. Therefore, I was wondering if any of you dealt with this kind of a problem and have a solution for it.
Here's the procedure I wrote, and thanks in advance for all your help:
Declare #i as integer;
Declare #x as integer;
Declare #y as integer
Set #i= (Select Max(RID) from TempRegistration)
Set #x= 0
Set #y= 1
Declare #curChassis as nvarchar(100)
Declare #nextChassis as nvarchar(100)
While (#x <= #i)
Begin
set #curChassis = (Select ChassisNumber from TempRegistration where RID = #x)
set #nextChassis = (Select ChassisNumber from TempRegistration where RID = #y)
If (#curChassis = #nextChassis)
Begin
Update Registration set NextRegistrationId = (Select RegistrationId from TempRegistration where RID = #y)
Where RegistrationId = (Select RegistrationId from TempRegistration where RID = #x)
End
Set #x = #x + 1
Set #y = #y + 1
Print(#x)
End
TempRegistration is a temporary table I've created to assign a row_id which guides the while loop to assign the Reg_ID to the Next_RegId on the previous row.
This can be done with one UPDATE query. You haven't mentioned your RDBMS so...
For MSSQL:
Update Registration as t1
set NextRegistrationId = (Select TOP 1 RegistrationId
from Registration
where RID = t1.RID
and EntryDate>t1.EntryDate
order by EntryDate DESC)
For MySQL
Update Registration as t1
set NextRegistrationId = (Select RegistrationId
from Registration
where RID = t1.RID
and EntryDate>t1.EntryDate
order by EntryDate DESC
LIMIT 1)
If RID's are increasing with EntryDate then
Update Registration as t1
set NextRegistrationId = (Select MIN(RegistrationId)
from Registration
where RID = t1.RID
and EntryDate>t1.EntryDate
)
Tested and it seems to be working but this version uses a CTE (SQL Server)
with RegDetails as
(
select VehicleID, Reg_ID, ROW_NUMBER() OVER(PARTITION BY VehicleID ORDER BY EntryDate) AS ROWNUMBER
FROM dbo.Vehicle)
UPDATE a SET a.Next_RegID = b.Reg_ID
FROM RegDetails b
INNER JOIN dbo.Vehicle a ON (a.VehicleID = b.VehicleID)
WHERE b.ROWNUMBER = 2 and a.Next_RegID IS NULL and a.Reg_ID != b.Reg_ID

Handling multiple records in a MS SQL trigger

I am having to use triggers in MSSQL for the first time, well triggers in general. Having read around and tested this myself I realise now that a trigger fires per command and not per row inserted, deleted or updated.
The entire thing is some statistics for an advertising system. Our main stat table is rather large and doesn't contain the data in a way that makes sense in most cases. It contains one row per advert clicked, viewed and etc. As a user one is more inclined to want to view this as day X has Y amount of clicks and Z amount of views and so forth. We have done this purely based on a SQL query so far, getting this sort of report from the main table, but as the table has grown so does the time for that query to execute. Because of this we have opted for using triggers to keep another table updated and hence making this a bit easier on the SQL server.
My issue is now to get this working with multiple records. What I have done is to create 2 stored procedures, one for handling the operation of an insert, and one for a delete. My insert trigger (written to work with a single record) then graps the data off the Inserted table, and sends it off to the stored procedure. The delete trigger works in the same way, and (obviously?) the update trigger does the same as a delete + an insert.
My issue is now how to best do this with multiple records. I have tried using a cursor, but as far as I have been able to read and see myself, this performs really badly. I have considered writing some "checks" as well - as in checking to see IF there are multiple records in the commands and then go with the cursor, and otherwise simply just avoid this. Anyhow, here's my solution with a cursor, and im wondering if there's a way of doing this better?
CREATE TRIGGER [dbo].[TR_STAT_INSERT]
ON [iqdev].[dbo].[Stat]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Date DATE
DECLARE #CampaignId BIGINT
DECLARE #CampaignName varchar(500)
DECLARE #AdvertiserId BIGINT
DECLARE #PublisherId BIGINT
DECLARE #Unique BIT
DECLARE #Approved BIT
DECLARE #PublisherEarning money
DECLARE #AdvertiserCost money
DECLARE #Type smallint
DECLARE InsertCursor CURSOR FOR SELECT Id FROM Inserted
DECLARE #curId bigint
OPEN InsertCursor
FETCH NEXT FROM InsertCursor INTO #curId
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Date = [Date], #PublisherId = [PublisherCustomerId], #Approved = [Approved], #Unique = [Unique], #Type = [Type], #AdvertiserCost = AdvertiserCost, #PublisherEarning = PublisherEarning
FROM Inserted
WHERE Id = #curId
SELECT #CampaignId = T1.CampaignId, #CampaignName = T2.Name, #AdvertiserId = T2.CustomerId
FROM Advert AS T1
INNER JOIN Campaign AS T2 on T1.CampaignId = T2.Id
WHERE T1.Id = (SELECT AdvertId FROM Inserted WHERE Id = #curId)
EXEC ProcStatInsertTrigger #Date, #CampaignId, #CampaignName, #AdvertiserId, #PublisherId, #Unique, #Approved, #PublisherEarning, #AdvertiserCost, #Type
FETCH NEXT FROM InsertCursor INTO #curId
END
CLOSE InsertCursor
DEALLOCATE InsertCursor
END
The stored procedure is rather big and intense and I do not think there's a way of having to avoid looping through the records of the Inserted table in one way or another (ok, maybe there is, but I'd like to be able to read the code too :p), so I'm not gonna bore you with that one (unless you like to think otherwise). So pretty much, is there a better way of doing this, and if so, how?
EDIT: Well after request, here's the sproc
CREATE PROCEDURE ProcStatInsertTrigger
#Date DATE,
#CampaignId BIGINT,
#CampaignName varchar(500),
#AdvertiserId BIGINT,
#PublisherId BIGINT,
#Unique BIT,
#Approved BIT,
#PublisherEarning money,
#AdvertiserCost money,
#Type smallint
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF #Approved = 1
BEGIN
DECLARE #test bit
SELECT #test = 1 FROM CachedStats WHERE [Date] = #Date AND CampaignId = #CampaignId AND CustomerId = #PublisherId
IF #test IS NULL
BEGIN
INSERT INTO CachedStats ([Date], CustomerId, CampaignId, CampaignName) VALUES (#Date, #PublisherId, #CampaignId, #CampaignName)
END
SELECT #test = NULL
DECLARE #Clicks int
DECLARE #TotalAdvertiserCost money
DECLARE #TotalPublisherEarning money
DECLARE #PublisherCPC money
DECLARE #AdvertiserCPC money
SELECT #Clicks = Clicks, #TotalAdvertiserCost = AdvertiserCost + #AdvertiserCost, #TotalPublisherEarning = PublisherEarning + #PublisherEarning FROM CachedStats
WHERE [Date] = #Date AND CustomerId = #PublisherId AND CampaignId = #CampaignId
IF #Type = 0 -- If click add one to the calculation
BEGIN
SELECT #Clicks = #Clicks + 1
END
IF #Clicks > 0
BEGIN
SELECT #PublisherCPC = #TotalPublisherEarning / #Clicks, #AdvertiserCPC = #TotalAdvertiserCost / #Clicks
END
ELSE
BEGIN
SELECT #PublisherCPC = 0, #AdvertiserCPC = 0
END
IF #Type = 0
BEGIN
UPDATE CachedStats SET
Clicks = #Clicks,
UniqueClicks = UniqueClicks + #Unique,
PublisherEarning = #TotalPublisherEarning,
AdvertiserCost = #TotalAdvertiserCost,
PublisherCPC = #PublisherCPC,
AdvertiserCPC = #AdvertiserCPC
WHERE [Date] = #Date AND CustomerId = #PublisherId AND CampaignId = #CampaignId
END
ELSE IF #Type = 1 OR #Type = 4 -- lead or coreg
BEGIN
UPDATE CachedStats SET
Leads = Leads + 1,
PublisherEarning = #TotalPublisherEarning,
AdvertiserCost = #TotalAdvertiserCost,
AdvertiserCPC = #AdvertiserCPC,
PublisherCPC = #AdvertiserCPC
WHERE [Date] = #Date AND CustomerId = #PublisherId AND CampaignId = #CampaignId
END
ELSE IF #Type = 3 -- Isale
BEGIN
UPDATE CachedStats SET
Leads = Leads + 1,
PublisherEarning = #TotalPublisherEarning,
AdvertiserCost = #TotalAdvertiserCost,
AdvertiserCPC = #AdvertiserCPC,
PublisherCPC = #AdvertiserCPC,
AdvertiserOrderValue = #AdvertiserCost,
PublisherOrderValue = #PublisherEarning
WHERE [Date] = #Date AND CustomerId = #PublisherId AND CampaignId = #CampaignId
END
ELSE IF #Type = 2 -- View
BEGIN
UPDATE CachedStats SET
[Views] = [Views] + 1,
UniqueViews = UniqueViews + #Unique,
PublisherEarning = #TotalPublisherEarning,
AdvertiserCost = #TotalAdvertiserCost,
PublisherCPC = #PublisherCPC,
AdvertiserCPC = #AdvertiserCPC
WHERE [Date] = #Date AND CustomerId = #PublisherId AND CampaignId = #CampaignId
END
END
END
After help, here's my final result, posted in case others have a similiar issue
CREATE TRIGGER [dbo].[TR_STAT_INSERT]
ON [iqdev].[dbo].[Stat]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
-- insert all missing "CachedStats" rows
INSERT INTO
CachedStats ([Date], AdvertId, CustomerId, CampaignId, CampaignName)
SELECT DISTINCT
CONVERT(Date, i.[Date]), i.AdvertId, i.[PublisherCustomerId], c.Id, c.Name
FROM
Inserted i
INNER JOIN Advert AS a ON a.Id = i.AdvertId
INNER JOIN Campaign AS c ON c.Id = a.CampaignId
WHERE
i.[Approved] = 1
AND NOT EXISTS (
SELECT 1
FROM CachedStats as t
WHERE
[Date] = CONVERT(Date, i.[Date])
AND CampaignId = c.Id
AND CustomerId = i.[PublisherCustomerId]
AND t.AdvertId = i.AdvertId
)
-- update all affected records at once
UPDATE
CachedStats
SET
Clicks =
Clicks + (
SELECT COUNT(*) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] = 0
),
UniqueClicks =
UniqueClicks + (
SELECT COUNT(*) FROM Inserted s
WHERE s.Approved = 1
AND s.[Unique] = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] = 0
),
[Views] =
[Views] + (
SELECT COUNT(*) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] = 2
),
UniqueViews =
UniqueViews + (
SELECT COUNT(*) FROM Inserted s
WHERE s.Approved = 1
AND s.[Unique] = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] = 2
),
Leads =
Leads + (
SELECT COUNT(*) FROM Inserted s
WHERE s.Approved = 1
AND s.[Unique] = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] IN (1,3,4)
),
PublisherEarning =
CachedStats.PublisherEarning + ISNULL((
SELECT SUM(PublisherEarning) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
), 0),
AdvertiserCost =
CachedStats.AdvertiserCost + ISNULL((
SELECT SUM(AdvertiserCost) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
), 0),
PublisherOrderValue =
PublisherOrderValue + ISNULL((
SELECT SUM(PublisherEarning) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] = 3
), 0),
AdvertiserOrderValue =
AdvertiserOrderValue + ISNULL((
SELECT SUM(AdvertiserCost) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] = 3
), 0),
PublisherCPC =
CASE WHEN (Clicks + (
SELECT COUNT(*) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] = 0
)) > 0 THEN
(CachedStats.PublisherEarning + ISNULL((
SELECT SUM(PublisherEarning) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
), 0)) -- COST ^
/ (
Clicks + (
SELECT COUNT(*) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] = 0
)
) --- Clicks ^
ELSE
0
END,
AdvertiserCPC =
CASE WHEN (Clicks + (
SELECT COUNT(*) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] = 0
)) > 0 THEN
(CachedStats.AdvertiserCost + ISNULL((
SELECT SUM(AdvertiserCost) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
), 0)) -- COST ^
/ (
Clicks + (
SELECT COUNT(*) FROM Inserted s
WHERE s.Approved = 1
AND s.PublisherCustomerId = i.PublisherCustomerId
AND CONVERT(Date, s.[Date]) = CONVERT(Date, i.[Date])
AND s.AdvertId = i.AdvertId
AND s.[Type] = 0
)
) --- Clicks ^
ELSE
0
END
FROM
Inserted i
WHERE
i.Approved = 1 AND
CachedStats.Advertid = i.AdvertId AND
CachedStats.[Date] = Convert(Date, i.[Date]) AND
CachedStats.CustomerId = i.PublisherCustomerId
SET NOCOUNT OFF
END
It looks slightly different now because I had to index it per advertisement too - but thanks alot for the help - sped everything up from 30hour+ to 30 sec to generate the CachedStats from my own development Stat table :)
The trick with these kinds of situations is to turn the sequential operation (for each record do xyz) into a set-based operation (an UPDATE statement).
I have analyzed your stored procedure and merged your separate UPDATE statements into a single one. This single statement can then be transformed into a version that can be applied to all inserted records at once, eliminating the need for a stored procedure and thereby the need for a cursor.
EDIT: Below is the code that we finally got working. Execution time for the whole operation went down from "virtually forever" (for the original solution) to something under one second, according to the OP's feedback. Overall code size also decreased quite noticeably.
CREATE TRIGGER [dbo].[TR_STAT_INSERT]
ON [iqdev].[dbo].[Stat]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
-- insert all missing "CachedStats" rows
INSERT INTO
CachedStats ([Date], AdvertId, CustomerId, CampaignId, CampaignName)
SELECT DISTINCT
CONVERT(Date, i.[Date]), i.AdvertId, i.PublisherCustomerId, c.Id, c.Name
FROM
Inserted i
INNER JOIN Advert a ON a.Id = i.AdvertId
INNER JOIN Campaign c ON c.Id = a.CampaignId
WHERE
i.Approved = 1
AND NOT EXISTS (
SELECT 1
FROM CachedStats
WHERE Advertid = i.AdvertId AND
CustomerId = i.PublisherCustomerId AND
[Date] = CONVERT(DATE, i.[Date])
)
-- update all affected records at once
UPDATE
CachedStats
SET
Clicks = Clicks + i.AddedClicks,
UniqueClicks = UniqueClicks + i.AddedUniqueClicks,
[Views] = [Views] + i.AddedViews,
UniqueViews = UniqueViews + i.AddedUniqueViews,
Leads = Leads + i.AddedLeads,
PublisherEarning = PublisherEarning + ISNULL(i.AddedPublisherEarning, 0),
AdvertiserCost = AdvertiserCost + ISNULL(i.AddedAdvertiserCost, 0),
PublisherOrderValue = PublisherOrderValue + ISNULL(i.AddedPublisherOrderValue, 0),
AdvertiserOrderValue = AdvertiserOrderValue + ISNULL(i.AddedAdvertiserOrderValue, 0)
FROM
(
SELECT
AdvertId,
CONVERT(DATE, [Date]) [Date],
PublisherCustomerId,
COUNT(*) NumRows,
SUM(CASE WHEN Type IN (0) THEN 1 ELSE 0 END) AddedClicks,
SUM(CASE WHEN Type IN (0) AND [Unique] = 1 THEN 1 ELSE 0 END) AddedUniqueClicks,
SUM(CASE WHEN Type IN (2) THEN 1 ELSE 0 END) AddedViews,
SUM(CASE WHEN Type IN (2) AND [Unique] = 1 THEN 1 ELSE 0 END) AddedUniqueViews,
SUM(CASE WHEN Type IN (1,3,4) AND [Unique] = 1 THEN 1 ELSE 0 END) AddedLeads,
SUM(PublisherEarning) AddedPublisherEarning,
SUM(AdvertiserCost) AddedAdvertiserCost,
SUM(CASE WHEN Type IN (3) THEN PublisherOrderValue ELSE 0 END) AddedPublisherOrderValue,
SUM(CASE WHEN Type IN (3) THEN AdvertiserOrderValue ELSE 0 END) AddedAdvertiserOrderValue
FROM
Inserted
WHERE
Approved = 1
GROUP BY
AdvertId,
CONVERT(DATE, [Date]),
PublisherCustomerId
) i
INNER JOIN CachedStats cs ON
cs.Advertid = i.AdvertId AND
cs.CustomerId = i.PublisherCustomerId AND
cs.[Date] = i.[Date]
SET NOCOUNT OFF
END
The operations involving the CachedStats table will greatly benefit from one multiple-column index over (Advertid, CustomerId, [Date]) (as confirmed by the OP).
Depending on what version of MSSQL you are running, you should also consider using Indexed Views for this as well. That could very well be a simpler approach than your triggers, depending on what the report query looks like. See here for more info.
Also, in your trigger, you should try to write your updates to the materialized results table as a set based operation, not a cursor. Writing a cursor based trigger could potentially just be moving your problem from the report query to your table inserts instead.
First thing I would do is use a FAST_FORWARD cursor instead. As you are only going from one record to the next and not doing any updates this will be much better for performance.
DECLARE CURSOR syntax
You can slightly optimize your cursor variation by doing FAST_FORWARD, READ_ONLY and LOCAL options on the cursor. Also, you're pulling the Id into your cursor, and then looping back to get the values. Either use CURRENT_OF or throw them all into variables. But, I wouldn't expect these changes to buy you much.
You really need to move to a set based approach. That stored proc is definitely doable in a set based model - though it may take 3 or 4 different update statements. But even 3 or 4 different triggers (1 for views, 1 for clicks, etc.) would be better than the cursor approach.
Your best bet is to move to a set based operation in the trigger. I'm not going write this for you 100% but let me get you started, and we can see where we go from there. Keep in mind I am writting this without tables / schemas and so I'm not going validate. Expect Typos:-)
Let's look at your update statements first, From what I can tell you are updating the same table with the same where clause the only difference is the columns. You can consolidate this to look like:
UPDATE CachedStats SET
/* Basically we are going to set the counts based on the type inline in the update clause*/
Leads= CASE WHEN (#Type = 1 OR #Type = 4 OR #Type=3 ) THEN Leads + 1 ELSE LEADS END,
Clicks=CASE WHEN (#Type=0) THEN Clicks+1 ELSE Clicks END,
Views=CASE WHEN (#Type=4) THEN Views+1 ELSE Views END,
PublisherEarning = #PublisherEarning + PublisherEarning,
AdvertiserCost = #AdvertiserCost +AdvertiserCost,
FROM CachedStats CS
INNER JOIN Inserted INS
ON CS.Date=Inserted.Date AND CS.CustomerId=Ins.PublisherId AND CS.CampaignId=Ins.CampaignId
I do aggree with you that this could get ugly but that's a decision you'll have to make.
As for your insert clause, I would handle that the same way you already are just insert into the table from the Inserted table whatever doesn't already exist.

Resources