SQL statement causing timeouts - sql

Here is my code that I really need to get revised. I did it in a simple way as I am not a pro in SQL.
SELECT Inv1.AutoIndex, Inv1.DocState, Inv1.OrderNum,
Inv1.ExtOrderNum, dbo.Client.ubARSMS, dbo.Client.Fax1
FROM dbo.InvNum Inv1 INNER JOIN
dbo.Client ON Inv1.AccountID = dbo.Client.DCLink
WHERE (dbo.Client.ubARSMS = 1)
AND (Inv1.OrderDate >= dbo.Client.udARSMSACTDATE)
AND Inv1.OrderNum NOT IN (SELECT o.OrderNum
FROM dbo.net_OrderSMSLog o
WHERE (o.DocState = 4))
AND Inv1.AutoIndex NOT IN(SELECT Inv2.OrigDocID
FROM dbo.InvNum Inv2
WHERE Inv2.OrderNum = Inv1.OrderNum)
AND
(
DATEPART(YEAR, Inv1.InvDate) = DATEPART(YEAR, GETDATE())
AND DATEPART(MONTH, Inv1.InvDate) = DATEPART(MONTH, GETDATE())
AND DATEPART(DAY, Inv1.InvDate) = DATEPART(DAY, GETDATE())
OR
DATEPART(YEAR, Inv1.InvDate) = DATEPART(YEAR,DATEADD(dd,-1,GETDATE()))
AND DATEPART(MONTH, Inv1.InvDate) = DATEPART(MONTH,DATEADD(dd,-1,GETDATE()))
AND DATEPART(DAY, Inv1.InvDate) = DATEPART(DAY,DATEADD(dd,-1,GETDATE()))
)
I need this to work as fast as possible.

This is your query:
SELECT Inv1.AutoIndex, Inv1.DocState, Inv1.OrderNum,
Inv1.ExtOrderNum, c.ubARSMS, c.Fax1
FROM dbo.InvNum Inv1 INNER JOIN
dbo.Client c
ON Inv1.AccountID = c.DCLink AND Inv1.OrderDate >= c.udARSMSACTDATE
WHERE (c.ubARSMS = 1) AND
Inv1.OrderNum NOT IN (SELECT o.OrderNum
FROM dbo.net_OrderSMSLog o
WHERE (o.DocState = 4)
) AND
Inv1.AutoIndex NOT IN (SELECT Inv2.OrigDocID
FROM dbo.InvNum Inv2
WHERE Inv2.OrderNum = Inv1.OrderNum
) OR
(Inv1.InvDate >= CAST(DATEADD(day, -1 GETDATE()) as date) AND
Inv1.InvDate < CAST(GETDATE() as date)
)
This is really two queries, which you can combine using UNION ALL. The first is:
SELECT Inv1.AutoIndex, Inv1.DocState, Inv1.OrderNum,
Inv1.ExtOrderNum, c.ubARSMS, c.Fax1
FROM dbo.InvNum Inv1 INNER JOIN
dbo.Client c
ON Inv1.AccountID = c.DCLink AND Inv1.OrderDate >= c.udARSMSACTDATE
WHERE (c.ubARSMS = 1) AND
Inv1.OrderNum NOT IN (SELECT o.OrderNum
FROM dbo.net_OrderSMSLog o
WHERE (o.DocState = 4)
) AND
Inv1.AutoIndex NOT IN (SELECT Inv2.OrigDocID
FROM dbo.InvNum Inv2
WHERE Inv2.OrderNum = Inv1.OrderNum
)
For this, I would suggest indexes on Client(ubARSMS, DCLink, udARSMACTDate), InvNum(ClientId, OrderNum, AutoIndex), InvNum(OrderNum, OrigDocId), and net_OrderSMSLog(DocState, OrderNum).
For the second query:
SELECT Inv1.AutoIndex, Inv1.DocState, Inv1.OrderNum,
Inv1.ExtOrderNum, c.ubARSMS, c.Fax1
FROM dbo.InvNum Inv1 INNER JOIN
dbo.Client c
ON Inv1.AccountID = c.DCLink AND Inv1.OrderDate >= c.udARSMSACTDATE
WHERE (Inv1.InvDate >= CAST(DATEADD(day, -1 GETDATE()) as date) AND
Inv1.InvDate < CAST(GETDATE() as date)
)
You want an index on InvNum(InvDate, AccountId, OrderDate) and Client(DCLink, udARSMACTDate).

