Trying to solve this puzzle: the query works very slow and as I could understand, the problem is the join to the [sys].[time_zone_info] table - it creates nested loops with the specific warning - "No Join Predicate"
I read numerous articles trying to identify the issue but I couldn't.
I tried to use OPTION (USE HINT ('DISABLE_OPTIMIZER_ROWGOAL')) hint and it worked great.
It's also not a UDF column (http://techblog.elish.net/2010/10/nested-loops-join-no-join-predicate.html).
declare #StartDate date = '2022-12-08'
, #EndDate date = '2022-12-08'
, #OnlyActivated int = 0
, #partner int = 32558
, #timezone varchar(50) = 'Pacific Standard Time'
;WITH Result
AS
(SELECT
[Program] = a.ProgramName
,[Account #] = a.accountID
,[Account Name] = a.CustomerName
,[Purchase ID] = r.PURCHASEID
,[Purchase Date] = CAST((r.PURCHASE_DATE AT TIME ZONE ISNULL(tzi.[name], 'Pacific Standard Time')) AS DATETIMEOFFSET)
,[Activation Date] = CAST((r.ACTIVATION_DATE AT TIME ZONE ISNULL(tzi.[name], 'Pacific Standard Time')) AS DATETIMEOFFSET)
,[Cancellation Date] = CAST((r.CANCELLATION_DATE AT TIME ZONE ISNULL(tzi.[name], 'Pacific Standard Time')) AS DATETIMEOFFSET)
,[Product Description] = r.DESCR
,[Product Code] = r.PartNumber
,[Product Type] = r.ChargeType
,[Currency] = r.CURRENCY
,[Customer Name] = a.CustomerName
,[Email of Login User] = r.EmailOfLoginUser
,[Creation Date] = a.dateOfPurchase
,[Account Plan] = a.PlanName
FROM [Partner].[OrderReport] r
INNER JOIN [Partner].[DimAccountsPartner] a
ON r.AccountKey = a.AccountKey
LEFT JOIN [sys].[time_zone_info] tzi
ON tzi.[name] = ISNULL(#timezone, 'Pacific Standard Time')
WHERE ((a.RootPLR_ID IN (34747, 37323)
AND SKU NOT LIKE 'VSHP%'
AND SKU NOT LIKE 'VUSG%'
AND SKU NOT LIKE 'VOPT%'
AND SKU NOT LIKE 'VUSR00000%'
AND SKU NOT LIKE 'VUSR000101')
OR a.RootPLR_ID NOT IN (34747, 37323))
AND ((#OnlyActivated = 0
AND ((DATEADD(HOUR, CAST(LEFT(tzi.current_utc_offset, 3) AS INT), r.PURCHASE_DATE) BETWEEN #StartDate AND DATEADD(DAY, 1, #EndDate))
OR (DATEADD(HOUR, CAST(LEFT(tzi.current_utc_offset, 3) AS INT), r.Cancellation_Date) BETWEEN #StartDate AND DATEADD(DAY, 1, #EndDate))))
OR (#OnlyActivated = 1
AND DATEADD(HOUR, CAST(LEFT(tzi.current_utc_offset, 3) AS INT), r.Activation_Date) BETWEEN #StartDate AND DATEADD(DAY, 1, #EndDate)))
AND (a.PLR_ID = #partner
OR a.Parent_PLR_ID = #partner
OR a.RootPLR_ID = #partner)
AND a.[AccountFlags] <> 4
AND SKU NOT IN ('VUSG000001','VUSG000000')
)
SELECT TOP(10)
KeyColumn = ROW_NUMBER() OVER (ORDER BY (SELECT
1)
)
,[Program] = ISNULL([Program], '')
,[Account #] = ISNULL([Account #], 0)
,[Account Name] = ISNULL([Account Name], '')
,[Purchase ID] = ISNULL([Purchase ID], '')
,[Purchase Date] = ISNULL(FORMAT([Purchase Date], 'yyyy-MM-dd hh:mm:ss'), '')
,[Activation Date] = ISNULL(FORMAT([Activation Date], 'yyyy-MM-dd hh:mm:ss'), '')
,[Cancellation Date] = ISNULL(FORMAT([Cancellation Date], 'yyyy-MM-dd hh:mm:ss'), '')
,[Product Description] = ISNULL([Product Description], '')
,[Product Code] = ISNULL([Product Code], '')
,[Product Type] = ISNULL([Product Type], '')
,[Currency] = ISNULL([Currency], '')
,[Email of Login User] = ISNULL([Email of Login User], '')
,[Creation Date] = ISNULL(FORMAT([Creation Date], 'yyyy-MM-dd hh:mm:ss'), '')
,[Account Plan] = ISNULL([Account Plan], '')
FROM Result
Forgot to add Exec plan:
I would get rid of all those ISNULL's by pre-checking the passed in #timezone parameter and removing the join to the system table.
Declare #timezone_input sysname = 'Pacific Standard Time';
Declare #timezone sysname = coalesce((Select name From sys.time_zone_info Where name = #timezone_input), 'Pacific Standard Time');
If the passed in value is not a valid time zone in the table it will be defaulted to 'Pacific Standard Time'.
With that you can remove the join - and the ISNULL around the conversion to the identified time zone.
,[Purchase Date] = CAST(r.PURCHASE_DATE AT TIME ZONE #timezone AS DATETIMEOFFSET)
,[Activation Date] = CAST(r.ACTIVATION_DATE AT TIME ZONE #timezone) AS DATETIMEOFFSET)
,[Cancellation Date] = CAST(r.CANCELLATION_DATE AT TIME ZONE #timezone) AS DATETIMEOFFSET)
I would then convert the #StartDate and #EndDate parameters from the input values - to the same time zone that the dates have been stored. For example, if the passed in value is 'Eastern Standard Time' and the stored value is UTC:
SET #StartDate = #StartDate AT TIME ZONE #timezone AT TIME ZONE 'UTC';
SET #Enddate = #EndDate AT TIME ZONE #timezone AT TIME ZONE 'UTC';
And that removes the requirement to use DATEADD in the where clause.
And finally, remove FORMAT and use CONVERT instead. Format can be up to 40x slower than the equivalent CONVERT function.
Related
Start Date = 2016-03-01
End Date = 2019-02-15
I need the query/Function which should retrive the below result based on start and end date. I have a query to frame the result .But i need some query/function to retrive result set in 5 to 10 sec with performance
You mean like
SELECT * FROM table WHERE [start date]>='2016-03-01' AND [end date]<='2019-02-15'
Am I missing something? This seems too simple to ask as a question
If your problem is performance perhaps consider indexing start and end date columns, and if you're only getting a couple other values from the table include those in the index too.
CREATE INDEX IX_table_startdate_enddate
ON schema.table ([start date], [end date])
INCLUDE (othercolumn1, othercolumn2);
It means that queries like:
SELECT othercolumn1, othercolumn2 FROM table
WHERE [start date]>='2016-03-01' AND [end date]<='2019-02-15'
Can be answered from the index without having to connect the index to the table to pull the desired data
If you still can't wrong enough out of it after that perhaps you have a design flaw in your app; pilling a db for a massive amount of data in a tight interval could be solved another way like only emitting events when data actually changes
It's just occurred to me you're probably looking for a query to generate that result set from nothing. In sqlserver we can use a recursive cte:
DECLARE #dateFrom DATE = '2016-03-01'
DECLARE #dateTo DATE = '2019-02-15'
with d as (
SELECT #datefrom as m
UNION ALL
SELECT DATEADD(MONTH,1,reqDate)
FROM d
WHERE DATEADD(MONTH,1,reqDate) <= #dateto
),
SELECT
CONCAT(
DATENAME(MONTH, #dateFrom), ' ',
CASE WHEN MONTH(d.m) < MONTH(#dateFrom) THEN YEAR(d.m) - 1 ELSE YEAR(d.m) END, '-',
DATENAME(MONTH, #dateTo), ' ',
CASE WHEN MONTH(d.m) < MONTH(#dateFrom) THEN YEAR(d.m) ELSE YEAR(d.m) +1 END
) as range,
MONTH(d.m) as month,
d.m as startdate,--do not use spaces in column names
CASE WHEN #dateTo < EOMONTH(d.m) then #dateTo ELSE EOMONTH(d.m) END as enddate --or dateadd 1 month then dateadd -1 day if you don't have eomonth
FROM d
OPTION(MAXRECURSION 0);
Same format with your result try it.
DECLARE #StartDate DATETIME, #EndDate DATETIME
SET #StartDate = '2016-03-01'
SET #EndDate = '2038-02-15'
;WITH CTEs AS
(
SELECT #StartDate as [Start Date]
UNION ALL
SELECT DATEADD(MONTH,1,[Start Date])
FROM CTEs WHERE DATEADD(MONTH,1,[Start Date]) <= #EndDate
)
SELECT
CONCAT(
DATENAME(MONTH, #StartDate), ' ',
CASE WHEN MONTH([Start Date]) < MONTH(#StartDate) THEN YEAR([Start Date]) - 1 ELSE YEAR([Start Date]) END, '-',
DATENAME(MONTH, #EndDate), ' ',
CASE WHEN MONTH([Start Date]) < MONTH(#StartDate) THEN YEAR([Start Date]) ELSE YEAR([Start Date]) + 1 END
) AS [Range],
MONTH([Start Date]) AS [Month],
CONVERT(VARCHAR(10),[Start Date],101) AS [Start Date],
CONVERT(VARCHAR(10),(CASE WHEN [Start Date] <> DATEFROMPARTS(YEAR(#EndDate),MONTH(#EndDate),1)
THEN EOMONTH([Start Date])
ELSE #EndDate
END),101) AS [End Date]
FROM CTEs
OPTION(MAXRECURSION 0);
I need to insert a range of dates into a table, along with corresponding column data. My table's earliest date stops at 2017-3-16, but I need to add dates going back to 2016-1-1. See screenshot below for reference:
I'm sure I can figure out how to attribute day of the week, type of day, etc. using functions such as datepart, datename, etc. What I'm not certain of is how to insert a range of dates, between 2016-1-1 and 2017-3-15.
I keep this snippet handy as it is often needed. Simply set the high and low date variables and join your date on the CalendarDate field. If date gaps are ok then INNER JOIN, otherwise LEFT JOIN.
DECLARE #StartDate DATETIME = '01/01/2015'
DECLARE #EndDate DATETIME = '12/01/2016'
;WITH OrderedDays as
(
SELECT CalendarDate = #StartDate
UNION ALL
SELECT CalendarDate = DATEADD(DAY, 1, CalendarDate)
FROM OrderedDays WHERE DATEADD (DAY, 1, CalendarDate) <= #EndDate
),
Calendar AS
(
SELECT
DayIndex = ROW_NUMBER() OVER(PARTITION BY 1 ORDER BY CalendarDate),
CalendarDate,
CalenderDayOfMonth = DATEPART(DAY, CalendarDate),
CalenderMonthOfYear = DATEPART(MONTH, CalendarDate),
CalendarYear = DATEPART(YEAR, CalendarDate),
CalenderWeekOfYear = DATEPART(WEEK, CalendarDate),
CalenderQuarterOfYear = DATEPART(QUARTER, CalendarDate),
CalenderDayOfYear = DATEPART(DAYOFYEAR, CalendarDate),
CalenderDayOfWeek = DATEPART(WEEKDAY, CalendarDate),
CalenderWeekday = DATENAME(WEEKDAY, CalendarDate)
FROM
OrderedDays
)
SELECT * FROM Calendar
OPTION (MAXRECURSION 0)
I have a search engine where the user will specify multiple conditions and based on these conditions i will return a datatable.
The problem I am facing is that the conditions are not respected and I am getting wrong results.
I have tried to test every condition alone, it is working, but when I put all the conditions together, I am getting unexpected results.
USE [Tenant Management]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[RentSearchEngine]
#sqm INT,
#category INT,
#shortRentPrice MONEY,
#longRentPrice MONEY,
#fromDate DATETIME,
#toDate DATETIME
AS
BEGIN
SET NOCOUNT ON;
SELECT
c.[Name], cate.[Channel Category], tp.type, st.Status, c.Surface,
g.GOVERNATOR + ' ' + d.District + ' ' + cit.City + ' ' as [Address],
c.[Short term Price per night] AS [Short term monthly amount],
c.[Long term price per month] AS [Long term monthly amount],
c.[Selling Price]
FROM
[dbo].[Channel] c
INNER JOIN
[dbo].[Governator] g ON c.[Governator ID] = g.ID
INNER JOIN
[dbo].[District] d ON c.[District ID] = d.ID
INNER JOIN
[dbo].[City] cit ON c.[City ID] = cit.id
INNER JOIN
[dbo].[Channel_Category] cate ON c.[Channel Category ID] = cate.ID
INNER JOIN
[dbo].[Channel_Type] tp ON c.[Channel Type] = tp.id
INNER JOIN
[dbo].[Channel_Status] st ON c.[Channel Status] = st.ID
LEFT JOIN
[dbo].[Reservations] r ON c.[ID] = r.[Channel ID]
WHERE
c.[Channel Status] = '5'
AND c.[Channel Type] = '1'
AND c.[Channel Category ID] = #category OR #category IS NULL
AND c.Surface BETWEEN #sqm * 0.85 AND #sqm * 1.15 OR #sqm IS NULL
AND c.[Long term price per month] BETWEEN #longRentPrice * 0.85
AND #longRentPrice * 1.15 OR #longRentPrice IS NULL
AND c.[Short term Price per night] BETWEEN #shortRentPrice * 0.85
AND #shortRentPrice * 1.15 OR #shortRentPrice IS NULL
AND (r.[Actual Date in] > #fromDate AND r.[Actual Date out] > #toDate)
AND (r.[Actual Date in] < #fromDate AND r.[Actual Date out] < #toDate)
END
The current result is:
fdfd Residential apatment For Rent Available 500 Mont Liban Baabda Ain El Remmaneh 1287182.00 28712.00 128712.00
When executing the stored procedure as follow:
DECLARE #return_value int
EXEC #return_value = [dbo].[RentSearchEngine]
#sqm = 40000,
#category = 1,
#shortRentPrice = 5,
#longRentPrice = 4,
#fromDate = NULL,
#toDate = NULL
SELECT 'Return Value' = #return_value
I think the key issue is the operator preference for AND & OR.
Bear in mind that AND has higher preference than OR.
In these complex conditions is a good practise to ensure the order with parenthesis.
I will write what I understand you are trying to achieve but ensure you use the parenthesis in the order you require:
where c.[Channel Status] = '5'
and c.[Channel Type] = '1'
and (c.[Channel Category ID] =#category or #category IS NULL)
and (c.Surface between #sqm*0.85 and #sqm*1.15 or #sqm IS NULL)
and (c.[Long term price per month] between #longRentPrice*0.85 and #longRentPrice*1.15 or #longRentPrice IS NULL)
and (c.[Short term Price per night] between #shortRentPrice*0.85 and #shortRentPrice*1.15 or #shortRentPrice IS NULL)
and (r.[Actual Date in] > #fromDate and r.[Actual Date out] > #toDate)
and (r.[Actual Date in] < #fromDate and r.[Actual Date out] < #toDate)
I am trying to use MAX() to select the most recent placement date within our database, and use Table_CTE so I can then select and filter between the dates desired.
BEGIN
DECLARE #Rangetill DATE, #Rangefrom DATE
SET #rangefrom = DATEADD(day, -50, GETDATE())
SET #Rangetill = DATEADD(day, -90, GETDATE());
WITH Table_CTE (Name, ID, Rangefrom, Rangetill, StatusID, Statusdate) AS
(
SELECT
PE.Personname + ' ' + PE.Surname [Name],
A.ApplicantId,
#rangefrom [Expiry warning from],
#rangetill [Expiry warning till],
A.Statusid,
selected = CASE
WHEN P.EndDate IS NOT NULL AND P.EndDate > A.StatusDate
THEN CONVERT(DATE, P.EndDate, 103)
ELSE CONVERT(DATE, A.StatusDate, 103)
END
FROM
Applicants AS A
LEFT JOIN
Person AS PE ON A.ApplicantId = PE.PersonID
LEFT JOIN
Placements AS P on A.applicantid = P.Applicantid
)
SELECT *
FROM Table_CTE
WHERE table_cte.Statusdate BETWEEN #Rangetill AND #Rangefrom
AND (Table_CTE.StatusID = 58 OR Table_CTE.statusid = 63)
ORDER BY Name DESC
END
The above selects the right information but also selects duplicate applicants with placement end dates (p.enddate) as they could have been placed more than once. The WHERE clause also limits the most recent enddate to within the range provided by the Variables and as there needs to be a log there will be multiple end dates. so my solution or idea would be to uses a max() within the Case or CTE Select. However I am not sure how to use or work with Max() in this case.
In this case I would like to check and return the Max(p.enddate) if it exists and store that in the statusdate of Table_CTE.
Is this possible and is it the best way to provide this information in a stored procedure?
In the CTE would be more efficient but this is easier
SELECT c.[Name], max(c.Statusdate)
FROM Table_CTE c
WHERE c.Statusdate Between #Rangetill and #Rangefrom
AND c.StatusID in (58, 63)
group by c.[Name]
Add the other columns on your own
Declare
#Rangetill date,
#Rangefrom date
SET #rangefrom = DATEADD(day, -50, GETDATE())
SET #Rangetill = DATEADD(day, -90, GETDATE());
With Table_CTE ( ID, Rangefrom, Rangetill, Statusdate)
AS (
Select
A.ApplicantId
, #rangefrom [Expiry warning from]
, #rangetill [Expiry warning till]
, selected = CASE
WHEN max(P.EndDate) IS NOT NULL AND max(P.EndDate) > max(A.StatusDate)
THEN max(CONVERT(DATE, P.EndDate, 103))
ELSE max(CONVERT(DATE, A.StatusDate, 103))
END
FROM Applicants AS A
LEFT JOIN Person AS PE ON A.ApplicantId = PE.PersonID
LEFT JOIN Placements AS P on A.applicantid = P.Applicantid
GROUP BY A.ApplicantId
)
SELECT
PE.PersonName + ' ' + PE.Surname [NAME]
, A.ApplicantId
, Table_CTE.ID
, Table_CTE.Statusdate
, #Rangefrom [Range from]
, #Rangetill [Range till]
FROM Table_CTE
LEFT JOIN Applicants AS A ON A.ApplicantId = Table_CTE.ID
LEFT JOIN Person as PE on PE.PersonID = A.ApplicantId
WHERE table_cte.Statusdate Between #Rangetill and #Rangefrom
AND (A.StatusID = 58 or A.statusid = 63 )
Order by PE.PersonName + ' '+ PE.Surname desc
END
Really messy way around things but I got my solution by removing all but the variables and ID from CTE so I could select Max(DATE) on both A.statusdate and P.EndDate.
By doing this I could group by A.ApplicantID and rejoin specific tables outside of the CTE in order to get Applicant Name and Status ID back into my results set.
Thank you for your help everyone.
I am making a stored procedure in MSSQL and I want to group my results by the interval day, I am unsure if this is correct though:
(this is a portion)
INSERT #Results
(
[Day],
[Month],
[Year],
[Result]
)
SELECT
DATEPART(DD, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(MM, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(YY, DATEADD(MI, #GmtOffset, EventDate)),
Result = CASE WHEN #Metric = 'Take Rate' THEN NULL ELSE COUNT(*) END
FROM BundleEvent
WHERE BundleEventTypeId = CASE WHEN #Metric = 'Take Rate' THEN #TypeTakeId ELSE #BundleEventTypeId END
AND EventDate >= #StartTime AND EventDate <= #EndTime
GROUP BY
DATEPART(YY, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(MM, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(DD, DATEADD(MI, #GmtOffset, EventDate))
My fear is it is ultimately going to group the data by the date where the year or month do not matter. Does anyone know if I am doing this correctly? EventDate is the DateTime field that I want to do a GROUP BY on by interval of day.
Thanks
EDIT:
This is the correct way, a simple format exchange... sometimes I need more coffee.
INSERT #Results
(
[Date],
[Result]
)
SELECT
CAST(DATEADD(MI, #GmtOffset, BundleEvent.EventDate) AS DATE),
Result = CASE WHEN #Metric = 'Take Rate' THEN 0 ELSE COUNT(*) END
FROM dbo.BundleEvent WITH (NOLOCK)
JOIN dbo.BundleUser WITH (NOLOCK)
ON BundleEvent.BundleId = BundleUser.BundleId
JOIN dbo.Bundle WITH (NOLOCK)
ON BundleEvent.BundleId = Bundle.BundleId
WHERE BundleEvent.EventDate >= #StartTimeGmt AND BundleEvent.EventDate <= #EndTimeGmt
AND BundleEvent.BundleEventTypeId = CASE WHEN #Metric = 'Take Rate' THEN #TypeTakeId ELSE #BundleEventTypeId END
AND BundleUser.UserId = CASE WHEN #UserId IS NULL THEN BundleUser.UserId ELSE #UserId END
AND Bundle.BundleType = 1
GROUP BY
CAST(DATEADD(MI, #GmtOffset, BundleEvent.EventDate) AS DATE)
Then I do a sub compare with the format exchange:
CAST(
(SELECT COUNT(*)
FROM dbo.BundleEvent WITH (NOLOCK)
JOIN dbo.BundleUser WITH (NOLOCK)
ON BundleEvent.BundleId = BundleUser.BundleId
JOIN dbo.Bundle WITH (NOLOCK)
ON BundleEvent.BundleId = Bundle.BundleId
WHERE CAST(DATEADD(MI, #GmtOffset, BundleEvent.EventDate) AS DATE) = [Date]
AND BundleEvent.BundleEventTypeId = #TypeTakeId
AND BundleUser.UserId = CASE WHEN #UserId IS NULL THEN BundleUser.UserId ELSE #UserId END
AND Bundle.BundleType = 1)
AS DECIMAL(5,2)
So in essence the query is matching up correctly and using a new format.
Since you are not using and aggregates (except Count(*), Are you sure you want a GROUP BY? Using DISTINCT will produce a single insert entry for all matching dates; perhaps something like:
DECLARE #DATE_GMT datetime
SET #DATE_GMT=DATEADD(MI, #GmtOffset, EventDate)
INSERT #Results
(
[Day],
[Month],
[Year],
[Result]
)
SELECT DISTINCT
DATEPART(DD, #DATE_GMT),
DATEPART(MM, #DATE_GMT),
DATEPART(YY, #DATE_GMT),
Result = CASE WHEN #Metric = 'Take Rate' THEN NULL ELSE COUNT(*) END
FROM BundleEvent
WHERE BundleEventTypeId = CASE WHEN #Metric = 'Take Rate' THEN #TypeTakeId ELSE #BundleEventTypeId END
AND EventDate Between #StartTime AND #EndTime