The objective here is to update the table: BOOK_LOANS, column: #Status to a string (See code below) when I execute the Stored procedure. When the loan time goes past the Due_date adds 1 #Warning to the table READERS. When #Warnings = 3, SET #Eligibility to 'NOT ELIGIBLE'. How can I write this, this syntax is a mess.
IF SELECT DATEDIFF(day, #Due_date, CONVERT(date, getdate())) = 5
BEGIN
UPDATE BOOK_LOANS
SET #Status = 'DELAYED'
RETURN;
END
IF DATEDIFF(day, #Due_date, CONVERT(date, getdate())) = 3
BEGIN
UPDATE BOOK_LOANS
SET #Status = 'URGENT RETURN'
RETURN;
END
IF DATEDIFF(day, #Due_date, CONVERT(date, getdate())) = -1
BEGIN
UPDATE BOOK_LOANS
SET #Status = 'OUTDATED'
UPDATE READERS
SET #Warnings = #Warnings + 1
RETURN;
END
IF (#Warnings > 3)
BEGIN
UPDATE READERS
SET #Eligibility = 'NOT ELIGIBLE'
END
I think here is what you are trying to do :
UPDATE BOOK_LOANS
SET
status = CASE DATEDIFF(day, Due_date, CONVERT(date, getdate()))
WHEN 5 THEN 'DELAYED'
WHEN 3 THEN 'URGENT RETURN'
WHEN -1 THEN 'OUTDATED'
ELSE status
END
, warning = CASE WHEN DATEDIFF(day, Due_date, CONVERT(date, getdate())) = -1
THEN warning + 1
ELSE warning
END
GO
UPDATE r
SET Eligibility = CASE WHEN l.warning > 3
THEN 'NOT ELIGIBLE'
ELSE Eligibility
END
FROM READERS r
JOIN BOOK_LOANS l
on l.reader_id = l.reader_is -- here I assume you need to link two tables based on the PK, FKs
Thank you so much for the time. After some time learning a lit bit more I came to this final code. For some reason I couldn't apply the code above but nevertheless helped me to reach my final code.
UPDATE book_loans
SET loan_status = CASE
WHEN Datediff(day, CONVERT(DATE, Getdate()), due_date) =
5 THEN
'RETURN SOON'
WHEN Datediff(day, CONVERT(DATE, Getdate()), due_date) =
3 THEN
'URGENT RETURN'
WHEN Datediff(day, CONVERT(DATE, Getdate()), due_date) <=
-1
THEN 'OUTDATED'
ELSE loan_status
END;
-- Create temp table
SELECT r.card_no,
b.due_date,
Warnings = 0
INTO #temp3
FROM readers AS r
JOIN book_loans AS b
ON r.card_no = b.card_no
SELECT *
FROM #temp3
-- Calculate warnings
UPDATE #temp3
SET warnings = CASE
WHEN Datediff(day, CONVERT(DATE, Getdate()), due_date) <= -1
THEN
warnings + 1
ELSE warnings
END;
UPDATE readers
SET readers.warnings = readers.warnings + p.total
FROM (SELECT card_no,
Sum(warnings) AS total
FROM #temp3
GROUP BY card_no) p
WHERE readers.card_no = p.card_no
UPDATE readers
SET eligibility = CASE
WHEN warnings > 3 THEN 'NOT ELIGIBLE'
ELSE eligibility
END
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 want to find if query does not return any results, then print 'no records found' else execute the query.
Here is my query,
select case when exists (
SELECT CAST(dm.[ID] AS VARCHAR(100)) as ID, case when dm.[Que_Type] = 0 then 'Valid' else 'Invalid' end [Type],
dm.[Name_List], t.[Name], dm.[FromDate], dm.[ToDate] FROM tblDays dm(nolock)
inner join (select pr.ID, pr.name from tblProduct pr(nolock)) as t
on dm.TradeID = t.ID where 1=1 and dm.ToDate between GETDATE() and DATEADD(dd, 15, GETDATE()))
then 'ok'
else 'no records'
end
In this query, I want to execute the query instead of printing 'ok'. How can I do that?
You could use an if statement along with exists
Since the conditional check is just to see if any records are returned, there is no need to select all those columns, so we just select 1. If records are found, the if tests true, and we'll run the sql statement. If no records are found, we'll drop to the else block and print 'no records'.
This may work for you:
IF(
exists(
select 1
FROM tblDays dm(nolock)
inner join (select pr.ID, pr.name from tblProduct pr(nolock)) as t
on dm.TradeID = t.ID
where 1=1
and dm.ToDate between GETDATE() and DATEADD(dd, 15, GETDATE())
)
)
BEGIN
SELECT CAST(dm.[ID] AS VARCHAR(100)) as ID
, case when dm.[Que_Type] = 0 then 'Valid' else 'Invalid' end [Type]
, dm.[Name_List]
, t.[Name]
, dm.[FromDate]
, dm.[ToDate]
FROM tblDays dm(nolock)
inner join (select pr.ID, pr.name from tblProduct pr(nolock)) as t
on dm.TradeID = t.ID
where 1=1
and dm.ToDate between GETDATE() and DATEADD(dd, 15, GETDATE())
END
ELSE
BEGIN
print 'no records'
END
You could use ##ROWCOUNT:
According to BOL:
Returns the number of rows affected by the last statement.
SELECT
CAST(dm.[ID] AS VARCHAR(100)) AS ID,
CASE WHEN dm.[Que_Type] = 0 THEN 'Valid' ELSE 'Invalid' END AS [Type],
dm.[Name_List],
t.[Name],
dm.[FromDate],
dm.[ToDate]
FROM tblDays dm(NOLOCK)
INNER JOIN (
SELECT
pr.ID,
pr.name
FROM tblProduct pr(NOLOCK)
) AS t
ON dm.TradeID = t.ID
WHERE
1 = 1
AND dm.ToDate BETWEEN GETDATE() AND DATEADD(dd, 15, GETDATE())
IF ##ROWCOUNT = 0
PRINT 'No records found.'
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.