Since my query above was working when the load on the server was minimal and there were less records to pull.
The timeout was the only thing that I needed to be handled without having to create indexes on the clients Main database.
So I simply created a 2nd windows service that checks if the 1st windows service crashes/stopped due to timeout, then restart it.
Solved my issue of the timeout crashing my service.

Related

Leverage T-SQL newer capabilities to make query more efficient

I have this T-SQL query written in a very basic and inefficient way:
SELECT
e.ExchangeId,
e.ExchangeName,
s.StockId,
s.StockName,
sp.StockDate,
sp.StockPrice,
DATEADD(YEAR, -3, sp.StockDate) AS YearTDate,
(SELECT spm.StockPrice FROM dbo.StockPrices spm WITH(NOLOCK) WHERE (spm.StockId = sp.StockId) AND (spm.StockDate = DATEADD(YEAR, -3, sp.StockDate))) AS YearTPrice,
(SELECT TOP 1 spm.StockDate FROM dbo.StockPrices spm WITH(NOLOCK) WHERE (spm.StockId = sp.StockId) AND (spm.StockDate = DATEADD(YEAR, -3, sp.StockDate)) ORDER BY spm.StockPriceId) AS LatestDate,
(SELECT TOP 1 spm.StockPrice FROM dbo.StockPrices spm WITH(NOLOCK) WHERE (spm.StockId = sp.StockId) AND (spm.StockDate = DATEADD(YEAR, -3, sp.StockDate)) ORDER BY spm.StockPriceId) AS LatestPrice,
((SELECT TOP 1 spm.StockPrice FROM dbo.StockPrices spm WITH(NOLOCK) WHERE (spm.StockId = sp.StockId) AND (spm.StockDate = DATEADD(YEAR, -3, sp.StockDate)) ORDER BY spm.StockPriceId) - sp.StockPrice) AS PL,
CASE WHEN sp.StockPrice < (SELECT MIN(spm.StockPrice) FROM dbo.StockPrices spm WITH(NOLOCK) WHERE (spm.StockId = sp.StockId) AND (spm.StockDate BETWEEN DATEADD(YEAR, -3, sp.StockDate) AND DATEADD(DAY, -1, sp.StockDate))) THEN 'Opportunity' ELSE 'None' END AS [Status]
FROM dbo.StockPrices sp WITH(NOLOCK)
INNER JOIN dbo.Stocks s WITH(NOLOCK)
ON s.StockId = sp.StockId
INNER JOIN dbo.Exchanges e WITH(NOLOCK)
ON e.ExchangeId = s.ExchangeId
GO
How can I rewrite this query to be more efficient? i.e. using WITH keyword or some other features I might not be aware of.
I would start my moving your 5 subqueries into a single query. Of the 4 TOP (1) queries all but one of them order by StockPriceId, so I'm going to assume it should be the same for YearTPrice (which currently returns an arbitrary row).
For the MIN value, I use a windowed MIN instead.
I also remove the NOLOCK hints as they are clearly being abused. If you "msut" (you don't) need to have the NOLOCK hint against every table in the query then change the isolation level of the tranasction.
SELECT e.ExchangeId,
e.ExchangeName,
s.StockId,
s.StockName,
sp.StockDate,
sp.StockPrice,
DATEADD(YEAR, -3, sp.StockDate) AS YearTDate,
spm.StockPrice AS YearTPrice,
spm.StockDate AS LatestDate,
spm.StockPrice AS LatestPrice,
spm.StockPrice - sp.StockPrice AS PL,
CASE WHEN sp.StockPrice < spm.MinPrice THEN 'Opportunity' ELSE 'None' END AS [Status]
FROM dbo.StockPrices sp
INNER JOIN dbo.Stocks s ON s.StockId = sp.StockId
INNER JOIN dbo.Exchanges e ON e.ExchangeId = s.ExchangeId
--I use an outer apply, as I don't know if a row is guarenteed to be returned
OUTER APPLY (SELECT TOP (1)
dt.StockPrice,
dt.StockDate,
dt.MinPrice
FROM (SELECT ca.StockPriceId,
ca.StockPrice,
ca.StockDate,
MIN(StockPrice) OVER (PARTITION BY ca.StockId) AS MinPrice
FROM dbo.StockPrices ca
WHERE ca.StockId = sp.StockId
AND ca.StockDate = DATEADD(YEAR, -3, sp.StockDate)) dt --This isn't SARGable, so will result in a scan
ORDER BY dt.StockPriceId) spm;
of course, this is all completely untested as no sample data exists, so I have no way of knowing how much this will change your query (or even effect the results as I can't test) but it does reduce 5 or 6 scans of StockPrices down to 1 or 2

How to troubleshooting the Sql test queries of on-prime and synapse

I am new to ETL testing, and I am comparing the 7 and 90 days data between on-prime and synapse,
The query Was written by my lead, but He left the project.
This is my on-prime Query,
Select s.StoreNumber,
rt.Description As RecordType,
rt.IsReturn,
Sum(tm.Amount) As TotalSales,
Sum(tm.Quantity) As TotalSalesQty
From pos.Ticket t Join pos.TicketSegment ts
On t.TicketKey = ts.TicketKey
Join pos.TicketMerchandise tm
On tm.TicketKey = ts.TicketKey
And tm.TicketSegmentKey = ts.TicketSegmentKey
Join org.Store s
On t.StoreKey = s.StoreKey
Join pos.RecordType rt
On tm.RecordTypeKey = rt.RecordTypeKey
Where t.BusinessDate = Dateadd(day,-90, getdate()) and t.BusinessDate< getdate()
Group By s.StoreNumber,
rt.Description,
rt.IsReturn
UNION ALL
Select s.StoreNumber,
rt.Description As RecordType,
rt.IsReturn,
Sum(tdi.DiscountAmount) As TotalSales,
Sum(td.Quantity) As TotalSalesQty
From pos.Ticket t Join pos.TicketSegment ts
On t.TicketKey = ts.TicketKey
Join pos.TicketDiscount td
On td.TicketKey = ts.TicketKey
And td.TicketSegmentKey = ts.TicketSegmentKey
Join pos.TicketDiscountItem tdi
On tdi.TicketDiscountKey = td.TicketDiscountKey
Join org.Store s
On t.StoreKey = s.StoreKey
Join pos.RecordType rt
On td.RecordTypeKey = rt.RecordTypeKey
Where t.BusinessDate >= Dateadd(day,-90, getdate()) and t.BusinessDate < getdate()
Group By s.StoreNumber,
rt.Description,
rt.IsReturn
Order By s.StoreNumber,
rt.Description,
rt.IsReturn
This is my synapse query:
Select s.StoreNumber,
tt.TransactionType,
tt.IsReturn,
Sum(Convert(Money, f.Amount)) As TotalSales,
Sum(f.Qty) As TotalSalesQty
From fact.PrdTransaction f
Join dim.Calendar c
On f.DateID = c.DateID
Join dim.Store s
On f.StoreID = s.StoreID
Join dim.TransactionType tt
On f.TransactionTypeID = tt.TransactionTypeID
Where c.Date>= DATEADD(day,-90, getdate()) and c.Date< getDate()
Group By s.StoreNumber,
tt.TransactionType,
tt.IsReturn
Order By s.StoreNumber,
tt.TransactionType,
tt.IsReturn
When I compared both results, I got some differences for the 7 days trailing period of TotalsalesQty and 90 days trailing period is a little more different than 7 days for TotalSales and TotalSlaesQty.
I am struggling to troubleshoot the difference, where the problem is.
Please help me to troubleshoot this.

I need help improving my SQL query for pulling a recent document count

This is just a portion of the query, but it seems to be the bottleneck:
SELECT CAST (CASE WHEN EXISTS
(SELECT 1
FROM dbo.CBDocument
WHERE (FirmId = R.FirmId) AND
(ContributionDate > DATEADD(m, -3, GETDATE())) AND
((EntityTypeId = 2600 AND EntityId = P.IProductId) OR
(EntityTypeId = 2500 AND EntityId = M.IManagerId)))
THEN 1 ELSE 0 END AS BIT) AS HasRecentDocuments
FROM dbo.CBIProduct P
JOIN dbo.CBIManager M ON P.IManagerId = M.IManagerId
JOIN dbo.CBIProductRating R ON P.IProductId = R.IProductId
JOIN dbo.CBIProductFirmDetail D ON (D.IProductId = P.IProductId) AND
(R.FirmId = D.FirmId)
CROSS APPLY (SELECT TOP 1 RatingDate, IProductRatingId, FirmId
FROM dbo.CBIProductRating
WHERE (IProductId = P.IProductId) AND (FirmId = R.FirmId)
ORDER BY RatingDate DESC) AS RD
WHERE (R.IProductRatingId = RD.IProductRatingId) AND (R.FirmId = RD.FirmId)
There are a lot of other columns that I typically pull back that need the CROSS APPLY and the other joins. The bit I need to optimize is the sub-query in the case statement. This subquery takes over 3 minutes to return 119k records. I know enough about SQL to get this far, but there has to be a way to make this more efficient.
The gist of the query is just to return a flag if the associated product has any documents that have been added to the system within the last 3 months.
Edit: My DB is hosted in Azure and the database tuning advisor won't connect to it. There is a tuning advisor component in Azure, but it's not suggesting anything. There must be a better approach to the query.
Edit: In an attempt to further simplify and determine the culprit, I whittled it down to this query: (Rather than determine if a recent doc exists, it just counts recent docs.)
SELECT D.FirmId, P.IProductId,
,(SELECT COUNT(DocumentId) FROM dbo.CBDocument WHERE
(FirmId = D.FirmId) AND
(ContributionDate > DATEADD(m, -3, GETDATE())) AND
((EntityTypeId = 2600 AND EntityId = P.IProductId) OR
(EntityTypeId = 2500 AND EntityId = M.IManagerId))) AS RecentDocCount
FROM dbo.CBIProduct P
FULL JOIN dbo.CBIProductFirmDetail D ON D.IProductId = P.IProductId
JOIN dbo.CBIManager M ON M.IManagerId = P.IManagerId
That runs in 3 minutes, 53 seconds.
If I declare a variable to store the date (DECLARE #Today DATE = GETDATE())
and put the variable in place of GETDATE() in the query (DATEADD(m, -3, #Today)), it runs in 12 seconds.
Is there a known performance issue with GETDATE()? As far as I know, I can't use the variable in a view definition.
Does this shine any light on anything that could point to a solution? I suppose I could turn the whole thing into a stored procedure, but then I also have to adjust the application code.
Thanks.
This is the query that you claim needs optimization:
SELECT CAST(CASE WHEN EXISTS (SELECT 1
FROM dbo.CBDocument d
WHERE (d.FirmId = R.FirmId) AND
(d.ContributionDate > DATEADD(m, -3, GETDATE())) AND
((d.EntityTypeId = 2600 AND d.EntityId = P.IProductId) OR
(d.EntityTypeId = 2500 AND d.EntityId = M.IManagerId)
)
)
. . .
I'll trust your judgement. I think phrasing the query like this gives you more paths to optimization:
SELECT CAST(CASE WHEN EXISTS (SELECT 1
FROM dbo.CBDocument d
WHERE d.FirmId = R.FirmId AND
d.ContributionDate > DATEADD(m, -3, GETDATE()) AND
d.EntityTypeId = 2600 AND d.EntityId = P.IProductId
) OR
EXISTS (SELECT 1
FROM dbo.CBDocument d
WHERE d.FirmId = R.FirmId AND
d.ContributionDate > DATEADD(m, -3, GETDATE()) AND
d.EntityTypeId = 2500 AND d.EntityId = M.IManagerId
)
. . .
Then you want an index on CBDocument(FirmId, EntityTypeId, EntityId, ContributionDate).
Operations such as correlated subqueries and full outer join are rather expensive and I would suggest looking for alternatives to those. Whilst I am not familiar with your data model or data, I suggest the changing the "from table" to CBIProductFirmDetail and I have further assumed an inner join the product table and the manager table then inner joined to the product table. If that join sequence is correct this removes the expense of some outer joins.
In place of the correlated subquery to determine a count, I suggest you treat that as a subquery which is left joined.
SELECT
d.FirmId
, p.IProductId
, COALESCE(Docs.RecentDocCount,0) RecentDocCount
FROM dbo.CBIProductFirmDetail d
JOIN dbo.CBIProduct p ON d.IProductId = p.IProductId
JOIN dbo.CBIManager m ON p.IManagerId = m.IManagerId
LEFT JOIN (
SELECT
FirmId
, EntityId
, EntityTypeId
, COUNT(DocumentId) recentdoccount
FROM dbo.CBDocument
WHERE ContributionDate > DATEADD(m, -3, GETDATE())
AND EntityTypeId IN (2500,2600)
GROUP BY
FirmId
, EntityId
, EntityTypeId
) AS docs ON d.FirmId = docs.FirmId
AND (
(docs.EntityTypeId = 2600 AND docs.EntityId = p.IProductId)
OR (docs.EntityTypeId = 2500 AND docs.EntityId = m.IManagerId)
)
;
There might be benefit in dividing that subquery too to avoid the awkward OR in that join, so:
SELECT
d.FirmId
, p.IProductId
, COALESCE(d2500.DocCount,0) + COALESCE(d2600.DocCount,0) RecentDocCount
FROM dbo.CBIProductFirmDetail d
JOIN dbo.CBIProduct p ON d.IProductId = p.IProductId
JOIN dbo.CBIManager m ON p.IManagerId = m.IManagerId
LEFT JOIN (
SELECT
FirmId
, EntityId
, COUNT(DocumentId) doccount
FROM dbo.CBDocument
WHERE ContributionDate > DATEADD(m, -3, GETDATE())
AND EntityTypeId = 2500
GROUP BY
FirmId
, EntityId
) AS d2500 ON d.FirmId = d2500.FirmId
AND m.IManagerId = d2500.EntityId
LEFT JOIN (
SELECT
FirmId
, EntityId
, COUNT(DocumentId) doccount
FROM dbo.CBDocument
WHERE ContributionDate > DATEADD(m, -3, GETDATE())
AND EntityTypeId = 2600
GROUP BY
FirmId
, EntityId
) AS d2600 ON d.FirmId = d2600.FirmId
AND p.IProductId = d2600.EntityId
;
Depending on stuff it may be faster to use a left join:
SELECT CAST(CASE when x.FirmId is not null THEN 1 ELSE 0 END AS BIT) AS HasRecentDocuments
FROM dbo.CBIProduct P
JOIN dbo.CBIManager M ON P.IManagerId = M.IManagerId
JOIN dbo.CBIProductRating R ON P.IProductId = R.IProductId
JOIN dbo.CBIProductFirmDetail D ON (D.IProductId = P.IProductId) AND (R.FirmId = D.FirmId)
LEFT JOIN dbo.CBDocument x ON x.FirmId = R.FirmId
AND x.ContributionDate > DATEADD(m, -3, GETDATE())
AND ( (x.EntityTypeId = 2600 AND x.EntityId = P.IProductId)
OR (x.EntityTypeId = 2500 AND x.EntityId = M.IManagerId))
CROSS APPLY (SELECT TOP 1 RatingDate, IProductRatingId, FirmId
FROM dbo.CBIProductRating
WHERE (IProductId = P.IProductId) AND (FirmId = R.FirmId)
ORDER BY RatingDate DESC) AS RD
WHERE (R.IProductRatingId = RD.IProductRatingId) AND (R.FirmId = RD.FirmId)
it certainly looks simpler.

Select ID from customers that do not have contacts last 7 days and 2 times in the month

I have 2 tables: customer, c_contact.
c_contact is all the e-mails I send to my customers.
From now on I need to put a new rule that the customer can't receive a new e-mail if:
1) It received an e-mail in the last 7 days
2) Have 2 or more e-mails sent in the current month
I thought something like that:
SELECT * from customer c inner join c_contact cc on cc.ID = c.ID WHERE
ID not in (select ID from c_contact c1 where c1.ID = cc.ID and
c1.CONTDATE >= getdate()-7) AND
ID not in (select count(ID) from c_contact where MONTH(contdate) = MONTH(getdate())
and YEAR(contdate) = YEAR(getdate() HAVING count(ID) >= 2)
But the table c_contact is huge and it taking ages to run this.
Is there a way to do these 2 conditions in 1 "ID not in"? I think it will run a lot faster.
I'm not sure how much better this would be performance wise, but off the top of my head you could do this.
SELECT *
FROM customer c
OUTER APPLY (SELECT COUNT(*) monthCount
FROM c_contact cc
WHERE cc.ID = c.ID
AND cc.contdate >= DATEADD(mm, DATEDIFF(mm, 0, GETDATE()), 0)
AND cc.contdate < DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) + 1, 0)) ct
OUTER APPLY (SELECT MAX(cc.contdate) lastSent
FROM c_contact cc
WHERE cc.ID = c.ID AND cc.contdate < GETDATE()) ls
WHERE ct.monthCount < 2
AND ls.lastSent < DATEADD(dd, -7, GETDATE())
Or, using a left join instead of 2 outer applies, you can try:
SELECT *
FROM customer c
LEFT JOIN (
SELECT cc.ID,
COUNT(CASE WHEN MONTH(cc.contdate) = MONTH(GETDATE()) THEN 1 END) monthCount,
MAX(cc.contdate) lastSent
FROM c_contact cc
WHERE cc.contdate BETWEEN DATEADD(dd, -32, GETDATE()) AND GETDATE()
GROUP BY cc.ID
) cc ON c.ID = cc.ID
WHERE ISNULL(cc.monthCount,0) < 2
AND ISNULL(cc.lastSent,GETDATE()) < DATEADD(dd, -7, GETDATE())
if you really just want to use NOT IN you can try:
SELECT *
FROM customer c
WHERE c.ID NOT IN (
SELECT cc.ID,
COUNT(CASE WHEN MONTH(cc.contdate) = MONTH(GETDATE()) THEN 1 END) monthCount,
MAX(cc.contdate) lastSent
FROM c_contact cc
WHERE cc.contdate BETWEEN DATEADD(dd, -32, GETDATE()) AND GETDATE()
GROUP BY cc.ID
HAVING COUNT(CASE WHEN MONTH(cc.contdate) = MONTH(GETDATE()) THEN 1 END) > 1
AND MAX(cc.contdate) > DATEADD(dd, -7, GETDATE())
)
This can also be done in other way:
select A.* from Customers A
where A.CustID not in (
select B.CustID from (
(select C.CustID, Count(C.CustID) cnt from Contacts C
where C.ContactedDate >=GETDATE()-7 group by C.CustID)
UNION
(select C.CustID, Count(C.CustID) cnt from Contacts C
where C.ContactedDate <=GETDATE()-7 and Month(C.ContactedDate) = Month(GETDATE()) and YEAR(C.ContactedDate)= YEAR(GETDATE())
group by C.CustID having COUNT(C.CustID) >= 2)) B);
go
Divide customer IDs into two sets- the first set containing IDs that were contacted in the recent 7 days, and the second set containing IDs that were contacted in the last three weeks of the current month. Take UNION of these two sets to finally exclude while selecting the desired set of customers.

If field = Null then use a different field

I have written some Sql code to display all clients who's offers are about to expire in the next 90 days by using the dateOffered field. However there is another field in the database called OfferExpirydate I would use this field however it it not always filled out.
My question is i want the code to look at OfferExpirydate and if it has a value then use it or else use the Dateoffered field as my code below stats.
( if the OfferExpirydate is not filled out it is set to a NULL )
Any help on this would be great thanks
SELECT
DateOffered,
pr.ClientID,
pr.id AS profileID,
cf.Clntnme,
pm.Lender,
ABS(DATEDIFF(DAY, DateOffered, DATEADD(d,-90, GETDATE()))) AS 'NoOfDays'
FROM tbl_profile AS pr
INNER JOIN tbl_Profile_Mortgage AS pm
ON pr.id = pm.fk_profileID
INNER JOIN dbo.tbl_ClientFile AS cf
ON pr.ClientID = cf.ClientID
WHERE
DateCompleted IS NULL AND
DateOffered > DATEADD(d,-90, GETDATE())
AND DATEDIFF(DAY, DateOffered, DATEADD(d,-90, GETDATE())) > -15
ORDER BY DateOffered ASC
COALESCE(col1, col2, ...)
will pick the first non-null value.
Try this:
SELECT DateOffered,
pr.ClientID,
pr.id AS profileID,
cf.Clntnme,
pm.Lender,
ABS(DATEDIFF(DAY, DateOffered, DATEADD(d,-90, GETDATE()))) AS 'NoOfDays'
FROM tbl_profile AS pr
INNER JOIN tbl_Profile_Mortgage AS pm
ON pr.id = pm.fk_profileID
INNER JOIN dbo.tbl_ClientFile AS cf
ON pr.ClientID = cf.ClientID
WHERE DateCompleted IS NULL AND
1 = CASE WHEN OfferExpirydate IS NOT NULL AND DATEDIFF(DAY, OfferExpirydate, GETDATE()) > -15 THEN 1
WHEN DateOffered > DATEADD(d,-90, GETDATE()) AND DATEDIFF(DAY, DateOffered, DATEADD(d,-90, GETDATE())) > -15 THEN 1
ELSE 0
END
ORDER BY DateOffered ASC