How to replace a SQL While Loop with a Select - sql-server

We have a procedure that uses the below While..Loop to calculate the total number of Days and Weeks paid for absences in our PayRoll System:
EDITED
Declare #Absences Table (slno int identity (1,1),AbsenceId int, ToDate datetime)
INSERT INTO #Absences (AbsenceID,ToDate)
Select AbsenceID, AB.ToDate
from t_Absence AB with (nolock)
Inner Join t_AbsenceCategory AB_CAT with (nolock) ON (AB.AbsenceCategoryID = AB_CAT.AbsenceCategoryID)
where (AB_CAT.IsSSP =1)
and ClientID = #ClientID
and AB.FromDate >= #SSPYearStart --D7830 SJH 21/10/2015
order BY AB.ToDate desc
Declare #AbsenceID INT, #iCtr INT, #maxRows int
Declare #FromDate datetime
SELECT #iCtr = 1, #maxRows = MAX(slno) FROM #Absences
Select #SSPDaysPaid = 0, #SSPweeksPaid = 0, #QualifyingDaysInWeek = 0
If IsNull(#maxRows,0) > 0 select #FromDate = FromDate from t_Absence where AbsenceID = (SELECT AbsenceID FROM #Absences WHERE slno = 1)
WHILE ( #ictr <= #maxRows )
BEGIN
SELECT #AbsenceID = AbsenceID
FROM #Absences
WHERE slno = #iCtr
--Print #AbsenceID
If Exists (Select TOP 1 1 from t_Absence where ToDate > DATEADD(dd,-56, #FromDate))
BEGIN
SELECT #SSPDaysPaid = #SSPDaysPaid + IsNull(A.SSPDays,0),
#FromDate = A.FromDate
from t_Absence A
where A.AbsenceID = #AbsenceID
print '#SSPDaysPaid=' + CAST(#SSPDaysPaid AS Varchar(3)) + ' in Absence ' + cast(#AbsenceID as varchar(6))
DECLARE #Monday int, #Tuesday int, #Wednesday int, #Thursday int, #Friday int, #Saturday int, #Sunday int
SELECT #Monday = QD.Monday, #Tuesday = QD.Tuesday, #Wednesday =QD.Wednesday, #Thursday =QD.Thursday,
#Friday = QD.Friday, #Saturday = QD.Saturday, #Sunday = QD.Sunday
from t_PayrollEmployeeSSPQualifyingDays QD
inner JOIN t_Absence A on A.ClientID = QD.ClientID and A.FromDate = QD.DateFrom AND A.ToDate = QD.DateTo
where A.ClientID = #ClientId
SET #QualifyingDaysInWeek = #Monday + #Tuesday + #Wednesday + #Thursday + #Friday + #Saturday + #Sunday
print '#QualifyingDaysInWeek = ' + cast(#QualifyingDaysInWeek as char(2))
END
SET #iCtr = #iCtr + 1
END
if #QualifyingDaysInWeek <> 0 Set #SSPWeeksPaid = #SSPDaysPaid/#QualifyingDaysInWeek Else Set #SSPWeeksPaid = 0
print '#SSPWeeksPaid=' + cast(#SSPWeeksPaid as varchar(2))
Select
BradfordFactor
, CSPFDEntitlement
, CSPHDEntitlement
, CSPDaysTaken
, HasContract
, CSPFullDaysTaken
, CSPHalfDaysTaken -- DevTask 112703 06/11/2012 SWB Start
, IsNull(#SSPDaysPaid,0) as 'SSPDaysPaid'
, IsNull(#SSPWeeksPaid,0) as 'SSPWeeksPaid'
from
fn_GetEmployeeBradfordFactor(#ClientID,DEFAULT,0, DEFAULT)
END
As I need to find out this information for several different persons I will have to execute this stored proc and loop once for each client Id (#ClientId) identified in the calling procedure ...
Is there an alternative to this loop and would it be worth it in terms of performance?

Based on the OP comment, no other value is needed from the loop, but #QualifyingDaysInWeek
#RicardoC : Well I don't need the prints (they're there for debugging and analysis) but I do need to sum the values #QualifyingDaysInWeek in for each record in the table #Absences
There appears to be no need for the loop at all.
Declare #Absences Table (slno int identity (1,1),AbsenceId int, ToDate datetime)
INSERT INTO #Absences (AbsenceID,ToDate)
Select AbsenceID, AB.ToDate
from t_Absence AB with (nolock)
Inner Join t_AbsenceCategory AB_CAT with (nolock) ON (AB.AbsenceCategoryID = AB_CAT.AbsenceCategoryID)
where (AB_CAT.IsSSP =1)
and ClientID = #ClientID
and AB.FromDate >= #SSPYearStart --D7830 SJH 21/10/2015
order BY AB.ToDate desc
DECLARE #QualifyingDaysInWeek INT
SELECT #QualifyingDaysInWeek = SUM(QD.Monday + QD.Tuesday + QD.Wednesday + QD.Thursday + QD.Friday + QD.Saturday + QD.Sunday)
FROM t_PayrollEmployeeSSPQualifyingDays QD
INNER JOIN t_Absence A ON A.ClientID = QD.ClientID
AND A.FromDate = QD.DateFrom
AND A.ToDate = QD.DateTo
WHERE A.ClientID = #ClientId;

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

Use cross apply function

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

How to optimise this stored procedure?

I have the stored procedure below and I am having two issues with it
It is running very slowly, and
It is returning a blank result set
The idea for the stored procedure is to do a sequence check to find if any account numbers have not been assigned.
Step 1 loops through all the branches and builds up the tempdetails table.
After that it creates the list of all the numbers that have been used and uses that to delete everything that exists to leave a list of those accountnumbers that do not exist,
But een though I know there are missing account numbers, aside from the exceptionally long tun time it is returning a blank result set.
Anyone have any ideas what is going wrong with it?
Thanks
ALTER PROCEDURE [dbo].[PracticeFindMissingSequenceDetail]
#pracId VARCHAR(128),
#Prefix VARCHAR(256)
AS
BEGIN
DECLARE #TempDetails TABLE (SequenceCheck VARCHAR(24),
Prefix VARCHAR(4),
BranchName VARCHAR(256),
RisStatus VARCHAR(256),
Rislink VARCHAR(256)
);
DECLARE #Branchlist TABLE (BranchId INTEGER,
BranchName VARCHAR(256),
BranchPrefix VARCHAR(4),
PrefixLength INT,
SequenceLength INT
);
DECLARE #TempPatNo TABLE (Patno VARCHAR(24));
DECLARE #BranchName VARCHAR(256),
#BranchPrefix VARCHAR(256),
#PrefixLength INT,
#BranchId INT,
#SequenceLength INT,
#rangestart INTEGER,
#rangeend INTEGER,
#rangenow INTEGER,
#startDate DATETIME,
#Patno VARCHAR(128),
#FormatZeroes VARCHAR(3),
#CurrentLength INT,
#RangeString VARCHAR(256);
INSERT INTO #Branchlist (BranchId, BranchName, BranchPrefix, PrefixLength, SequenceLength)
SELECT
b.id, b.name, b.prefix, PrefixLength, SequenceLength
FROM
Branch b
INNER JOIN
Practice pr ON pr.id = b.practiceid
INNER JOIN
[Sequence] s ON s.id = b.id
WHERE
pr.APIKey = #pracID
AND b.inactive = 0
AND b.prefix = #Prefix
/* insert values for each branch into table*/
DECLARE BranchPointer CURSOR FOR
SELECT BranchID FROM #Branchlist
OPEN BranchPointer
FETCH NEXT FROM BranchPointer INTO #BranchId
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #BranchPrefix = (SELECT BranchPrefix
FROM #Branchlist
WHERE BranchId = #BranchId)
SELECT #PrefixLength = (SELECT PrefixLength
FROM #Branchlist
WHERE BranchId = #BranchId)
SELECT #SequenceLength = (SELEct SequenceLength
FROM #Branchlist
WHERE BranchId = #BranchId)
/* Set the starting date from the sequence */
SELECT #startDate = (SELECT MIN(MinimumSequenceDate)
FROM [Sequence] s
WHERE s.id = #BranchId)
/*get the earliest number in the sequence from the startdate*/
SELECT #rangestart = (SELECT MIN(SUBSTRING(v.bookingnumber, 3, LEN(bookingnumber)))
FROM Visit v
INNER join Branch b ON b.id = v.branchid
INNER join Practice pr ON pr.id = b.practiceid
WHERE pr.APIKey = #pracId
AND LEFT(v.bookingnumber, 2) = #Prefix
AND v.date >= #startDate
AND v.branchid = #BranchId);
/*get the latest number in the sequence from the startdate*/
SELECT #rangeend = (SELECT MAX(SUBSTRING(v.bookingnumber, 3, LEN(bookingnumber)))
FROM Visit v
INNER JOIN Branch b ON b.id = v.branchid
INNER JOIN Practice pr ON pr.id = b.practiceid
WHERE pr.APIKey = #pracId
AND LEFT(v.bookingnumber, 2) = #Prefix
AND v.date >= #startDate
AND v.branchid = #BranchId);
SET #RangeNow = #rangestart
WHILE #rangenow < #rangeend
BEGIN
/*check if leading zeroes are needed in the number and add them if needed*/
SET #RangeString = CAST(#RangeNow AS VARCHAR(256))
SET #CurrentLength = LEN(#rangenow)
IF #prefixlength + #currentlength < #SequenceLength
WHILE #CurrentLength + #PrefixLength < #SequenceLength
BEGIN
SET #RangeString = '0' + #RangeString;
SET #currentlength = LEN(#RangeString);
END;
/*Insert full sequence into temporary table*/
INSERT INTO #TempDetails (SequenceCheck, Prefix, BranchName)
SELECT #Prefix + #RangeString, #Prefix, #BranchName
SET #rangenow =#rangenow+1
END;
FETCH NEXT FROM BranchPointer INTO #BranchName
END
CLOSE BranchPointer
DEALLOCATE BranchPointer
/*delete existing sequence numbers from table*/
INSERT INTO #TempPatNo (PatNo)
SELECT BookingNumber
FROM Visit v1
INNER JOIN Branch b1 ON b1.id = v1.branchid
INNER JOIN Practice pr1 ON pr1.id = b1.practiceid
WHERE pr1.APIKey = #pracId
DELETE #TempDetails
WHERE sequencecheck IN (SELECT patNo FROM #TempPatNo)
/*Insert the status and link for error messages*/
UPDATE #tempDetails
SET RisStatus = (SELECT Status
FROM RISErrors r
INNER JOIN Practice pr ON pr.id = r.PracticeId
WHERE pr.APIKey = #pracId
AND VisitNumber = SequenceCheck
AND r.id = (SELECT MAX(r1.id)
FROM RISErrors r1
INNER JOIN Practice pr1 ON pr1.id = r1.PracticeId
WHERE pr1.APIKey = #pracId
AND VisitNumber = SequenceCheck)),
RisLink = 'http://billing.cryanic.co.za/Clinton/RISErrors?searchquery=' + SequenceCheck
/*return missing numbers into sequence control callong procedure*/
SELECT DISTINCT SequenceCheck, RisStatus, Rislink
FROM #TempDetails
END
After reading your procedure, I made some assumptions:
there is a one-to-one relationsship between sequence and branch
the visits are the details of a sequence
the BranchName and BranchPrefix aren't needed.
I recommend to not do lookups by BranchID but instead retrieve the values from the cursor query. The table BranchList isn't needed, you can base the cursor on the query directly. Also, the TempPatNo table can be avoided.
Here's what I have come up with:
ALTER PROCEDURE [dbo].[PracticeFindMissingSequenceDetail]
#pracId VARCHAR(128),
#Prefix VARCHAR(256)
AS
BEGIN
DECLARE #TempDetails TABLE (
BranchID INT,
SequenceCheck VARCHAR(24),
RisStatus VARCHAR(256),
Rislink VARCHAR(256)
);
DECLARE
#PrefixLength INT,
#BranchId INT,
#SequenceLength INT,
#rangestart INTEGER,
#rangeend INTEGER,
#rangenow INTEGER,
#startDate DATETIME;
/* insert values for each branch into table*/
DECLARE BranchPointer CURSOR FOR
SELECT b.id, PrefixLength, SequenceLength, s.MinimumSequenceDate
FROM Branch b
INNER JOIN Practice pr ON pr.id = b.practiceid
INNER JOIN [Sequence] s ON s.id = b.id
WHERE pr.APIKey = #pracID
AND b.prefix = #Prefix
AND b.inactive = 0
OPEN BranchPointer
FETCH NEXT FROM BranchPointer INTO #BranchId, #PrefixLength, #SequenceLength, #startDate
WHILE ##FETCH_STATUS = 0
BEGIN
/*get the earliest and latest number in the sequence from the startdate*/
SELECT
#rangestart = MIN(SUBSTRING(v.bookingnumber, 3, LEN(v.bookingnumber))),
#rangeend = MAX(SUBSTRING(v.bookingnumber, 3, LEN(v.bookingnumber)))
FROM Visit v
WHERE v.branchid = #BranchId
AND v.date >= #startDate
AND LEFT(v.bookingnumber, 2) = #Prefix;
SET #RangeNow = #rangestart
WHILE #rangenow < #rangeend
BEGIN
/*Insert full sequence into temporary table*/
INSERT INTO #TempDetails (BranchID, SequenceCheck)
SELECT #BranchId,
#Prefix + REPLICATE('0', #SequenceLength-#PrefixLength-LEN(#rangenow)) + CAST(#RangeNow AS VARCHAR(256));
SET #rangenow =#rangenow+1
END;
FETCH NEXT FROM BranchPointer INTO #BranchId, #PrefixLength, #SequenceLength, #startDate
END
CLOSE BranchPointer
DEALLOCATE BranchPointer
/*delete existing sequence numbers from table*/
DELETE FROM #TempDetails
FROM #TempDetails t
INNER JOIN Visit v ON t.BranchID = v.branchid
WHERE t.SequenceCheck = v.BookingNumber
/*Insert the status and link for error messages*/
UPDATE #tempDetails
SET RisStatus = (SELECT Status
FROM RISErrors r
INNER JOIN Practice pr ON pr.id = r.PracticeId
WHERE pr.APIKey = #pracId
AND VisitNumber = SequenceCheck
AND r.id = (SELECT MAX(r1.id)
FROM RISErrors r1
INNER JOIN Practice pr1 ON pr1.id = r1.PracticeId
WHERE pr1.APIKey = #pracId
AND VisitNumber = SequenceCheck)),
RisLink = 'http://billing.cryanic.co.za/Clinton/RISErrors?searchquery=' + SequenceCheck
/*return missing numbers into sequence control callong procedure*/
SELECT DISTINCT SequenceCheck, RisStatus, Rislink
FROM #TempDetails
END

Select multiple columns but group by only one

Using SQL Server 2008. I have been researching this problem for days. Thought about CTE and trying to use a predicate on the problem tables.
Nothing I have tried or researched has worked so here I am. The problem is it's returning duplicate OrderID.
I've marked the problem joins.
Even tried OUTER APPLY but it caused certain searches not to work. Tried INNER APPLY then duplicates again.
The problem joins, the tables, have multiple references to OrderID.
So tblRun has multiple rows with the same OrderID showing which run it was in at and what date and so forth.
I really need suggestions from all the guru's out there.
Here is the SQL:
DECLARE #CompanyID INT = 22718,
#StartDate DATETIME = '',
#EndDate DATETIME = '',
#SalesRepID INT = NULL,
#AssignedTo INT = NULL,
#ServiceDefID INT = NULL,
#ProductName VARCHAR(512) = NULL,
#IsCCOrder BIT = NULL,
#OrderID INT = NULL,
#LocationID INT = NULL,
#SalesRepLocationID INT = NULL,
#PONum VARCHAR(150) = NULL,
#InvoiceID INT = NULL,
#IsSplitOrder BIT = NULL,
#ContactID INT = NULL,
#ContactName VARCHAR(150) = NULL,
#JobName VARCHAR(200) = NULL,
#Notes VARCHAR(MAX) = NULL,
#CompanyName VARCHAR(255) = NULL,
#DueDateFrom DATETIME = '',
#DueDateTo DATETIME = '',
#SubmitedDateFrom DATETIME = '',
#SubmitedDateTo DATETIME = '',
#IsRush BIT = NULL,
#Msg VARCHAR(1000) = NULL
--#Stages dbo.int_tbltype READONLY
DECLARE #Stages TABLE (ID INT)
--INSERT INTO #Stages (ID) VALUES (1)
DECLARE #DueDate DATETIME = NULL
SET NOCOUNT ON
DECLARE #OrderIDsTBL TABLE(OrderID INT)
IF #Msg IS NOT NULL
BEGIN
INSERT INTO #OrderIDsTBL (OrderID)
SELECT OrderID
FROM tblOrderLog
WHERE Msg LIKE '%' + #Msg + '%'
END
IF #OrderID IS NOT NULL
BEGIN
INSERT INTO #OrderIDsTBL (OrderID)
VALUES (#OrderID)
END
DECLARE #OderIDsCnt INT = (SELECT COUNT(OrderID) FROM #OrderIDsTBL)
DECLARE #StageCnt INT = (SELECT COUNT(ID) FROM #Stages)
SELECT
o.OrderID,
o.CompanyID,
co.Name AS CompanyName,
o.ContactID,
o.JobName,
p.FirstName + ' ' + p.LastName AS ContactName,
p2.FirstName + ' ' + p2.LastName AS SalesRep,
o.DueDate,
CASE WHEN MAX(oi.PriorityService) > 0 THEN 1 ELSE 0 END AS IsRush,
ISNULL(s.StageID, 0) AS StageID,
o.Notes, r.SubmittedComplete,
dbo.fOrderRunLocationCSVByOrderID(o.OrderID) AS LocationCSV,
(SELECT
STUFF((SELECT DISTINCT ' ' + st.Name + '<br />' FROM tblStage st
INNER JOIN tblOrderItem oi ON oi.OrderID = o.OrderID
INNER JOIN tblRun r ON r.OrderItemID = oi.OrderItemID
INNER JOIN tblStage s ON s.StageID = r.StageID
LEFT JOIN tblRunService rs ON rs.RunID = r.RunID
WHERE (s.StageID = st.StageID)
AND (rs.AssignedTo = #AssignedTo OR #AssignedTo IS NULL)
FOR XML PATH(''), TYPE).value('.','VARCHAR(max)'), 1, 1, ''))
AS Stages,
Row_Number() Over(Order By o.OrderID Desc) As RowNum
FROM
tblOrder o
INNER JOIN
tblCompany co ON co.CompanyID = o.CompanyID
INNER JOIN
tblParty p ON p.PartyID = o.ContactID
-------- PROBLEM JOINS ------------
LEFT JOIN
tblOrderItem oi ON oi.OrderID = o.OrderID
LEFT JOIN
tblRun r ON r.OrderItemID = oi.OrderItemID
LEFT JOIN
tblService srv ON srv.OrderItemID = oi.OrderItemID
-------- END PROBLEM JOINS ------------
LEFT JOIN
tblStage s ON s.StageID = r.StageID
LEFT JOIN
tblParty p2 ON p2.PartyID = o.SalesRepID
LEFT JOIN
tblEmployee e ON e.EmployeeID = o.SalesRepID
LEFT JOIN
tblShipTo st ON o.ShipToID = st.ShipToID
WHERE
(#CompanyID IS NULL OR (o.CompanyID = #CompanyID )) AND
(#IsCCOrder IS NULL OR (ISNULL(o.IsCreditCardOrder, 0) = #IsCCOrder )) AND
(#SalesRepID IS NULL OR o.SalesRepID = #SalesRepID) AND
(#ServiceDefID IS NULL OR (srv.ServiceDefID = #ServiceDefID)) AND
(#ProductName IS NULL OR (oi.Name LIKE '%' + #ProductName + '%')) AND
(#IsSplitOrder IS NULL OR (#IsSplitOrder = 1 AND oi.SplitOrderID IS NOT NULL)) AND
(
(#StartDate = '' OR #EndDate = '') OR
(#StartDate >= CreatedDate AND #EndDate <= COALESCE(CancelledDate, ClosedDate, GetDate())) OR
(#StartDate <= COALESCE(CancelledDate, ClosedDate, GETDATE()) AND #EndDate >= COALESCE(CancelledDate, ClosedDate, GetDate()) ) OR
(#StartDate <= CreatedDate AND #EndDate >= CreatedDate )
) AND
(#LocationID IS NULL OR (#LocationID = srv.LocationID OR srv.LocationID IS NULL)) AND
(#SalesRepLocationID IS NULL OR (#SalesRepLocationID = e.LocationID OR e.LocationID IS NULL))
AND (#InvoiceID IS NULL OR o.InvoiceID = #InvoiceID )
AND (#PONum IS NULL OR o.PONum LIKE '%' + #PONum + '%')
AND (COALESCE(s.StageID, 0) IN (SELECT ID FROM #Stages) OR #StageCnt = 0)
AND (o.ContactID = #ContactID OR #ContactID IS NULL)
AND (p.FirstName + ' ' + p.LastName LIKE '%' + #ContactName + '%' OR #ContactName IS NULL)
AND (o.JobName LIKE '%' + #JobName + '%' OR #JobName IS NULL)
AND (o.Notes LIKE '%' + #Notes + '%' OR #Notes IS NULL)
AND (co.Name LIKE '%' + #CompanyName + '%' OR #CompanyName IS NULL)
AND (o.DueDate >= #DueDateFrom OR #DueDateFrom = '')
AND (o.DueDate <= #DueDateTo OR #DueDateTo = '')
AND (r.SubmittedComplete >= #SubmitedDateFrom OR #SubmitedDateFrom = '')
AND (r.SubmittedComplete <= #SubmitedDateTo OR #SubmitedDateTo = '')
AND (#IsRush = (CASE WHEN oi.PriorityService > 0 THEN 1 ELSE 0 END)
OR #IsRush IS NULL)
AND (o.OrderID IN (SELECT OrderID FROM #OrderIDsTBL) OR #OderIDsCnt = 0)
GROUP BY
o.OrderID, o.CompanyID,
co.Name,
o.ContactID, o.JobName,
p.FirstName, p.LastName, p2.FirstName, p2.LastName,
o.DueDate, o.Notes,
r.SubmittedComplete,
s.StageID
Thanks for any suggestions. I've been working on this for some time now and just can't get it working right.
It looks like you're trying to do too much with a single SELECT statement. If you want one row per unique OrderID, then don't join with the tables that have multiple rows for the same OrderID. Remove the GROUP BY clause. Use one or more separate SELECT statements to get the details from the tables that have multiple rows per OrderID.
Thanks everyone for the suggestions but I found my own solution.
With the entire sql, I placed it into a temp table.
Then used this to sort out the duplicate OrderIds....
SELECT OrderID, CompanyID, ContactID, CompanyName, JobName, ContactName,
SalesRep, DueDate, IsRush , StageID, Notes, SubmittedComplete,
LocationCSV, Stages
FROM (SELECT *, ROW_NUMBER() OVER(PARTITION BY OrderID ORDER BY OrderID DESC) 'RowRank'
FROM #SearchTbl
)sub
WHERE RowRank = 1

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