Can we merge 2 SQL with same logic with just marginally difference? - sql

I have 2 SQLs,
SELECT #AlreadyPerformedActionsPerDay = COUNT(*)
FROM UserRewardActions URA
INNER JOIN UserRewardActionTypes URAT ON URAT.ID = URA.UserRewardActionTypeID
WHERE URA.UserID = #UserID
AND (
#UserRewardActionTypeID = 1 -- Because the register action is not per-date-wise(only allows once in lifetime)
OR CreationDateInUtc BETWEEN CAST(GETUTCDATE() AS DATE) AND DATEADD(DAY, 1, CAST(GETUTCDATE() AS DATE)) -- Inside Current Date
);
SELECT #AlreadyRedeemedTheCurrentAction = 1
FROM UserRewardActions URA
INNER JOIN UserRewardActionTypes URAT ON URAT.ID = URA.UserRewardActionTypeID
WHERE URA.UserID = #UserID
AND CreationDateInUtc BETWEEN CAST(GETUTCDATE() AS DATE) AND DATEADD(DAY, 1, CAST(GETUTCDATE() AS DATE)) -- Inside Current Date
AND URL IS NOT NULL
AND URL = #Url;
The fist SQL is the grabbing total number of user actions today. Second, is checking whether the current url is already used by the same user. This is fine. But I am looking to merge this query for performance issue.

well, after reformatting your queries as below. I can see that they have different criteria.
DECLARE #from DateTime;
DECLARE #to DateTime;
SET #from = cast(getutcdate() as Date);
SET #to = dateadd(DAY, 1, #from));
SELECT
#AlreadyPerformedActionsPerDay = COUNT(*)
FROM
UserRewardActions URA
JOIN
UserRewardActionTypes URAT
ON URAT.ID = URA.UserRewardActionTypeID
WHERE
URA.UserID = #UserID
AND
(
#UserRewardActionTypeID = 1
OR
CreationDateInUtc BETWEEN #from AND #to
);
SELECT
#AlreadyRedeemedTheCurrentAction = 1
FROM
UserRewardActions URA
JOIN
UserRewardActionTypes URAT
ON URAT.ID = URA.UserRewardActionTypeID
WHERE
URA.UserID = #UserID
AND
CreationDateInUtc BETWEEN #from AND #to
AND
URL IS NOT NULL
AND
URL = #Url;

Related

How to resolve execution timeout expired issue occuring in a stored procedure

