Insert data based on non existence and different criteria's [closed] - sql

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I'm trying to add a row to my table only on two conditions but when inserting it retrieves error and I cannot figure it out
Create PROC [dbo].[setvisitorqueue]
#pid bigint = null , #vid int = NULL ,#regdate nvarchar(50) =NULL
AS
declare #queNum int =null
set #queNum = (select max([ticketNo]) + 1 from [dbo].[queue] where [ticketdate]= GetDate())
if( #queNum is null) begin set #queNum=1 end
Declare #Tktt int = null
set #Tktt = (select count(queue.ticketid) from queue where (queue.pid = #pid )and (queue.ticketdate = GetDate()) and (queue.vid = #vid and queue.checked = 0))
if (#Tktt is null )
begin insert into queue (vid , pid , ticketNo , ticketdate ) Values (#Vid,#pid,#queNum,#regdate ) end
Its not working for me.

Can you try it simple way like this?
CREATE PROC [dbo].[setvisitorqueue]
#pid BIGINT = null,
#vid INT = NULL,
#regdate NVARCHAR(50) = NULL
AS
IF (
SELECT COUNT(ticketid)
FROM [dbo].[queue]
WHERE checked = 0 and pid = #pid and vid = #vid and ticketdate = GetDate()
) = 0
INSERT INTO [dbo].[queue](vid pid, ticketdate, ticketNo )
SELECT #Vid, #pid, #regdate, ticketNo = IsNull(MAX([ticketNo]),0) + 1
FROM [dbo].[queue]
WHERE [ticketdate]= GetDate();
RETURN;
GO
In this code I've done following:
Improved readability by Caps, intend, spaces, etc.
Eliminated variables - you do not need them in that code You do not
need to calculate a "TicketNo" in the beginning if it won't be used.
So, it will be calculated if needed within IF statement.
You do not need to use BEGIN-END on every transaction, single
request IS a transaction
Not sure what your error was, but your procedure won't do anything just because when you do "COUNT" it returns a number. That means your "#Tktt" variable would never be NULL.
I guess your intention is to run the Insert statement when it is no records found and compared "COUNT" query to "0" value.

Here is your SP with all the issues I spotted corrected with comments, and with best practices added. As noted by the other answer you can probably simplify things. I have just aimed to correct existing issues.
-- NOTES: Keep your casing and layout consistent
-- Always terminate statements with a semi-colon
-- Don't add un-necessary brackets, they just clutter the code
-- You also have a concurrency issue:
-- if this proc is called twice at the same time you could issue the same ticket number twice
create proc [dbo].[setvisitorqueue]
(
#pid bigint = null,
#vid int = null,
-- Only every use a datetime/date datatype to store a datatime/date. Datetime2 is the current standard. Change precision to suit.
#regdate datetime2(0) = null
)
as
begin
-- Always start your SP with
set nocount, xact_abort on;
declare #queNum int = null;
set #queNum = (
select max([ticketNo]) + 1
from dbo.[queue]
-- This seems very unlikely to happen? It has to match down to the fraction of a second.
where [ticketdate] = getdate()
);
if #queNum is null begin
set #queNum = 1;
end;
declare #Tktt int = null;
-- #Tktt will *never* be null after this, it may be zero though.
set #Tktt = (
select count(*)
from dbo.[queue]
where pid = #pid
-- This seems very unlikely to happen? It has to match down to the fraction of a second.
and ticketdate = getdate()
and vid = #vid and checked = 0
);
-- Handle 0 or null just in case
-- if #Tktt is null -- THIS IS WHAT PREVENTED YOUR INSERT
if coalesce(#Tktt,0) = 0
begin
insert into dbo.[queue] (vid, pid, ticketNo, ticketdate)
values (#Vid, #pid, #queNum, #regdate);
end;
-- Always return the status of the SP, 0 means OK
return 0;
end;

Related

SQL Function is INCREDIBLY slow

I have a SQL function that is used to return a single value in another view, this function takes well over 30 seconds sometimes on larger databases, I think it might be running over and over?
Honestly I'm just losing my mind at this point and need the help. Does anyone know the best way to optimize this?
The T-SQL function looks like this:
IF OBJECT_ID('Base.fn_AssetPriority') IS NOT NULL
DROP FUNCTION Base.fn_AssetPriority
GO
CREATE FUNCTION Base.fn_AssetPriority
(#LID BIGINT)
RETURNS NVARCHAR(20)
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE #Priority NVARCHAR(20)
DECLARE #RGID BIGINT
DECLARE #CP TABLE
(
ConsequenceAssessmentID BIGINT,
[Sign] VARCHAR(2),
Score DECIMAL(18,2),
AssetPriority CHAR(1),
ConsNo INT
)
SET #Priority = 'Not Allocated'
INSERT INTO #CP
SELECT
ConsequenceAssessmentID, [Sign], Score, AssetPriority, ConsNo
FROM
Base.ConsequencePriority
ORDER BY
ConsNo DESC
SELECT #RGID = MAX(ID)
FROM ACA.ReviewGroup
WHILE EXISTS (SELECT * FROM #CP)
BEGIN
DECLARE #CAID BIGINT
DECLARE #ConsNo INT
DECLARE #Sign VARCHAR(2)
DECLARE #Score DECIMAL(18,2)
DECLARE #AP CHAR(1)
SELECT TOP 1
#CAID = ConsequenceAssessmentID,
#ConsNo = ConsNo,
#Sign = [Sign],
#Score = Score,
#AP = AssetPriority
FROM
#CP
ORDER BY
ConsNo DESC
IF #Sign = '='
BEGIN
IF EXISTS (SELECT * FROM ACA.ConsequenceAssessment
WHERE LID = #LID AND RGID = #RGID
AND BaseCAID = #CAID AND Score = #Score)
BEGIN
SET #Priority = #AP
BREAK
END
END
ELSE BEGIN
IF EXISTS (SELECT * FROM ACA.ConsequenceAssessment
WHERE LID = #LID AND RGID = #RGID
AND BaseCAID = #CAID AND Score >= #Score)
BEGIN
SET #Priority = #AP
BREAK
END
END
DELETE FROM #CP
WHERE ConsequenceAssessmentID = #CAID
AND ConsNo = #ConsNo
END
RETURN #Priority
END
There is another view that calls this as a field:
Base.fn_AssetPriority(BaseAS.ID) AS AssetPriority,
How on Earth do I optimize this? or get it to run a bit quicker?
It's possible the execution plan for your stored function is stale.
Try doing this and rerunning it.
EXEC sp_recompile N'Base.fn_AssetPriority';
If it gets faster you may want to run that recompile every so often. Maybe use a job to recompile it every day.
You probably don't want to put WITH RECOMPILE in the function's definition, because you use it a lot and the reason for recompilation is changing statistics in the tables it queries.

I have to update 40 million rows in SQL Server [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
The code shown here is taking a very long time to update or populate 42673844. Could anyone help me on how to simplify this code and improve the performance.
I am trying to populate the Session ID's based on some conditions
Declaration of variables
declare #sval as INT
declare #maxRow as INT
declare #currvlt as int
declare #prevvlt as int
declare #cashamount as int
declare #sec as INT
declare #next_action as INT
declare #previous_action as INT
declare #rec as int
declare #currentRow as INT
-- assigning the maximum value to the #maxrow
set #maxRow = (select max(number) from dbo.stage3 as count)
print #maxRow
set #sval = 0
set #currentRow = 1
I am putting the conditions in a while loop to perform an update all the 42 million rows. The below select will assign the values to the declared variables
While (#currentRow <= #maxRow)
begin
select
#currvlt = vltid ,
#prevvlt = previous_vltid ,
#cashamount = isnull(playercashableamount,0),
#sec = seconds,
#next_action = next_action , --next record type
#previous_action = previous_action,
#rec = recordtype
from dbo.stage3
where number = #currentRow
if #currvlt <> #prevvlt or #prevvlt is null
begin
update dbo.stage3 set sessionid = #sval+1 where number = #currentRow
set #sval = #sval +1
end
else
if (#rec = '4' and #sec > 30 and #previous_action in( 1 ,5)) or
(#cashamount < 1 and #sec < 30 and #next_action in( 1 ,4 )) or
((#rec = '5' and #sec < 30 and #next_action = '4' ) or
#next_action = '4')
begin
update dbo.stage3 set sessionid = #sval where number = #currentRow
end
else
update dbo.stage3 set sessionid = #sval where number = #currentRow
set #currentRow = #currentRow +1
end
This might help. You should not need to do a row by row evaluation. Instead, address the entire set in one call when possible. Since you have provided no information about the business challenge you are trying to solve, this code is posted as-is. This may not be exactly what you're after, but it should get you going in the right direction. Please adjust for your own needs.
Also, any update on a table with 40 million rows will take a long time; even if you have perfectly tuned indexes on lightning fast storage. Instead of thinking "how do I get the answer I want?", try thinking "what am I asking the server to do for me?", and "is there a better way to make this request?" SQL Server runs best when dealing with a set of data in one pass. Analyzing row by row is not what the engine is built to do. It can be done, but should be used only when all other means have been exhausted.
The code below is a select statement. Run it, and if the results are what you're looking for, uncomment the UPDATE section, then comment out the SELECT section and run it.
SELECT CASE
WHEN recordtype = '4'
AND seconds > 30
AND previous_action IN (1, 4) THEN 0
WHEN s.cash_amount < 1
AND seconds < 30
AND next_action IN (1, 4) THEN 0
WHEN next_action = 4 THEN 0
WHEN (vltid <> previous_vltid)
OR previous_vltid IS NULL THEN s.RowNum
ELSE s.RowNum
END AS session_id
,s.vltid
,s.previous_vltid
,s.cash_amount
,s.recordtype
,s.previous_action
,s.seconds
,s.next_action
--UPDATE s
--SET sessionid = CASE
-- WHEN recordtype = '4'
-- AND seconds > 30
-- AND previous_action IN (1, 4) THEN 0
-- WHEN s.cash_amount < 1
-- AND seconds < 30
-- AND next_action IN (1, 4) THEN 0
-- WHEN next_action = 4 THEN 0
-- WHEN (vltid <> previous_vltid)
-- OR previous_vltid IS NULL THEN s.RowNum
-- ELSE s.RowNum
-- END
FROM (
SELECT sessionid
,ROW_NUMBER() OVER (PARTITION BY vltid
ORDER BY
vltid
) AS RowNum
,vltid
,previous_vltid
,ISNULL(playercashableamount, 0) AS cash_amount
,recordtype
,previous_action
,seconds
,next_action
FROM dbo.stage3
) AS s;

How to set total number of rows before an OFFSET occurs in stored procedure

I've created a stored procedure that filters and paginates for a DataTable.
Problem: I need to set an OUTPUT variable for #TotalRecords found before an OFFSET occurs, otherwise it sets #TotalRecord to #RecordPerPage.
I've messed around with CTE's and also simply trying this:
SELECT *, #TotalRecord = COUNT(1)
FROM dbo
But that doesn't work either.
Here is my stored procedure, with most of the stuff pulled out:
ALTER PROCEDURE [dbo].[SearchErrorReports]
#FundNumber varchar(50) = null,
#ProfitSelected bit = 0,
#SortColumnName varchar(30) = null,
#SortDirection varchar(10) = null,
#StartIndex int = 0,
#RecordPerPage int = null,
#TotalRecord INT = 0 OUTPUT --NEED TO SET THIS BEFORE OFFSET!
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM
(SELECT *
FROM dbo.View
WHERE (#ProfitSelected = 1 AND Profit = 1)) AS ERP
WHERE
((#FundNumber IS NULL OR #FundNumber = '')
OR (ERP.FundNumber LIKE '%' + #FundNumber + '%'))
ORDER BY
CASE
WHEN #SortColumnName = 'FundNumber' AND #SortDirection = 'asc'
THEN ERP.FundNumber
END ASC,
CASE
WHEN #SortColumnName = 'FundNumber' AND #SortDirection = 'desc'
THEN ERP.FundNumber
END DESC
OFFSET #StartIndex ROWS
FETCH NEXT #RecordPerPage ROWS ONLY
Thank you in advance!
You could try something like this:
create a CTE that gets the data you want to return
include a COUNT(*) OVER() in there to get the total count of rows
return just a subset (based on your OFFSET .. FETCH NEXT) from the CTE
So your code would look something along those lines:
-- CTE definition - call it whatever you like
WITH BaseData AS
(
SELECT
-- select all the relevant columns you need
p.ProductID,
p.ProductName,
-- using COUNT(*) OVER() returns the total count over all rows
TotalCount = COUNT(*) OVER()
FROM
dbo.Products p
)
-- now select from the CTE - using OFFSET/FETCH NEXT, get only those rows you
-- want - but the "TotalCount" column still contains the total count - before
-- the OFFSET/FETCH
SELECT *
FROM BaseData
ORDER BY ProductID
OFFSET 20 ROWS FETCH NEXT 15 ROWS ONLY
As a habit, I prefer non-null entries before possible null. I did not reference those in my response below, and limited a working example to just the two inputs you are most concerned with.
I believe there could be some more clean ways to apply your local variables to filter the query results without having to perform an offset. You could return to a temp table or a permanent usage table that cleans itself up and use IDs that aren't returned as a way to set pages. Smoother, with less fuss.
However, I understand that isn't always feasible, and I become frustrated myself with those attempting to solve your use case for you without attempting to answer the question. Quite often there are multiple ways to tackle any issue. Your job is to decide which one is best in your scenario. Our job is to help you figure out the script.
With that said, here's a potential solution using dynamic SQL.
I'm a huge believer in dynamic SQL, and use it extensively for user based table control and ease of ETL mapping control.
use TestCatalog;
set nocount on;
--Builds a temp table, just for test purposes
drop table if exists ##TestOffset;
create table ##TestOffset
(
Id int identity(1,1)
, RandomNumber decimal (10,7)
);
--Inserts 1000 random numbers between 0 and 100
while (select count(*) from ##TestOffset) < 1000
begin
insert into ##TestOffset
(RandomNumber)
values
(RAND()*100)
end;
set nocount off;
go
create procedure dbo.TestOffsetProc
#StartIndex int = null --I'll reference this like a page number below
, #RecordsPerPage int = null
as
begin
declare #MaxRows int = 30; --your front end will probably manage this, but don't trust it. I personally would store this on a table against each display so it can also be returned dynamically with less manual intrusion to this procedure.
declare #FirstRow int;
--Quick entry to ensure your record count returned doesn't excede max allowed.
if #RecordsPerPage is null or #RecordsPerPage > #MaxRows
begin
set #RecordsPerPage = #MaxRows
end;
--Same here, making sure not to return NULL to your dynamic statement. If null is returned from any variable, the entire statement will become null.
if #StartIndex is null
begin
set #StartIndex = 0
end;
set #FirstRow = #StartIndex * #RecordsPerPage
declare #Sql nvarchar(2000) = 'select
tos.*
from ##TestOffset as tos
order by tos.RandomNumber desc
offset ' + convert(nvarchar,#FirstRow) + ' rows
fetch next ' + convert(nvarchar,#RecordsPerPage) + ' rows only'
exec (#Sql);
end
go
exec dbo.TestOffsetProc;
drop table ##TestOffset;
drop procedure dbo.TestOffsetProc;

Trying to query and then update a table in one transaction

Spec for the stored procedure is:
To select and return the Id from my table tb_r12028dxi_SandpitConsoleProofClient (order is not important just the top 1 found will do) and as soon as I've selected that record it needs to be marked 'P' so that it does not get selected again.
Here is the stored procedure:
ALTER PROCEDURE [dbo].[r12028dxi_SandpitConsoleProofSweep]
#myId INT OUTPUT
AS
/*
DECLARE #X INT
EXECUTE [xxx].[dbo].[r12028dxi_SandpitConsoleProofSweep] #X OUTPUT
SELECT #X
*/
DECLARE #NumQueue INT = (
SELECT [cnt] = COUNT(*)
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient
WHERE [Status] IS NULL
);
IF #NumQueue > 0
BEGIN
BEGIN TRANSACTION;
DECLARE #foundID INT = (SELECT TOP 1 Id FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient WHERE [Status] IS NULL);
UPDATE x
SET x.[Status] = 'P'
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient x
WHERE x.Id = #foundID
SET #myId = #foundID;
RETURN;
COMMIT TRANSACTION;
END;
GO
It is returning the error message:
Transaction count after EXECUTE indicates a mismatching number of
BEGIN and COMMIT statements. Previous count = 0, current count = 1.
I've just added the Update script and the BEGIN TRANSACTION; and COMMIT TRANSACTION; before that it worked fine when it looked like the following...
ALTER PROCEDURE [dbo].[r12028dxi_SandpitConsoleProofSweep]
#myId INT OUTPUT
AS
/*
DECLARE #X INT
EXECUTE [xxx].[dbo].[r12028dxi_SandpitConsoleProofSweep] #X OUTPUT
SELECT #X
*/
DECLARE #NumQueue INT = (
SELECT [cnt] = COUNT(*)
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient
WHERE [Status] IS NULL
);
IF #NumQueue > 0
BEGIN
SELECT TOP 1 #myId = Id FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient;
RETURN;
END;
GO
I added the BEGIN TRANSACTION; / COMMIT TRANSACTION; because I wanted to ensure that the data gets read into the output variable AND that the UPDATE happens. Should I just leave out this section of the procedure?
You have "RETURN;" before "COMMIT TRANSACTION;" which means "COMMIT TRANSACTION;" is never executed.
Give that you want:
and as soon as I've selected that record it needs to be marked 'P' so
that it does not get selected again.
you can achieve that in a single statment (and not in a transaction)
ALTER PROCEDURE [dbo].[r12028dxi_SandpitConsoleProofSweep]
#myId INT OUTPUT
AS
BEGIN
UPDATE x
SET x.[Status] = 'P',
#myID = x.ID
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient x
/* a sample join to get your single row in an update statement */
WHERE x.ID = (SELECT MIN(ID)
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient sub
WHERE ISNULL(sub.[Status], '') != 'P')
END
Note: When dealing with concurrent processing (ie: two threads trying to select from a single queue) it's more about the locking behavior than doing it inside a single transaction.
As an alternative to a perfectly reasonable suggestion by #Andrew Bickerton, you could also use a CTE and the ROW_NUMBER() function, like this:
WITH ranked AS (
SELECT
Id,
[Status],
rnk = ROW_NUMBER() OVER (ORDER BY Id)
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient
WHERE [Status] IS NULL
)
UPDATE ranked
SET
[Status] = 'P',
#myId = Id
WHERE rnk = 1
;
The ROW_NUMBER() function assigns rankings to all rows where [Status] IS NULL, which allows you to update only a specific one.
The use of the CTE as the direct target of the UPDATE statement is absolutely legitimate in this case, as the CTE only pulls rows from one table. (This is similar to the use of views in UPDATE statements.)

Sql Optimization on advertising system

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.