The following UDF returns an integer based on different conditions executed after a query. The variables, #recCount, is always a zero despite the fact that it can also be greater than zero. It should contain the value of inline query using COUNT(*).
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[fnGetConfID] (#eventID INT,
#conferenceID INT,
#companyID VARCHAR(32))
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #confID INT; -- Conference ID we're passing back
DECLARE #curAtt INT; -- Current attendance
DECLARE #maxAtt INT; -- Max attendance
DECLARE #waitConfID INT; --Waitlist value of current conference
DECLARE #recCount INT; -- Total number of employees who selected same conference
SELECT #confID = ec.conferenceID,
#recCount = (SELECT count(*)
FROM tblRegistration r
INNER JOIN tblRegConferences rc
ON r.ID = rc.regID
WHERE r.optfield2 = #companyID
AND r.eventID = #eventID
AND rc.conferenceID = #conferenceID),
#curAtt = ec.currentAttendance,
#maxAtt = ec.maxAttendance,
#waitConfID = ec.waitListProdID
FROM tblEventConferences ec
WHERE ec.conferenceID = #conferenceID
AND ec.isWaitList = 0
-- If no records were found (waitlist item)
IF ##ROWCOUNT = 0
BEGIN
SET #confID = #conferenceID -- use same value we passed in
END
-- records found
ELSE
BEGIN
--Max attendance not reached, return main conference ID
IF #curAtt < #maxAtt
BEGIN
SET #confID = #conferenceID
END
--Max attendance reached, return waitlist ID
IF #curAtt >= #maxAtt
BEGIN
SET #confID = #waitConfID
END
--Company cap reached, return waitlist ID
IF #recCount > 1
BEGIN
SET #confID = #waitConfID
END
END
RETURN #confID
END
Running it as a query, I get values greater than zero for the field companyCnt, which is #recCount's equivalent.
SELECT ec.conferenceID, (
SELECT count(*)
FROM tblRegistration r
INNER JOIN tblRegConferences rc ON r.ID = rc.regID
WHERE
r.optfield2 = '83b90acc-42af-4de2-9279-76e80eb8b73a'
AND
r.eventID = 624
AND
rc.conferenceID = 8848
) AS companyCnt, ec.currentAttendance, ec.maxAttendance, ec.waitListProdID
FROM
tblEventConferences ec
WHERE
ec.conferenceID = 8848
AND
ec.isWaitList = 0
Change #companyid from varchar(32) to varchar(50).
You are experiencing this problem because the length of company in your query is 36 characters where as the variable is declared only for 32 characters SQL server don't give a warning it just truncates the last characters.
Related
Question: the iteration happens only till record 131 and gives accurate value, after that the parameter #ADE_END_DATE returns a NULL value, why would that be? Below is my code.
Additionally I noticed the column Leave_Date has NULL values and the iteration stops and returns NULL value for the parameter #ADE_END_DATE where the NULL value starts.
Thanks for your help.
BEGIN
DECLARE #HIREDATEPlus1Yr DATETIME
DECLARE #ADE_Leave_Date DATETIME
DECLARE #ADE_End_Date DATETIME
DECLARE #ADE_Start_Date DATETIME
DECLARE #DATECAL DATETIME
DECLARE #i INT
DECLARE #j INT
DECLARE #Loop_length INT
DECLARE #ID VARCHAR(18)
-- start of loop
SET #j = 1
-- Loop length will equal to the list of all ADRs
SET #Loop_Length = (SELECT COUNT([AD_ID])
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE Status NOT IN ('MANAGER', 'TBH', 'FROZEN'))
-- Loop through each ADRs
WHILE (#j <= #Loop_length)
BEGIN
-- Loop through each ADRs
SET #i = 0
-- Find AD ID
SET #ID = (SELECT TOP 1 [AD_ID] FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE [AD_ID] NOT IN (SELECT TOP (#j-1) [AD_ID]
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE ([AD_ID] IS NOT NULL
AND Status NOT IN ('MANAGER', 'TBH', 'FROZEN'))))
-- Find the start date of the ADR
SET #ADE_Start_Date = (SELECT TOP 1 [Hire_Date]
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE [AD_ID] NOT IN (SELECT TOP (#j-1) [AD_ID]
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE ([AD_ID] IS NOT NULL
AND Status NOT IN ('MANAGER', 'TBH', 'FROZEN'))))
-- Hire date plus 1 year
SET #HIREDATEPlus1Yr = DATEADD(YEAR, 1, #ADE_Start_Date)
--Adding Leave Date
SET #ADE_Leave_Date = (SELECT TOP 1 [LEAVE_DATE]
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE [AD_ID] NOT IN (SELECT TOP (#j-1) [AD_ID]
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE ([AD_ID] IS NOT NULL
AND Status NOT IN ('MANAGER', 'TBH', 'FROZEN'))))
-- Set a temporary variable which will be 1 year from now. Use the Date ADD formulae to start date, if they are leaver before one year then add leave date (Use IF): DONE
-- Put everything inside the while loop and add opportunity selecting to it.
IF #ADE_Leave_Date IS NULL
SET #ADE_End_Date = DATEADD(YEAR, 1, #ADE_Start_Date)
ELSE IF #HIREDATEPlus1Yr < #ADE_Leave_Date
SET #ADE_End_Date = DATEADD(YEAR, 1, #ADE_Start_Date)
ELSE
SET #ADE_End_Date = #ADE_Leave_Date
SET #DATECAL = datediff(DAY, #ADE_Start_Date, #ADE_End_Date)
SET #j = #j + 1
UPDATE #TEMPTABLEEEE
SET [#ADE_End_Date] = #ADE_End_Date
WHERE #ID = AD_ID
END
SELECT * FROM #TEMPTABLEEEE
END
I'm not sure why you are using a WHILE loop. It looks like this code could be much simplified. SQL is a set based language. Whenever possible, you should try to handle your data as a whole set instead of breaking it down into row by row evaluations.
Does this give you what you need? If the table has more than one row for each AD_ID, you will need to get the MAX() or MIN() Hire_Date/LEAVE_DATE. To improve the answer, consider providing sample data.
UPDATE t
SET [#ADE_End_Date] = ed.ADE_EndDate
FROM #TEMPTABLEEEE AS t
INNER JOIN (
SELECT AD_ID
,CASE
WHEN LEAVE_DATE IS NULL THEN DATEADD(YEAR,1,Hire_Date)
WHEN DATEADD(YEAR,1,Hire_Date) < LEAVE_DATE THEN DATEADD(YEAR,1,Hire_Date)
ELSE LEAVE_DATE
END AS ADE_EndDate
FROM DS_ADHOC_MOPs.ADE.List
WHERE Status NOT IN ('MANAGER', 'TBH', 'FROZEN')
) AS ed ON t.AD_ID = ed.AD_ID
Big edit to include more details
We display conferences for attendees to select from. Each conference has a duplicate which is treated as a "waiting list" version. The waitlist ID is stored in its parent record in the field waitListProdID. Certain fields are flagged as well.
Conference records = isWaitlist = 0, waitListProdID = *whatever its waitlist ID is*
Conference waitlist records = isWaitList = 1, waitListProdID = 0
Attendees see the waitlist version ONLY on two conditions:
When the conference's currentAttendance equals maxAttendance
If two people from the attendee's company have already registered with any given conference and currentAttendance < maxAttendance
Waitlist conferences have no attendance cap or per-company limit.
I have been tasked to generate a UDF to deal with the second issue. It should return the conference ID (primary or waitlist). The UDF will receive 3 inputs, and 1 output, with the output being either the passed in conferenceID or its waitlist equivalent.
I'm aware that my UDF syntax is incorrect as I'm more familiar with stored procedure creation, but I tried to make it as close as possible.
//This query calls the UDF (build in ColdFusion)
SELECT dt.conferenceID, ec.description, ec.price
FROM (
SELECT dbo.fnGetConfID(eci.conferenceID, #companyID#, #eventID#) as conferenceID
FROM tblEventConferences eci
WHERE eci.eventID = #eventID#
) dt
INNER JOIN tblEventConferences eco ON eci.conferneceID = eco.conferenceID
WHERE eco.eventID = #eventID#
AND eco.currentAttendance < eco.maxAttendance
//The function should return only the correct conferenceID
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION dbo.fnGetConfID (
#conferenceID int,
#companyID varchar(32),
#eventID int,
) RETURNS int
AS
BEGIN
DECLARE #count smallint;
DECLARE #waitListID int;
SET NOCOUNT ON;
--Search only conferences that are not waitlist
SELECT #count = count(rc.regID), #waitListID = rc.conferenceID
FROM tblRegistration r
INNER JOIN tblRegConferences rc ON r.ID = rc.regID
INNER JOIN tblEventConferences ec ON rc.conferenceID = ec.conferenceID
WHERE r.eventID = #eventID
AND r.optfield2 = #companyID
AND rc.conferenceID = #conferenceID
AND ec.isWaitList = 0
AND ec.currentAttendance < ec.maxAttendance
GROUP BY rc.conferenceID
--More than 1 person from same company registered for this conference. Return waitlistID
IF #count > 1
BEGIN
SELECT #conferenceIDout = conferenceID
FROM tblEventConferences
WHERE conferenceID = #waitListID
END
--Company limit not reached. Return conferenceID that was used originally
IF #count <= 1
BEGIN
SET #conferenceIDout = #conferenceID
END
END
GO
I am trying to achieve if else statement or case statement within my code below. I want to use one of these statement (if or case) to see if my RT_Ch_Pres_PX1 values are within certain min or max specs if they are above or below I want to say that if my RT_Ch_Pres_PX1 is below my min value by this much then display that value and indicate by how much is it of by and same this for exceeding max value. for example if my RT_Ch_Pres_PX1 value is 5 and I want to use my min valuse at 6 and max at 10. So my Rt_Ch_pres_px1 value is off by 1 so I would like to display this and say this value is of by 1 value. if RT_Ch_Pres_PX1 is within min and max values do nothing. Please see code below.
DECLARE #Result TABLE
(
RT_DateTime datetime,
RT_Phase_Name varchar(30),
RT_PhaseChangeCount int,
RT_Phase_Type int,
RT_Ch_Pres_PX1 float
);
/* Variables used to track changes to Phase Name */
DECLARE #RT_DateTime datetime;
DECLARE #RT_Phase_Name varchar(30);
DECLARE #RT_PhaseChangeCount int;
DECLARE #RT_Phase_Type int;
DECLARE #RT_Ch_Pres_PX1 float;
DECLARE #PhaseNameHold varchar(30);
DECLARE #PhaseChangeCount int;
SELECT #PhaseNameHold = ' ';
SELECT #PhaseChangeCount = 0;
SELECT #RT_PhaseChangeCount = 0;
/* Declare a cursor for determining when Phases change */
DECLARE ImportCursor CURSOR FAST_FORWARD FOR
SELECT
CONVERT(datetime, dbo.CycleData.Date_Time) as TimeConvert,
[dbo].[LookupPhases].[Phase_Name],
[dbo].[cycledata].[phase_type],
[dbo].[cycledata].[Ch_Pres_PX1]
FROM
CycleData INNER JOIN
CycleDataHeader ON CycleData.Unit_Number = CycleDataHeader.Unit_Number AND CycleData.Cycle_Counter_No = CycleDataHeader.Cycle_Counter_No INNER JOIN
LookupPhases ON CycleData.Phase_Type = LookupPhases.Phase_Type INNER JOIN
LookupEvent ON CycleData.Event_Type = LookupEvent.Event_Id LEFT OUTER JOIN
LookupAlarm ON CycleData.Alarm_Type = LookupAlarm.Alarm_Id
WHERE
[dbo].[CycleDataHeader].[Entered_Load_No1] = 'T14-0008'
ORDER BY
/* Appears to be the order that needs to be reported on */
Cycle_Time
-- dbo.CycleData.Unit_Number,
-- TimeConvert;
OPEN ImportCursor;
FETCH NEXT FROM ImportCursor INTO #RT_DateTime,
#RT_Phase_Name,
#RT_Phase_Type,
#RT_Ch_Pres_PX1
WHILE ##FETCH_STATUS = 0
BEGIN
IF (#RT_Phase_Name <> #PhaseNameHold)
BEGIN
SET #PhaseNameHold = #RT_Phase_Name;
SET #RT_PhaseChangeCount = #RT_PhaseChangeCount + 1;
END
INSERT INTO #Result VALUES(#RT_DateTime, #RT_Phase_Name,#RT_PhaseChangeCount,#RT_Phase_Type,#RT_Ch_Pres_PX1);
FETCH NEXT FROM ImportCursor INTO #RT_DateTime, #RT_Phase_Name,#RT_Phase_Type,#RT_Ch_Pres_PX1;
END
CLOSE ImportCursor;
DEALLOCATE ImportCursor;
SELECT
RT_DateTime,
RT_Phase_Name,
RT_PhaseChangeCount,
RT_Phase_Type,
RT_Ch_Pres_PX1
FROM #Result;
This case will generate the value you want:
case
when RT_Ch_Pres_PX1 < some_min then RT_Ch_Pres_PX1 - some_min
when RT_Ch_Pres_PX1 > some_max then RT_Ch_Pres_PX1 - some_max
else 0
end
The value created for the undershoot is negative (a good idea I think). If you want it to be positive, flip the calculation.
Is it possible to compare rows for similar data in SQL Server? I have a company name column in a table where company names could be somewhat similar. Here is an example of the different 8 values that represent the same 4 companies:
ANDORRA WOODS
ANDORRA WOODS HEALTHCARE CENTER
ABC HEALTHCARE, JOB #31181
ABC HEALTHCARE, JOB #31251
ACTION SERVICE SALES, A SUBSIDIARY OF SINGER EQUIPMENT
ACTION SERVICE SALES, A SUBSIDIARY OF SINGER EQUIPMENT COMPANY
APEX SYSTEMS
APEX SYSTEMS, INC
The way I clean it right now is using Google refine where I can identify clusters of similar data values and make them all as one.
Using this example I only need 4 names not 8 so I need to replace similar ones with only one since I will be assigning indexes to those names later on. Any help is greatly appreciated.
I have a couple UDF's I converted from some VB code some time ago that takes in 2 varchar() and returns an int between 0 and 100 (0= not similar, 100= same) if your interested.
-- Description: Removes any special characters from a string
CREATE FUNCTION [dbo].[SimReplaceSpecial]
(
-- Add the parameters for the function here
#String varchar(max)
)
RETURNS varchar(max)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result varchar(max) = ''
-- Add the T-SQL statements to compute the return value here
DECLARE #Pos int = 1
DECLARE #Asc int
DECLARE #WorkingString varchar(max)
SET #WorkingString = upper(#String)
WHILE #Pos <= LEN(#WorkingString)
BEGIN
SET #Asc = ascii(substring(#WorkingString,#Pos,1))
If (#Asc >= 48 And #Asc <= 57) Or (#Asc >= 65 And #Asc <= 90)
SET #Result = #Result + Char(#Asc)
SET #Pos = #Pos + 1
--IF #Pos + 1 > len(#String)
-- BREAK
--ELSE
-- CONTINUE
END
-- Return the result of the function
RETURN #Result
END
-- Description: DO NOT CALL DIRECTLY - Used by the Similar function
-- Finds longest common substring (other than single
-- characters) in String1 and String2, then recursively
-- finds longest common substring in left-hand
-- portion and right-hand portion. Updates the
-- cumulative score.
CREATE FUNCTION [dbo].[SimFindCommon]
(
-- Add the parameters for the function here
#String1 varchar(max),
#String2 varchar(max),
#Score int
)
RETURNS int
AS
BEGIN
-- Declare the return variable here
--DECLARE #Result int
DECLARE #Longest Int = 0
DECLARE #StartPos1 Int = 0
DECLARE #StartPos2 Int = 0
DECLARE #J Int = 0
DECLARE #HoldStr varchar(max)
DECLARE #TestStr varchar(max)
DECLARE #LeftStr1 varchar(max) = ''
DECLARE #LeftStr2 varchar(max) = ''
DECLARE #RightStr1 varchar(max) = ''
DECLARE #RightStr2 varchar(max) = ''
-- Add the T-SQL statements to compute the return value here
SET #HoldStr = #String2
WHILE LEN(#HoldStr) > #Longest
BEGIN
SET #TestStr = #HoldStr
WHILE LEN(#TestStr) > 1
BEGIN
SET #J = CHARINDEX(#TestStr,#String1)
If #J > 0
BEGIN
--Test string is sub-set of the other string
If Len(#TestStr) > #Longest
BEGIN
--Test string is longer than previous
--longest. Store its length and position.
SET #Longest = Len(#TestStr)
SET #StartPos1 = #J
SET #StartPos2 = CHARINDEX(#TestStr,#String2)
END
--No point in going further with this string
BREAK
END
ELSE
--Test string is not a sub-set of the other
--string. Discard final character of test
--string and try again.
SET #TestStr = Left(#TestStr, LEN(#TestStr) - 1)
END
--Now discard first char of test string and
--repeat the process.
SET #HoldStr = Right(#HoldStr, LEN(#HoldStr) - 1)
END
--Update the cumulative score with the length of
--the common sub-string.
SET #Score = #Score + #Longest
--We now have the longest common sub-string, so we
--can isolate the sub-strings to the left and right
--of it.
If #StartPos1 > 3 And #StartPos2 > 3
BEGIN
SET #LeftStr1 = Left(#String1, #StartPos1 - 1)
SET #LeftStr2 = Left(#String2, #StartPos2 - 1)
If RTRIM(LTRIM(#LeftStr1)) <> '' And RTRIM(LTRIM(#LeftStr2)) <> ''
BEGIN
--Get longest common substring from left strings
SET #Score = dbo.SimFindCommon(#LeftStr1, #LeftStr2,#Score)
END
END
ELSE
BEGIN
SET #LeftStr1 = ''
SET #LeftStr2 = ''
END
If #Longest > 0
BEGIN
SET #RightStr1 = substring(#String1, #StartPos1 + #Longest, LEN(#String1))
SET #RightStr2 = substring(#String2, #StartPos2 + #Longest, LEN(#String2))
If RTRIM(LTRIM(#RightStr1)) <> '' And RTRIM(LTRIM(#RightStr2)) <> ''
BEGIN
--Get longest common substring from right strings
SET #Score = dbo.SimFindCommon(#RightStr1, #RightStr2,#Score)
END
END
ELSE
BEGIN
SET #RightStr1 = ''
SET #RightStr2 = ''
END
-- Return the result of the function
RETURN #Score
END
-- Description: Compares two not-empty strings regardless of case.
-- Returns a numeric indication of their similarity
-- (0 = not at all similar, 100 = identical)
CREATE FUNCTION [dbo].[Similar]
(
-- Add the parameters for the function here
#String1 varchar(max),
#String2 varchar(max)
)
RETURNS int
AS
BEGIN
-- Declare the return variable here
DECLARE #Result int
DECLARE #WorkingString1 varchar(max)
DECLARE #WorkingString2 varchar(max)
-- Add the T-SQL statements to compute the return value here
if isnull(#String1,'') = '' or isnull(#String2,'') = ''
SET #Result = 0
ELSE
BEGIN
--Convert each string to simplest form (letters
--and digits only, all upper case)
SET #WorkingString1 = dbo.SimReplaceSpecial(#String1)
SET #WorkingString2 = dbo.SimReplaceSpecial(#String2)
If RTRIM(LTRIM(#WorkingString1)) = '' Or RTRIM(LTRIM(#WorkingString2)) = ''
BEGIN
--One or both of the strings is now empty
SET #Result = 0
END
ELSE
BEGIN
If #WorkingString1 = #WorkingString2
BEGIN
--Strings are identical
SET #Result = 100
END
ELSE
BEGIN
--Find all common sub-strings
SET #Result = dbo.SimFindCommon(#WorkingString1, #WorkingString2,0)
--We now have the cumulative score. Return this
--as a percent of the maximum score. The maximum
--score is the average length of the two strings.
SET #Result = #Result * 200 / (Len(#WorkingString1) + Len(#WorkingString2))
END
END
END
-- Return the result of the function
RETURN #Result
END
--Usage--------------------------------------------------------------------
--Call the "Similar" Function only
SELECT dbo.Similar('ANDORRA WOODS','ANDORRA WOODS HEALTHCARE CENTER')
--Result = 60
SELECT dbo.Similar('ABC HEALTHCARE, JOB #31181','ABC HEALTHCARE, JOB #31251')
--Result = 85
SELECT dbo.Similar('ACTION SERVICE SALES, A SUBSIDIARY OF SINGER EQUIPMENT','ACTION SERVICE SALES, A SUBSIDIARY OF SINGER EQUIPMENT COMPANY')
--Result = 92
SELECT dbo.Similar('APEX SYSTEMS','APEX SYSTEMS, INC')
--Result = 88
SSIS/Data Tools has a Fuzzy Grouping transformation that is very helpful in situations like this. It doesn't actually group your data, rather it gives you similarity scores that you can use to determine when items should be grouped together.
Plenty of tutorials out there, here's one: The Fuzzy Grouping Transformation
I am currently developing on an advertising system, which have been running just fine for a while now, apart from recently when our views per day have shot up from about 7k to 328k. Our server cannot take the pressure on this anymore - and knowing that I am not the best SQL guy around (hey, I can make it work, but not always in the best way) I am asking here for some optimization guidelines. I hope that some of you will be able to give rough ideas on how to improve this - I don't specifically need code, just to see the light :).
As it is at the moment, when an advert is supposed to be shown a PHP script is called, which in return calls a stored procedure. This stored procedure does several checks, it tests up against our customer database to see if the person showing the advert (given by a primary key id) is an actual customer under the given locale (our system is running on several languages which are all run as separate sites). Next up is all the advert details fetched out (image location as an url, height and width of the advert) - and lest step calls a separate stored procedure to test if the advert is allowed to be shown (is the campaign expired by either date or number of adverts allowed to show?) and if the customer has access to it (we got 2 access systems running, a blacklist and a whitelist one) and lastly what type of campaign we're running, is the view unique and so forth.
The code consists of a couple of stored procedures that I will post in here.
--- procedure called from PHP
CREATE PROCEDURE [dbo].[ExecView]
(
#publisherId bigint,
#advertId bigint,
#localeId int,
#ip varchar(15),
#ipIsUnique bit,
#success bit OUTPUT,
#campaignId bigint OUTPUT,
#advert varchar(500) OUTPUT,
#advertWidth int OUTPUT,
#advertHeight int OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #unique bit
DECLARE #approved bit
DECLARE #publisherEarning money
DECLARE #advertiserCost money
DECLARE #originalStatus smallint
DECLARE #advertUrl varchar(500)
DECLARE #return int
SELECT #success = 1, #advert = NULL, #advertHeight = NULL, #advertWidth = NULL
--- Must be valid publisher, ie exist and actually be a publisher
IF dbo.IsValidPublisher(#publisherId, #localeId) = 0
BEGIN
SELECT #success = 0
RETURN 0
END
--- Must be a valid advert
EXEC #return = FetchAdvertDetails #advertId, #localeId, #advert OUTPUT, #advertUrl OUTPUT, #advertWidth OUTPUT, #advertHeight OUTPUT
IF #return = 0
BEGIN
SELECT #success = 0
RETURN 0
END
EXEC CanAddStatToAdvert 2, #advertId, #publisherId, #ip, #ipIsUnique, #success OUTPUT, #unique OUTPUT, #approved OUTPUT, #publisherEarning OUTPUT, #advertiserCost OUTPUT, #originalStatus OUTPUT, #campaignId OUTPUT
IF #success = 1
BEGIN
INSERT INTO dbo.Stat (AdvertId, [Date], Ip, [Type], PublisherEarning, AdvertiserCost, [Unique], Approved, PublisherCustomerId, OriginalStatus)
VALUES (#advertId, GETDATE(), #ip, 2, #publisherEarning, #advertiserCost, #unique, #approved, #publisherId, #originalStatus)
END
END
--- IsValidPublisher
CREATE FUNCTION [dbo].[IsValidPublisher]
(
#publisherId bigint,
#localeId int
)
RETURNS bit
AS
BEGIN
DECLARE #customerType smallint
DECLARE #result bit
SET #customerType = (SELECT [Type] FROM dbo.Customer
WHERE CustomerId = #publisherId AND Deleted = 0 AND IsApproved = 1 AND IsBlocked = 0 AND LocaleId = #localeId)
IF #customerType = 2
SET #result = 1
ELSE
SET #result = 0
RETURN #result
END
-- Fetch advert details
CREATE PROCEDURE [dbo].[FetchAdvertDetails]
(
#advertId bigint,
#localeId int,
#advert varchar(500) OUTPUT,
#advertUrl varchar(500) OUTPUT,
#advertWidth int OUTPUT,
#advertHeight int OUTPUT
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT #advert = T1.Advert, #advertUrl = T1.TargetUrl, #advertWidth = T1.Width, #advertHeight = T1.Height FROM Advert as T1
INNER JOIN Campaign AS T2 ON T1.CampaignId = T2.Id
WHERE T1.Id = #advertId AND T2.LocaleId = #localeId AND T2.Deleted = 0 AND T2.[Status] <> 1
IF #advert IS NULL
RETURN 0
ELSE
RETURN 1
END
--- CanAddStatToAdvert
CREATE PROCEDURE [dbo].[CanAddStatToAdvert]
#type smallint, --- Type of stat to add
#advertId bigint,
#publisherId bigint,
#ip varchar(15),
#ipIsUnique bit,
#success bit OUTPUT,
#unique bit OUTPUT,
#approved bit OUTPUT,
#publisherEarning money OUTPUT,
#advertiserCost money OUTPUT,
#originalStatus smallint OUTPUT,
#campaignId bigint OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #campaignLimit int
DECLARE #campaignStatus smallint
DECLARE #advertsLeft int
DECLARE #campaignType smallint
DECLARE #campaignModeration smallint
DECLARE #count int
SELECT #originalStatus = 0
SELECT #success = 1
SELECT #approved = 1
SELECT #unique = 1
SELECT #campaignId = CampaignId FROM dbo.Advert
WHERE Id = #advertId
IF #campaignId IS NULL
BEGIN
SELECT #success = 0
RETURN
END
SELECT #campaignLimit = Limit, #campaignStatus = [Status], #campaignType = [Type], #publisherEarning = PublisherEarning, #advertiserCost = AdvertiserCost, #campaignModeration = ModerationType FROM dbo.Campaign
WHERE Id = #campaignId
IF (#type <> 0 AND #type <> 2 AND #type <> #campaignType) OR ((#campaignType = 0 OR #campaignType = 2) AND (#type = 1)) -- if not a click or view type, then type must match the campaign (ie, only able to do leads on lead campaigns, no isales or etc), click and view campaigns however can do leads too
BEGIN
SELECT #success = 0
RETURN
END
-- Take advantage of the fact that the variable only gets touched if there is a record,
-- which is supposed to override the existing one, if there is one
SELECT #publisherEarning = Earning FROM dbo.MapCampaignPublisherEarning
WHERE CanpaignId = #campaignId AND PublisherId = #publisherId
IF #campaignStatus = 1
BEGIN
SELECT #success = 0
RETURN
END
IF NOT #campaignLimit IS NULL
BEGIN
SELECT #advertsLeft = AdvertsLeft FROM dbo.Campaign WHERE Id = #campaignId
IF #advertsLeft < 1
BEGIN
SELECT #success = 0
RETURN
END
END
IF #campaignModeration = 0 -- blacklist
BEGIN
SELECT #count = COUNT([Status]) FROM dbo.MapCampaignModeration WHERE CampaignId = #campaignId AND PublisherId = #publisherId AND [Status] = 3
IF #count > 0
BEGIN
SELECT #success = 0
RETURN
END
END
ELSE -- whitelist
BEGIN
SELECT #count = COUNT([Status]) FROM dbo.MapCampaignModeration WHERE CampaignId = #campaignId AND PublisherId = #publisherId AND [Status] = 2
IF #count < 1
BEGIN
SELECT #success = 0
RETURN
END
END
IF #ipIsUnique = 1
BEGIN
SELECT #unique = 1
END
ELSE
BEGIN
IF (SELECT COUNT(T1.Id) FROM dbo.Stat AS T1
INNER JOIN dbo.IQ_Advert AS T2
ON T1.AdvertId = T2.Id
WHERE T2.CampaignId = #campaignId
AND T1.[Type] = #type
AND T1.[Unique] = 1
AND T1.PublisherCustomerId = #publisherId
AND T1.Ip = #ip
AND DATEADD(SECOND, 86400, T1.[Date]) > GETDATE()
) = 0
SELECT #unique = 1
ELSE
BEGIN
SELECT #unique = 0, #originalStatus = 1 -- not unique, and set status to be ip conflict
END
END
IF #unique = 0 AND #type <> 0 AND #type <> 2
BEGIN
SELECT #unique = 1, #approved = 0
END
IF #originalStatus = 0
SELECT #originalStatus = 5
IF #approved = 0 OR #type <> #campaignType
BEGIN
SELECT #publisherEarning = 0, #advertiserCost = 0
END
END
I am thinking this needs more than just a couple of indexes thrown in to help it, but rather a total rethinking of how to handle it. I have been heard that running this as a batch would help, but I am not sure how to get this implemented, and really not sure if i can implement it in a such way where I keep all these nice checks before the actual insert or if I have to give up on some of this.
Anyhow, all help would be appreciated, if you need any of the table layouts, let me know :).
Thanks for taking the time to look at it :)
Make sure to reference tables with the ownership prefix. So instead of:
INNER JOIN Campaign AS T2 ON T1.CampaignId = T2.Id
Use
INNER JOIN dbo.Campaign AS T2 ON T1.CampaignId = T2.Id
That will allow the database to cache the execution plan.
Another possibility is to disable database locking, which has data integrity risks, but can significantly increase performance:
INNER JOIN dbo.Campaign AS T2 (nolock) ON T1.CampaignId = T2.Id
Run a sample query in SQL Analyzer with "Show Execution Plan" turned on. This might give you a hint as to the slowest part of the query.
it seems like FetchAdvertDetails hit the same tables as the start of CanAddStatToAdvert (Advert and Campaign). If possible, I'd try to eliminate FetchAdvertDetails and roll its logic into CanAddStatToAdvert, so you don't have the hit Advert and Campaign the extra times.
Get rid of most of the SQL.
This stored procedure does several
checks, it tests up against our
customer database to see if the person
showing the advert (given by a primary
key id) is an actual customer under
the given locale (our system is
running on several languages which are
all run as separate sites). Next up is
all the advert details fetched out
(image location as an url, height and
width of the advert) - and lest step
calls a separate stored procedure to
test if the advert is allowed to be
shown (is the campaign expired by
either date or number of adverts
allowed to show?) and if the customer
has access to it (we got 2 access
systems running, a blacklist and a
whitelist one) and lastly what type of
campaign we're running, is the view
unique and so forth.
Most of this should not be done in the database for every request. In particular:
Customer and local can be stored in memory. Expire them after 5 minutes or so, but do not ask for this info on every repetitive request.
Advert details can also be stored. Every advert will have a "key" to identify it (Number)?. Ust a dictionary / hashtable in memory.
Eliminate as many SQL Parts as you can. Dumping repetitive work on the SQL Database is a typical mistake.