I have the following stored procedure and when I execute it by passing in the necessary parameters, I get an error:
Conversion failed when converting date and/or time from character string
I believe this is due in part to one of the columns I have set is still functioning as a nvarchar when I am trying to set it to equal a datetime parameter. I need help trying to figure out how to implement the CAST function into the specified line:
CONVERT(nvarchar(25), ISNULL(T.ActivityDate, T.CreatedDate), 101) AS 'Call Date'
Below are my parameters, as well as the entire SELECT query of my stored procedure:
--USE LOCAL VARIABLES FOR SPEED
DECLARE #LocInstitutionID INT, #LocLastMod as datetime, #LocProcessTime as datetime
SET #LocProcessTime = getutcdate();
SET #LocInstitutionID = #InstitutionID
SET #LocLastMod = #LastMod
--END USE LOCAL VARIABLES FOR SPEED
IF (#LocLastMod) IS NULL
BEGIN
SET #LocLastMod = '2000-01-01 01:01:01.001'
END
SELECT
#LocInstitutionID as 'institution_id',
#LocProcessTime as 'RunTime',
CO.Institution_ID__c AS Institution_Student_ID,
--I need to add CAST into the below line
CONVERT(nvarchar(25), ISNULL(T.ActivityDate, T.CreatedDate), 101) AS 'Call Date',
CAST(ISNULL(REPLACE(REPLACE(REPLACE(SUBSTRING(T.[Description], 1, 999), ',', ';'), CHAR(13), ''), CHAR(10), ''), '') AS NVARCHAR(1000)) AS 'Notes'
FROM
(SELECT *
FROM SFAPI_Task (NOLOCK)
WHERE ISNULL(IsDeleted, 0) = 0) T
INNER JOIN
(SELECT *
FROM SFAPI_Case (NOLOCK)
WHERE ISNULL(IsDeleted, 0) = 0) CA ON CA.Id = T.WhatId
INNER JOIN
(SELECT *
FROM SFAPI_Contact (NOLOCK)
WHERE ISNULL(IsDeleted, 0) = 0) CO ON CO.Id = CA.ContactId
INNER JOIN
(SELECT *
FROM SFAPI_Account (NOLOCK)
WHERE ISNULL(IsDeleted,0) = 0) A ON A.Id = T.Accountid
WHERE
T.ringdna__Call_Disposition__c IS NOT NULL
AND CO.Cohort__c >= 1000010
AND 'Call Date' >= #LOCLastMod
AND CO.Institution_ID__c = #LocInstitutionID
ORDER BY
CO.LRAP_ID__c, ISNULL(T.ActivityDate, T.CreatedDate) DESC
Thanks in advance!
I have this cross apply query and I want to sum the result of it
CROSS APPLY
(SELECT
SUM(CASE WHEN [day] BETWEEN #FirstDay AND #LastDay
THEN 1 ELSE 0
END) AS UsedDays
FROM
Calendar c
WHERE
([day] >= r.[DateFrom]
AND [day] <= r.[DateTo]
AND [WorkDay] = 1)) calculateUsedDays
I have a request table that contains different requests from different people
and my point is to sum all of the days from the requests from a person.
The cross apply returns the sum of the days by every request from the requests table for every person.
For example:
person
John, usedDays - 5
John, - 7
Peter - 10
Peter - 5
..
And I want to sum these days and group by name of the person so that I can have all of the days by person.
Example:
John - 12
Peter - 15
I tried with sum and group by , but it returns error:
Each GROUP BY expression must contain at least one column that is not an outer reference
Thank you :))
Thank you guys,
I solved this, but now my problem is: Implicit conversion from data type datetime to int is not allowed. Use the CONVERT function to run this query.
This is my code
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [core].[ReportBalanceYearSearch]
#Year int = NULL,
#TypeOfLeaveGid int = NULL,
#IsActive int = NULL
AS
BEGIN
SET NOCOUNT ON
DECLARE #Err int
DECLARE #sql nvarchar (max), #paramlist nvarchar(max)
DECLARE #FirstDay datetime
DECLARE #LastDay datetime
DECLARE #typeLeaveGid INT, #typeCreditGid INT, #relLeaveToCreditGid INT
SET #FirstDay = DATEFROMPARTS ( #Year, 1, 1)
SET #LastDay = DATEFROMPARTS ( #Year, 12, 31)
SELECT #typeLeaveGid = gid FROM Nomenclature WHERE type = 'RequestType' and Code = 1
SELECT #typeCreditGid = gid FROM Nomenclature WHERE type = 'RequestType' and Code = 2
SELECT #relLeaveToCreditGid = gid FROM Nomenclature WHERE type = 'Relation' and Code = 6
SELECT #sql = '
SELECT u.[Name],
u.[DepartmentGid],
sum(calculateUsedDays.UsedDays - isnull(calculateCreditDays.CreditDaysInPeriod,0)) as [UsedDays],
ub.[Balance],
sum(calculateUsedDays.UsedDays - isnull(calculateCreditDays.CreditDaysInPeriod,0)) + ub.[Balance] as [TotalDaysInYear],
r.[LeaveTypeGid]
FROM [dbo].[Request] r
inner join [User] u on r.[UserGid] = u.[Gid]
inner join [UserBalance] ub on r.[UserGid] = ub.UserGid and ub.Year = #xYear
LEFT OUTER JOIN dbo.Request CRD
inner join Relations rel ON rel.RelID2 = CRD.Gid AND rel.RelType = #xrelLeaveToCreditGid
inner join Nomenclature nsc ON nsc.Gid = CRD.StatusGid
cross apply (SELECT
sum(case when [day] between COALESCE(#xFirstDay, [day]) AND COALESCE(#xLastDay, [day]) then 1 else 0 end) as CreditDaysInPeriod
FROM Calendar c
WHERE [day] >= crd.[DateFrom] AND [day] <= crd.[DateTo] AND [WorkDay] = 1 ) calculateCreditDays
ON rel.RelID1 = r.Gid
and CRD.TypeGid = #xtypeCreditGid
cross apply (SELECT
sum(case when [day] between #xFirstDay and #xLastDay then 1 else 0 end) as UsedDays
FROM Calendar c
WHERE ([day] >= r.[DateFrom] AND [day] <= r.[DateTo] AND [WorkDay] = 1))calculateUsedDays
where #xYear = DATEPART(year,r.[DateFrom]) and r.TypeGid = #xtypeLeaveGid and #xIsActive IS NULL OR u.[Active] = #xIsActive
group by u.[Name], u.[DepartmentGid],r.[LeaveTypeGid], ub.[Balance]'
SELECT #paramlist ='
#xTypeOfLeaveGid int,
#xFirstDay datetime,
#xYear int,
#xLastDay datetime,
#xtypeLeaveGid int,
#xrelLeaveToCreditGid int,
#xtypeCreditGid int,
#xIsActive bit'
EXEC sys.sp_executesql #sql, #paramlist,
#TypeOfLeaveGid,
#Year,
#IsActive,
#typeLeaveGid,
#relLeaveToCreditGid,
#typeCreditGid,
#FirstDay,
#LastDay
SET #Err = ##Error
RETURN #Err
END
I am writing a query to calculate running totals of a few things over time. The time increments are in weekly buckets (every monday), however as time goes on, nothing needs to be in the past. This means my weekly buckets will be floating and always staying in the future. I have seen a couple links for how to setup a PIVOT function, however all of those examples the columns are fixed values. How do I get my query results to be displayed as my column names?
Here is my code which outputs what I need my column names to be. I set the increment to "1" to get just 4 results for testing, but will probably open it up to 12 months.
DECLARE #Startdate as date
DECLARE #Enddate as date
SET #Startdate = getdate()
SET #Enddate = DATEADD(Month,1,#StartDate)
;WITH cte(myDate) AS ( SELECT CASE WHEN DATEPART(Day,#StartDate) = 1 THEN #StartDate
ELSE DATEADD(Week,DATEDIFF(Week,0,#StartDate)+1,0) END AS myDate
UNION ALL
SELECT DATEADD(Week,1,myDate)
FROM cte
WHERE DATEADD(Week,1,myDate) <= #EndDate )
SELECT CONVERT(date,myDate) AS BuildWeek
FROM cte
OPTION (MAXRECURSION 0)
You will have to go dynamic.
You are missing some vital details. So I hope this will help with some guidance.
Below is a modified version of an answer from earlier today.
Declare #Startdate as date,#Enddate as date
Set #Startdate = getdate()
Set #Enddate = DATEADD(Month,1,#StartDate)
Declare #SQL varchar(max) = ''
Declare #Col varchar(max) = '
,[Title] = sum(case when Date between ''[DateR1]'' and ''[DateR2]'' then [YourField] else null end)'
;with cte(myDate) as (
Select Case When DatePart(Day,#StartDate) = 1 Then #StartDate Else DateAdd(Week,DateDiff(Week,0,#StartDate)+1,0) end as myDate
Union All
Select DateAdd(Week,1,myDate)
From cte
Where DateAdd(Week,1,myDate) <= #EndDate
)
Select #SQL = #SQL + Replace(Replace(Replace(#Col,'[DateR1]',DateR1),'[DateR2]',DateR2),'Title',Title)
From (Select DateR1 = cast(myDate as Date)
,DateR2 = DateAdd(DAY,6,cast(myDate as Date))
,Title = 'Week Of '+Substring(DateName(WEEKDAY,myDate),1,3)+' '+Substring(DateName(MONTH,myDate),1,3)+' '+cast(Day(myDate) as varchar(5))
From cte
) A
Set #SQL = 'Select ID'+#SQL+'
From YourTable
Group By ID
'
Print #SQL
--Exec(#SQL)
Returns the following SQL which would be executed
Select ID
,[Week Of Mon Sep 26] = sum(case when Date between '2016-09-26' and '2016-10-02' then [YourField] else null end)
,[Week Of Mon Oct 3] = sum(case when Date between '2016-10-03' and '2016-10-09' then [YourField] else null end)
,[Week Of Mon Oct 10] = sum(case when Date between '2016-10-10' and '2016-10-16' then [YourField] else null end)
,[Week Of Mon Oct 17] = sum(case when Date between '2016-10-17' and '2016-10-23' then [YourField] else null end)
From YourTable
Group By ID
What I am doing is using own function, which is returning string, now I want this column for ordering, and while using it for ordering it is not ordering properly because its a string, when I tried to convert as datetime it causes an error.
Any help ?
Thanks a lot.
SELECT
b.CallId,
CONVERT(VARCHAR(25), b.ETADate, 103) as'ETADate',
dbo.getCallEntrySerialNoLastTranDateTime(b.CallEntrySerialNumbersId) AS 'closingDateTime'
FROM
CallEntry AS a, CallEntrySerialNumbers AS b
WHERE
a.ASPId = 2
AND a.CompanyId = 3
AND a.CallId= b.CallId
AND a.ProdCompanyId = 1
AND b.CallCaseId IS NOT NULL
AND b.CallCaseId NOT LIKE 'NA'
AND ProdCategoryid = 1
AND a.CallDateTime > dateadd(day, -30, getdate())
AND b.StatusId = 2
ORDER BY
dbo.getCallEntrySerialNoLastTranDateTime(b.CallEntrySerialNumbersId) ASC
// what I tried and causes above error is
ORDER BY
CONVERT(DATE, dbo.getCallEntrySerialNoLastTranDateTime(b.CallEntrySerialNumbersId)) asc
EDIT-added function
ALTER FUNCTION [dbo].[ASP_getCallEntrySerialNoLastTranDateTime]
(
#CallEntrySerialNumbersId bigint
)
RETURNS varchar(500)
AS
BEGIN
DECLARE #ReturnVal NVARCHAR(500);
begin
select #ReturnVal = ( select Top 1 CONCAT(CONVERT(VARCHAR(10),CallTranEndDateTime,103),' ',CONVERT(VARCHAR(10),CallTranEndDateTime,108)) from calltransactions
where CallEntrySerialNumbersId = #CallEntrySerialNumbersId AND CallTranTypeId = 3
order by CallTranId desc)
end;
RETURN #ReturnVal;
END
Try this one -
SET DATEFORMAT dmy
SELECT *
FROM (
SELECT
b.CallId
, ETADate = CONVERT(VARCHAR(25), b.ETADate, 103)
, closingDateTime = dbo.getCallEntrySerialNoLastTranDateTime(b.CallEntrySerialNumbersId)
FROM dbo.CallEntry a
JOIN dbo.CallEntrySerialNumbers b ON a.CallId = b.CallId
WHERE a.ASPId = 2
AND a.CompanyId = 3
AND a.ProdCompanyId = 1
AND ISNULL(b.CallCaseId, '') NOT LIKE 'NA'
AND ProdCategoryid = 1
AND a.CallDateTime > DATEADD(day, -30, GETDATE())
AND b.StatusId = 2
) d
ORDER BY CAST(d.closingDateTime AS DATE)
ALTER FUNCTION [dbo].[ASP_getCallEntrySerialNoLastTranDateTime]
(
#CallEntrySerialNumbersId BIGINT
)
RETURNS VARCHAR(30)
AS BEGIN
DECLARE #ReturnVal VARCHAR(30)
SELECT #ReturnVal = (
SELECT TOP 1 CONVERT(VARCHAR(10), CallTranEndDateTime, 103) + ' ' + CONVERT(VARCHAR(10), CallTranEndDateTime, 108)
FROM dbo.CallTransactions
WHERE CallEntrySerialNumbersId = #CallEntrySerialNumbersId
AND CallTranTypeId = 3
ORDER BY CallTranId DESC
)
RETURN #ReturnVal
END
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.