How to use STRING_SPLIT() in a update query? - sql-server

I want to update a table, One of its fields is a comma-separated value, I want to update that table, I tried different code but not working
UPDATE [PATS].[ReportSubscription]
SET [ScheduleID] = #ScheduleID
,[ReferenceID] = #ReferenceID
,[ReferenceType] = #ReferenceType
,[Schedule] = #Schedule
,[Day] = #Day
,[Time] = #Time
,[ProjectID] = #ProjectID
,[LastSentDate] = #LastSentDate
,[UserID] = #UserID
--,[CreatedBy] = #CreatedBy
--,[CreatedDate] = #CreatedDate
,[UpdatedBy] = #UpdatedBy
,[UpdatedDate] = #UpdatedDate
WHERE ID=#ID AND #Day IN (SELECT * FROM STRING_SPLIT(#Day,','))
I think this (SELECT * FROM STRING_SPLIT(#Day,',')) should be joined with the code.

You should be selecting value from the table returned by STRING_SPLIT:
WHERE ID=#ID AND [Day] IN (SELECT value FROM STRING_SPLIT(#Day,','))
Also, you should be comparing the table's column [Day] against the CSV list, not the variable #Day.
But, it is usually not a good idea to mix CSV data with SQL databases. Consider storing your CSV list of days in a separate table if possible.

Its possible that the data in the variable has space.
You should be selecting value from the table returned by STRING_SPLIT and triming value for use in condition, for example:
DECLARE #day NVARCHAR(500) = N'12 , 25, 41,54 ,89'
IF '41' IN (SELECT RTRIM(LTRIM(value)) FROM STRING_SPLIT(#Day,','))
PRINT('Ok')
ELSE
PRINT('Not Found')
-- printed : 'Ok'

Your query would be:
UPDATE [PATS].[ReportSubscription]
SET [ScheduleID] = #ScheduleID
,[ReferenceID] = #ReferenceID
,[ReferenceType] = #ReferenceType
,[Schedule] = #Schedule
,[Day] = #Day
,[Time] = #Time
,[ProjectID] = #ProjectID
,[LastSentDate] = #LastSentDate
,[UserID] = #UserID
,[CreatedBy] = #CreatedBy
,[CreatedDate] = #CreatedDate
,[UpdatedBy] = #UpdatedBy
,[UpdatedDate] = #UpdatedDate
WHERE ID=#ID AND Day IN (SELECT value FROM STRING_SPLIT(#Day,','))
Visit https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-2017 for more info.
Note: Make sure to trim your selection for unwanted spaces accordingly!

Related

SQL Trigger doesn't execute triggers procedure

In SQL management studio I want to add trigger to one table. When data is inserted into table I want to insert or update data in other tables if some requirements are met.
ALTER trigger [dbo].[game_data_received]
on [dbo].[GameRounds]
for insert
as
declare #LastDate datetime
declare #CurrDate datetime
declare #GameID int
declare #TimeDiff int
declare #CurrMult int
declare #MaxMult int
declare #MaxDiff int
select #GameID = i.GameID from inserted i
select #CurrDate = i.[TimeStamp] from inserted i
select #CurrMult = i.MaxMultiplier from inserted i
select #LastDate = max([TimeStamp]) from GameRounds where GameID = #GameID
select #TimeDiff = DATEDIFF(SECOND, #LastDate, #CurrDate)
select #MaxDiff = (select TimeDifference from AcceptableTimeDiffGameRounds where GameID = #GameID)
select #MaxMult = (select MaxMultiplier from AcceptableTimeDiffGameRounds where GameID = #GameID)
if #TimeDiff > #MaxDiff
if #CurrMult >= #MaxMult
update AcceptableTimeDiffGameRounds
set MaxMultiplier = #CurrMult, TimeDifference = #TimeDiff
where GameID = #GameID
else
insert into Defects(DownFrom, DownUntill)
values (#LastDate, #CurrDate)
So this is my trigger query. Data in the GameRounds table are inserted automaticaly, but it doesn't executes this whole procedure. I can tell this because, when I select the tables where data should be added or change they stay same. Thanks in advance.
SQL Server is a SET-based language and the best practice is to handle it as such.
A commom misconception with triggers is that they're only responsible for 1 row, however, an insert trigger could have 1 or thousands of rows. The trigger you shared only handles a single inserted record (last in)--not all of them.
While I would not recommend handling your request this way--it really should be written to handle the entire inserted rowset at once--I have had to process an insert row-by-agonizing-row (RBAR) in the past. In this case, you could try rewriting your trigger like the following:
DECLARE #GameID int, #CurrDate datetime, #CurrMult int, #row_id int = 1;
DECLARE #inserted_rows table (
GameID int, CurrDate datetime, CurrMult int, row_id int IDENTITY ( 1, 1 )
);
INSERT INTO #inserted_rows (
GameID, CurrDate, CurrMult
)
SELECT
GameID, [TimeStamp], MaxMultiplier
FROM inserted;
WHILE #row_id <= ( SELECT MAX ( row_id ) FROM #inserted_rows )
BEGIN
-- Current row.
SELECT
#GameID = GameID, #CurrDate = CurrDate, #CurrMult = CurrMult
FROM #inserted_rows WHERE row_id = #row_id;
-- Process row.
DECLARE #MaxMult int, #MaxDiff int;
DECLARE #LastDate datetime = ( SELECT MAX( [TimeStamp] ) FROM GameRounds WHERE GameID = #GameID );
DECLARE #TimeDiff int = DATEDIFF( second, #LastDate, #CurrDate);
SELECT
#MaxMult = MaxMultiplier,
#MaxDiff = TimeDifference
FROM AcceptableTimeDiffGameRounds
WHERE
GameID = #GameID;
IF #TimeDiff > #MaxDiff
BEGIN
IF #CurrMult >= #MaxMult
UPDATE AcceptableTimeDiffGameRounds
SET
MaxMultiplier = #CurrMult,
TimeDifference = #TimeDiff
WHERE GameID = #GameID;
END
ELSE
BEGIN
INSERT INTO Defects ( DownFrom, DownUntill )
VALUES ( #LastDate, #CurrDate );
END
-- Next row.
SET #row_id = #row_id + 1;
END
This example captures all of the inserted rows and then processes them one at a time (without using a cursor)--which can be painfully slow, not to mention locking issues.
Ideally, you would rewrite this using a set-based approach.

Insert multiple lines with a stored procedure

With this stored procedure (SQL Server):
ALTER PROCEDURE [dbo].[fill_table1]
#CreatedBy nvarchar(max) = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT
[IdExchangeRate] = NEWID(),
[ExchangeRateCode] = 'EUR',
[ExchangeRatePeriodStartDate] = period,
[ExchangeRatePeriodEndDate] = EOMonth(period),
[ExchangeRateValue] = B.Value,
[CurrencyCode] = A.[Currency Code],
[CreatedBy] = 'string',
[CreatedAt] = GETUTCDATE()
FROM
[TempExchangeRates] AS A
CROSS APPLY
(SELECT
period = TRY_CONVERT(date),
Value = TRY_CONVERT(float, value)
......) B
END
I want to insert this output (+100rows) into another table (table2). I tried but it inserts just one row.
Thanks very much for #Sean Lange comment. It helped me so much.
I add that before select
INSERT INTO [dbo].[table2]
([column1]..
,[columnsn]
)

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

varchar entry of 5118 causing SQL Server stored procedure to fail

and thanks in advance
I have a very basic stored procedure that inserts a row into a table. It has been working flawlessly until today
Here is the script
(
#emp varchar(16),
#logdate date,
#logtime time,
#term char(20),
#SSID char(16)
)
AS
INSERT INTO AccessLog (EmployeeID, LogDate, LogTime, TerminalID, InOut, ChangedBy)
VALUES (#emp, #logdate, #logtime, #term, 3, #SSID)
When the string of 5118 is passed to it the insert will fail. There are several triggers that fire after this insert finishes.
Here's the strange part. You can pass it anyother number for the #emp variable and it works just fine, but pass it 5118, it fails.
The error I receive is below:
Msg 241, Level 16, State 1, Procedure UpdateTimeWorked, Line 27
Conversion failed when converting date and/or time from character
string.
Here is the procedure that is failing – the highlighted line is Line 27
TRIGGER [dbo].[UpdateTimeWorked] ON [dbo].[TimeLog]
FOR UPDATE
AS
SET NOCOUNT ON
DECLARE #ID int;
DECLARE #RCDIDIN int;
DECLARE #RCDIDOUT int;
DECLARE #ComboIn datetime;
DECLARE #ComboOut datetime;
SELECT #ID = ID FROM INSERTED;
SELECT #ComboIn = LoginCombo FROM INSERTED;
SELECT #ComboOut = LogoutCombo FROM INSERTED;
SELECT #RCDIDIN = RCDIDIN FROM INSERTED;
SELECT #RCDIDOUT = RCDIDOUT FROM INSERTED;
**IF ( UPDATE(LogoutCombo))**
BEGIN
IF (#RCDIDOUT != 0)
BEGIN
UPDATE TimeLog
SET LogOutRND = (select CAST(dbo.roundtime(LogOutRND,0.25) AS TIME))
WHERE ID = #ID
UPDATE TimeLog
SET LogOutComboRND = CAST(CAST(LogOutDate AS DATE) AS SMALLDATETIME) + CAST(LogOutRND AS TIME)
WHERE ID = #ID
UPDATE TimeLog
SET TimeWorked = dbo.gettime(DATEDIFF(ss,LogInComboRND,LogoutComboRND))
WHERE ID = #ID AND LogInEntered = 1 AND LogOutEntered = 1
UPDATE TimeLog
SET TimeWorked = (select CAST(dbo.roundtime(TimeWorked,0.25) AS TIME)), Rounded = 1
WHERE ID = #ID AND LogInEntered = 1 AND LogOutEntered = 1
END
END
IF ( UPDATE(LoginCombo))
BEGIN
IF (#RCDIDIN != 0)
BEGIN
UPDATE TimeLog
SET LogInRND = (select CAST(dbo.roundtime(LogInRND,0.25) AS TIME))
WHERE ID = #ID
UPDATE TimeLog
SET LogInComboRND = CAST(CAST(LogInDate AS DATE) AS SMALLDATETIME) + CAST(LogInRND AS TIME)
WHERE ID = #ID
UPDATE TimeLog
SET TimeWorked = dbo.gettime(DATEDIFF(ss,LogInComboRND,LogoutComboRND))
WHERE ID = #ID AND LogInEntered = 1 AND LogOutEntered = 1
UPDATE TimeLog
SET TimeWorked = (select CAST(dbo.roundtime(TimeWorked,0.25) AS TIME)), Rounded = 1
WHERE ID = #ID AND LogInEntered = 1 AND LogOutEntered = 1
END
END
GO
I am at a total blank to come up with why this is not working.
Anyone have any ideas?
Like I stated, pass it any other entry for the #emp and it will run fine. I can even pass it ‘5118’ and it will work, but not 5118.

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