Facing timeout expired issue in a code developed.
Shared below is the stored procedure where timeout occurs.
Purpose of the code : Dates being passed from frontend (using a forloop in Windows application vb.net code) for 1 million cases for which date difference needs to be calculated basis the date received.
create procedure sp_getdatediff
#strd1 date = null,
#strd2 date = null,
#strd3 date = null,
#strd4 date = null,
#strd5 date = null,
#strd6 date = null,
#strd7 date = null,
#strd8 date = null,
#strd9 date = null,
#strd10 date = null,
#strd11 date = null
as
begin
declare #vardatediff1 int
declare #vardatediff2 int
declare #vardatediff3 int
declare #vardatediff4 int
declare #vardatediff5 int
set #vardatediff1 = [fn_getdiff](#strd1,#strd2,#strd3) ----- input parameters are dates passed from frontend
set #vardatediff2 = [fn_getdiff](#strd2,#strd4,#strd5)
set #vardatediff3 = [fn_getdiff](#strd4,#strd5,#strd6)
set #vardatediff4 = [fn_getdiff](#strd5,#strd7,#strd8)
set #vardatediff5 = [fn_getdiff](#strd9,#strd10,#strd11)
update tbl_Scheduler set col_dif1 = #vardatediff1 , col_dif2 = #vardatediff2 ,
col_dif3 = #vardatediff3 , col_dif4 = #vardatediff4 , col_dif5 = #vardatediff5
where id = #id
end
Function code :
create function [fn_getdiff]
(
#startdate date = null,
#enddate date = null,
#ccode varchar(10) = null
)
returns integer
as
begin
declare #count integer
declare #tdaycount integer
if (#startdate is null or #startdate = '')
begin
set #count = 0
end
else if (#enddate is null or #enddate = '')
begin
set #count = 0
end
else
begin
select #tdaycount = count(distinct(convert(date,tdays))) from tbl_holidays with (nolock) where (convert(date,tdays,105) >= convert(date,#startdate,105))
and (convert(date,tdays,105) <= convert(date,#enddate,105)) and tcode in (select id from tbl_code with (nolock) where id = #ccode)
select #count = datediff(#startdate,#enddate)
set #count = #count - #tdaycount
end
return #count
end
Is there optimization required in this code to eliminate timeout issue? How can same be done?
The use of IN in your function can be replaced by a JOIN.
Your query :
SELECT #tdaycount = COUNT(DISTINCT(CONVERT(DATE, tdays)))
FROM tbl_holidays WITH(NOLOCK)
WHERE (CONVERT(DATE, tdays, 105) >= CONVERT(DATE, #startdate, 105))
AND (CONVERT(DATE, tdays, 105) <= CONVERT(DATE, #enddate, 105))
AND tcode IN (SELECT id
FROM tbl_code WITH(NOLOCK)
WHERE id = #ccode);
The rewriting :
SELECT #tdaycount = COUNT(DISTINCT(CONVERT(DATE, tdays)))
FROM tbl_holidays AS h
JOIN tbl_code AS c
ON h.tcode = c.id
WHERE (CONVERT(DATE, tdays, 105) >= CONVERT(DATE, #startdate, 105))
AND (CONVERT(DATE, tdays, 105) <= CONVERT(DATE, #enddate, 105))
AND c.id = #ccode;
When A = B and B = C the A = C, so :
...
ON h.tcode = c.id
WHERE ...
AND c.id = #ccode;
The rewriting (2) :
SELECT #tdaycount = COUNT(DISTINCT(CONVERT(DATE, tdays)))
FROM tbl_holidays AS h
WHERE (CONVERT(DATE, tdays, 105) >= CONVERT(DATE, #startdate, 105))
AND (CONVERT(DATE, tdays, 105) <= CONVERT(DATE, #enddate, 105))
AND tbl.tcode = #ccode;
You do not respects rules of posting... So we cannot helps you so much.
Put the description of your tables and indexes as DDL SQL Statement please.
Probably the CONVERTs are useless...
Your function contains this bit of SQL.
select #tdaycount = count(distinct(convert(date,tdays)))
from tbl_holidays with (nolock)
where (convert(date,tdays,105) >= convert(date,#startdate,105))
and (convert(date,tdays,105) <= convert(date,#enddate,105))
and tcode in (select id from tbl_code with (nolock) where id = #ccode)
Date-conversion error
CONVERT(DATE, #enddate, 105) produces a date of the form 31-12-2006. I suspect it makes no sense to use <= comparisons on that format because 30-12-2007 will come before 31-12-2006. Your data is already in the DATE format, so you can compare it without converting it.
Sargability performance antipattern
It contains a couple of performance antipatterns like this:
WHERE function(column) >= function(constant)
This is an antipattern because it's a non-sargable filter. What you want is this sort of pattern
WHERE column >= constant
or
WHERE column >= function(constant)
combined with an apppropriate index on the table.
Index
A multicolumn index on your tbl_holidays table on (tcode, tdays) will allow the server to satisfy your queries by range-scanning that index. Try this.
CREATE INDEX tcode_tdays ON tbl_holidays (tcode, tdays);
Rewrite
SELECT #tdaycount = COUNT(DISTINCT tdays)
FROM tbl_holidays WITH (NOLOCK)
WHERE tdays >= #startdate
AND tdays <= #enddate
AND tcode in (SELECT id
FROM tbl_code WITH (NOLOCK)
WHERE id = #ccode)
I don't know how much time this will save. But it will save some.
Default timeout too short
.NET's SQL classes are set up with 30-second timeouts. If you're processing millions (lakhs upon lakhs) of rows that's not long enough. Try setting the timeout to ten minutes or whatever duration makes sense.
Dim command = conn.CreateCommand()
...
command.CommandTimeout = 600 ' ten minutes

How can i improve performance of a scalar value function used in join clause?

I have developed a query to process the data for a report. I had to get only one record from the history table for the selected month.
For example: These are 4 vehicles. Vehicle "1" is modified four times in November 2018 on the following dates and prices:
1). 05 November 2018 with price 1000
2). 15 November 2018 with price 5000
3). 25 November 2018 with price 8000
Now i wanted the only one record on last modified date (25 November).
I developed a database scalar function and used that function in join clause which worked perfectly with less data. Now we have some records (not too much just 100 records in history table) and the report is suffering from speed/performance issues. I created indexes and removed sub-queries but still couldn't be much helpful.
Please see my tried scripts:
declare #pIntFranchiseId int = 16179;
declare #pDtFrom date = '2018-12-01';
declare #pDtTo date = '2018-12-28';
SELECT ba.[Id],BKAgrHistory.[BookingStart],BKAgrHistory.[BookingEnd],BKAgrHistory.[StartTime],BKAgrHistory.[EndTime]
,ba.[FranchiseId],BKAgrHistory.[FleetId],[BKRenterId],[IsReportGenerated],[StepsCompleted]
,ba.[BookingId],ba.[ReservationId],ba.[RentalAgreementId],[VehicleUsedFor],[OperatorLicence]
,[ClearanceCode],[ExcessAmount],[ClearanceCodeAdditionalDriv],[ExcessAmountAdditionalDriv],[IsClearanceVerified]
,[BKDiscount],BKAgrHistory.[AgreementStatus],[IsTravelingOutsideUk],[OtherCountries],[RentalType],[RenterType],[RenterCompanyType]
,[IsVehicleDelivered],[IsReturnedOnSameAddress],[PickUpAddress],[ReturnAddress],[OtherReturnAddress],[TariffId]
,[SpecialRateId],[IsChargeShortWeekend],[IsChargeLongWeekend],[ChargeHalfDay],[IsDamageProvided],[IsRenterAgreement]
,[BKIsOwnInsurance],[ExpectedMileage],BKAgrHistory.[TotalHireCharge],[BKFreeMiles],[BKExpensePerMile],[BKCollisionDamage],[BKDamage]
,[BKTheft],[BKOverHeight],[BKBookingNotes],[BKFleetCurrentMileage],[BKMileageOut],[BKMileageIn],[BKDamageOut],[BKDamageIn],[Condition]
,[AppliedRates],[Paid],[OnceInvoiced],[InterimInvoiceCount],[PBNumber],[Excess],[StatusId],[CreatedOn],[ModifiedOn],BKAgrHistory.[BookingDays]
,[VATPercentage],[BkOpeningHrsSurcharge],[DrivenMileage],[ParentRAId],[NetTotal],[VatTotal],[GrossTotal],[GrandInsurableRevenue]
,[GrandTotalWithoutExtra],[GrandInsurableRevWithoutExtra],[TariffNetPrice],[OnceMailed],[LastBookingEnd],[LastEndTime],[BkOpeningHrsSurchargeNet]
,[BkOpeningHrsSurchargeVat],BKAgrHistory.[DailyHireCharge],[BkNetExtraDrivenMilesExcess],[BkExtraDrivenMilesExcessVAT],[BkTotalExtraDrivenMilesExcess],[ExcessMilesDays]
,[BkWebWeekendSurcharge],BKAgrHistory.[InsertedUserId],BKAgrHistory.[UpdateUserId],BKAgrHistory.[InsertedDate],BKAgrHistory.[UpdatedDate],BKAgrHistory.[InsertedUserName],BKAgrHistory.[UpdatedUserName],
[SecurityDeposit],[SecurityDepositRuleId]
FROM [dbo].[BookingAgreements] ba
join BookingAgreementAmountHistory BKAgrHistory on BKAgrHistory.Id = dbo.[GetAgreementAmtHistoryId](#pIntFranchiseId,ba.Id,#pDtFrom,#pDtTo)
where
(
DATEADD(day, DATEDIFF(day, 0,BKAgrHistory.BookingStart), 0) <= DATEADD(day, DATEDIFF(day, 0, #pDtFrom), 0) OR
DATEADD(day, DATEDIFF(day, 0,BKAgrHistory.BookingEnd), 0) <= DATEADD(day, DATEDIFF(day, 0, #pDtFrom), 0)
)
and ba.FranchiseId=#pIntFranchiseId and StatusId=1
and ISNULL(BKAgrHistory.AgreementStatus,0) not in (0,2,6,7)
and BKAgrHistory.FleetId in (
select f.Id from Fleets f
join FleetsHistory fleetHis on fleetHis.Id = dbo.[GetVehicleHistoryById](#pIntFranchiseId,BKAgrHistory.FleetId,#pDtFrom,#pDtTo)
where fleetHis.GhostVehicle = 0 and fleetHis.CoreFleet = 1 and isnull(fleetHis.StatusId,0) in (1,4) and
f.Franchise_Id=#pIntFranchiseId and DATEADD(day, DATEDIFF(day, 0, fleetHis.PurchaseDate), 0) <= DATEADD(day, DATEDIFF(day, 0, #pDtTo), 0));
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[GetVehicleHistoryById](#franchiseId int = NULL,#fleetId int = NULL, #fromDate Date = NULL, #toDate Date = NULL)
RETURNS int
AS BEGIN
Declare #returnId int = 0;
Select top 1 #returnId = isnull(Id,0) from FleetsHistory
where TransactionDate =
(Select max(TransactionDate)
from FleetsHistory fh
where fh.Franchise_Id = #franchiseId and fh.Fleet_Id = #fleetId
--group by AgreementId
and (DATEADD(day, DATEDIFF(day, 0, TransactionDate),0)) <= DATEADD(day, DATEDIFF(day, 0, #toDate),0))
order by Id desc;
Return #returnId;
END
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[GetAgreementAmtHistoryId](#franchiseId int = NULL,#agreementId int = NULL, #fromDate Date = NULL, #toDate Date = NULL)
RETURNS int
AS BEGIN
Declare #returnId int = 0;
Select top 1 #returnId = isnull(Id,0) from BookingAgreementAmountHistory
where TransactionDate =
(Select max(TransactionDate)
from BookingAgreementAmountHistory
where FranchiseId = #franchiseId and AgreementId = #agreementId
--group by AgreementId
and (DATEADD(day, DATEDIFF(day, 0, TransactionDate),0)) <= DATEADD(day, DATEDIFF(day, 0, #toDate),0))
order by Id desc;
Return #returnId;
END
Whatever else functions might do, they do not improve the performance of SQL queries.
I would suggest using apply with the right arguments. It is a little hard to see what you really want, given the proliferation of queries in the question, the lack of sample data and desired results, and the disconnect between your simple question and the actual query. But the idea would be:
select ba.*, baah.*
from [dbo].[BookingAgreements] ba outer apply
(select top (1) baah.*
from BookingAgreementAmountHistory baah
where baah.Id = ba.FranchiseId and
baah.AgreementStatus not in (2, 6, 7) and -- this takes care of NULL values
baah.<date> >= #pDtFrom and
baah.<date> <= #pDtTo
order by baah.<date> desc
) baah
where ba.FranchiseId = #pIntFranchiseId;
<date> is for the modification date that you mention at the beginning of the question. It is quite unclear which date this actually refers to.

Sql joins returning Null Results and not accepting conditions

I have 4 tables that I am trying to get data from yet my join statement returns null. I have tried full, left and right joins with no success. Below I have provided my code and the diagram, the table that returns null values is the ChannelData table. I am trying to sum the Vvalue from this table, when i add the condition that i commented out below i.e.between start and end date` my query return no results. I know there must be a problem with my joins but I have tried everything. This table is not normalized properly and I have to use it as is:
DECLARE #bp varchar(4)
DECLARE #priority varchar(2)
DECLARE #startDate datetime
DECLARE #endDate datetime
SET #bp = 1710
SET #priority = 2
SET #endDate = (SELECT EndDate FROM BillingPeriod WHERE BillingPeriodClass_ID = 1 AND CODE = #BP)
SET #startDate = (SELECT EndDate FROM BillingPeriod WHERE BillingPeriodClass_ID = 1 AND CODE = #BP -1 )
SET #startDate = dateadd(minute, 1, #startDate)
SELECT CGS.Description, SUM(CD.Vvalue) AS P_Val, COUNT(CD.VValue) AS P_Rec, DI.Margin
FROM CGS AS CGS
FULL JOIN ChannelParameter AS CP ON CP.ID = CGS.ID
FULL JOIN ChannelData AS CD ON CP.ID = CD.ID
FULL JOIN DataIntegrity AS DI ON DI.CGS_ID_1 = CGS.ID OR DI.CGS_ID_2 = CGS.ID
WHERE --CD.DDate BETWEEN #startDate AND #endDate AND
DI.Priority = #priority
group by CGS.Description, DI.Margin
Click this for Query Result
Please see below:
Channel Data Table
Channel Parameter Table
I have attached pictures as links: also here is what i mean by the table not being normalised properly they both have CGS.ID as a primrary and foreign key but this is actually a primary key in the cgs table the problem is when i try and join the DI table i dont get results from all tables:
DECLARE #bp varchar(4)
DECLARE #priority varchar(2)
DECLARE #startDate datetime
DECLARE #endDate datetime
SET #bp = 1710
SET #priority = 2
SET #endDate = (SELECT EndDate FROM BillingPeriod WHERE BillingPeriodClass_ID = 1 AND CODE = #BP)
SET #startDate = (SELECT EndDate FROM BillingPeriod WHERE BillingPeriodClass_ID = 1 AND CODE = #BP -1 )
SET #startDate = dateadd(minute, 1, #startDate)
SELECT CGS.Description, SUM(CD.Vvalue) AS P_Val, COUNT(CD.VValue) AS P_Rec
FROM CGS AS CGS
FULL JOIN ChannelParameter AS CP ON CP.ID = CGS.ID
FULL JOIN ChannelData AS CD ON CP.ID = CD.ID
WHERE CD.DDate BETWEEN #startDate AND #endDate and CGS.ID = 88
group by CGS.Description
RESULT :
Description P_Val P_Rec
EP 308 12 CONTROL TRF FEEDER NO 1 - Q Exp, 4514.37, 1488
The problem is pretty clear that there is no matched record for CD.DDate BETWEEN #startDate AND #endDate condition. And I suspected that #startDate variable might be null because of returning no record for CODE = #BP -1 condition. So, would you try to change this line;
SET #startDate = (SELECT top 1 EndDate FROM BillingPeriod
WHERE BillingPeriodClass_ID = 1 AND CODE < #BP order by CODE desc)

SQL Server - IF EXISTS executes both the TRUE branch and the FALSE branch

I need to check if there is an existing record in a table (for an employee evaluation). If such a record exists, the query has to take the latest entry for that employee and calculate the next start date and end date for the next evaluation. If such a record doesn't exist, the query has to apply a different logic and calculate a new start date and end date for the first evaluation (counting back from the current date).
In order to do so, I used the IF EXISTS expression in SQL Server, but it seemed that if I kept the same name for the start date and end date variables, SQL was overwriting them (so was executing the branches sequentially, not picking one or the other). So I declared 2 sets of variables to overcome this issue. However, SQL Server seems to keep on calculating a start date and end date for each case (EXISTS and NOT EXISTS). I know this because I asked it to PRINT the 4 variables, and all were printed with different values.
Here is the declaration of my (now) 4 variables:
IF EXISTS (SELECT main.[login]
FROM dbo.EVALUATIONS_TABLE eval
WHERE eval.[login] = #login)
SET #startdate = DATEADD(day, 1, (SELECT END_DATE FROM (SELECT TOP 1 ID, END_DATE FROM dbo.EVALUATIONS_TABLE WHERE [LOGIN] = #login) lastscorecard));
SET #enddate = DATEADD(day, 10, #startdate);
ELSE
SET #startdate_new = DATEADD(day, -1, GETDATE());
SET #enddate_new = DATEADD(day, -10, #startdate_new);
I tried to insert the one or the other set of variables in the output table variable, but I still had to rely on the same IF EXISTS logic, which is unreliable as we saw above.
What is the reason why SQL Server gets so confused? I am open to rethinking my entire logic so as to avoid such a mess :-)
You need to use BEGIN / END because otherwise only the first statement after your IF will be run:
IF EXISTS (SELECT main.[login]
FROM dbo.EVALUATIONS_TABLE eval
WHERE eval.[login] = #login) BEGIN
SET #startdate = DATEADD(day, 1, (SELECT END_DATE FROM (SELECT TOP 1 ID, END_DATE FROM dbo.EVALUATIONS_TABLE WHERE [LOGIN] = #login) lastscorecard));
SET #enddate = DATEADD(day, 10, #startdate);
END ELSE BEGIN
SET #startdate_new = DATEADD(day, -1, GETDATE());
SET #enddate_new = DATEADD(day, -10, #startdate_new);
END
The "obvious" solution is to use begin/end for the if blocks. In fact, I would recommend always using begin/end with if to prevent errors of this type.
But, you can get rid of the if altogether:
SELECT #startdate = COALESCE(DATEADD(day, 1, MAX(END_DATE)),
DATEADD(day, -1 GETDATE)
)
FROM (SELECT TOP 1 et.*
FROM dbo.EVALUATIONS_TABLE et.
WHERE [LOGIN] = #login
) l;
SELECT #enddate = DATEADD(day, -10, #startdate);
Iulia Mihet,
You should always use BEGIN and END for this type of queries. Especially when you are using IF EXISTS query which is conditional.
Your code should look something like this :
IF EXISTS (SELECT main.[login]
FROM dbo.EVALUATIONS_TABLE eval
WHERE eval.[login] = #login)
BEGIN
SET #startdate = DATEADD(day, 1, (SELECT END_DATE FROM (SELECT TOP 1 ID, END_DATE FROM dbo.EVALUATIONS_TABLE WHERE [LOGIN] = #login) lastscorecard));
SET #enddate = DATEADD(day, 10, #startdate);
END
ELSE
BEGIN
SET #startdate_new = DATEADD(day, -1, GETDATE());
SET #enddate_new = DATEADD(day, -10, #startdate_new);
END
Hope It helps
You can use same variables. You have use BEGIN and END.
Use below query.
IF EXISTS (SELECT main.[login]
FROM dbo.EVALUATIONS_TABLE eval
WHERE eval.[login] = #login)
BEGIN
SET #startdate = DATEADD(day, 1, (SELECT END_DATE FROM (SELECT TOP 1 ID, END_DATE FROM dbo.EVALUATIONS_TABLE WHERE [LOGIN] = #login) lastscorecard));
SET #enddate = DATEADD(day, 10, #startdate);
END
ELSE
BEGIN
SET #startdate = DATEADD(day, -1, GETDATE());
SET #enddate = DATEADD(day, -10, #startdate_new);
END

Which query will be better in terms of perfomance SQL server

I have written two queries and I need help to know which one will be better in term of performance. Both do the same thing. Please also advise if you have better a way of writing the query
Query 1
DECLARE #TotalAvailedLateNightGeneral INT
;WITH CTE (TotalDaysAppliedFor) AS (
SELECT SUM(TotalDaysAppliedFor) AS TotalDaysAppliedFor
FROM [CURFEW_RLX_REQUEST] WITH (NOLOCK)
GROUP BY StaffSeqId, RequestTypeId, MONTH(PermissionRequiredFrom), StatusId, IsActive
HAVING StaffSeqId = 41130
AND RequestTypeId = 3
AND MONTH(PermissionRequiredFrom) = MONTH('2016-03-30 00:00:00.000')
AND StatusId <> 111
AND IsActive = 1
)
SELECT #TotalAvailedLateNightGeneral = SUM(TotalDaysAppliedFor)
FROM CTE
SELECT #TotalAvailedLateNightGeneral
QUERY 2
SELECT SUM(TotalDaysAppliedFor) AS TotalDaysAppliedFor
FROM [CURFEW_RLX_REQUEST] WITH (NOLOCK)
--GROUP BY StaffSeqId,RequestTypeId,MONTH(PermissionRequiredFrom),StatusId,IsActive
WHERE StaffSeqId = 41130
AND RequestTypeId = 3
AND MONTH(PermissionRequiredFrom) = MONTH('2016-03-30 00:00:00.000')
AND StatusId <> 111
AND IsActive = 1
DECLARE #StartDate DATETIME = '2016-03-01'
DECLARE #EndDate DATETIME = '2016-04-01'
SELECT TotalDaysAppliedFor = SUM(TotalDaysAppliedFor)
FROM dbo.QAG_GEMS_CURFEW_RLX_REQUEST
WHERE StaffSeqId = 41130
AND RequestTypeId = 3
AND PermissionRequiredFrom >= #StartDate
AND PermissionRequiredFrom < #EndDate
AND StatusId != 111
AND IsActive = 1