If field = Null then use a different field - sql

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

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

Joining two tables on columns that don't equal

I have a equipment table and a downtime table that I am wanting to join, I am wanting to display all the equipment and the downtime hours. If there is no downtime for a certain piece of equipment then I want to display a zero in the rows where value is null. This is what I have below. It only gives me the equipment that has downtime in the other table.
Select a.EquipNbr,
ISNULL(Sum(a.Downtime),0)
From MobileDowntime (nolock) a
Join MblEquip (nolock) b on a.EquipNbr = b.EquipNbr
Where b.DelFlg = 0 and
b.EquipNbr <> 'Clean Shop' and
a.DateTm Between DATEADD(month, DATEDIFF(month, 0, getDate()), 0) and DATEADD(month, DATEDIFF(month, -1, getDate()), -1)
Group By a.EquipNbr
Order by a.EquipNbr Asc
example of what I am trying to accomplish.. But the downtime table on captures data on change so there might not be any downtime for that piece of equipment for the whole month.
66 total pieces of equipment
Equipment / Downtime
1717 57
1723 0
1724 0
1725 50
1728 0
1734 35
1738 0
You want a left join and to move conditions on the MobileDowntime table to the on clause:
Select e.EquipNbr, coalesce(sum(md.Downtime), 0)
From MblEquip e left join
MobileDowntime md
on md.EquipNbr = e.EquipNbr and
md.DateTm between DATEADD(month, DATEDIFF(month, 0, getDate()), 0) and DATEADD(month, DATEDIFF(month, -1, getDate()), -1)
where e.DelFlg = 0 and e.EquipNbr <> 'Clean Shop'
group by e.EquipNbr
order by e.EquipNbr Asc;
Note that I replaced your table aliases (hopefully correctly). a and b are meaningless. Instead, I used abbreviations for the table names.
Final Answer
Select b.EquipNbr, Sum(ISNULL((a.Downtime),0)) From MobileDowntime (nolock) a
RIGHT OUTER Join MblEquip (nolock) b on a.EquipNbr = b.EquipNbr
Where b.DelFlg = 0 and b.EquipNbr != 'Clean Shop'
AND
(
a.datetm is null or
(a.DateTm Between DATEADD(month, DATEDIFF(month, 0, getDate()), 0)
and DATEADD(month, DATEDIFF(month, -1, getDate()), -1) )
)
Group By b.EquipNbr Order by b.EquipNbr Asc
Fiddle: https://dbfiddle.uk/?rdbms=sqlserver_2012&fiddle=cc2c2cce139cda7d7c5878d6c967da34
Step by Step
Step 1:
What you need to do is to use an outer-join, and a function that replaces NULL with zero (that you are doing).
So as a first step you would do the following:
Select b.EquipNbr, ISNULL((a.Downtime),0) From MobileDowntime (nolock) a
RIGHT OUTER Join MblEquip (nolock) b on a.EquipNbr = b.EquipNbr
Step 2: With Group by
Following, you can add the group by to get the following:
Select b.EquipNbr, Sum(ISNULL((a.Downtime),0)) From MobileDowntime (nolock) a
RIGHT OUTER Join MblEquip (nolock) b on a.EquipNbr = b.EquipNbr
Where b.DelFlg = 0 and b.EquipNbr != 'Clean Shop'
Group By b.EquipNbr Order by b.EquipNbr Asc
The final part is the where condition using the dates.
Update
The conversion error I think was because of the numerical comparison != .
I did an experiment and converted the Varchar to Int.
Then I changed the != to not like.
Select b.EquipNbr, Sum(ISNULL((a.Downtime),0)) From MobileDowntime (nolock) a
RIGHT OUTER Join MblEquip (nolock) b on a.EquipNbr = b.EquipNbr
Where b.DelFlg = 0 and b.EquipNbr not like 'Clean Shop'
AND
(
a.datetm is null or
(a.DateTm Between DATEADD(month, DATEDIFF(month, 0, getDate()), 0)
and DATEADD(month, DATEDIFF(month, -1, getDate()), -1) )
)
Group By b.EquipNbr Order by b.EquipNbr Asc
You can use left outer join in which it will show null when there is no downtime hours

SQL statement causing timeouts

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.

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.

How can I show when users were last active based on statistics?

I have a database that stores the datetime from getdate() when they log into the system.
I want to do a select that shows ones that have been active in the last 10mins as 'active now' and for all the rest, say how long ago they were active e.g. 3 days or 3hrs.
I have no idea how to do the second part of that.
This is what I have done to get the active users:
SELECT DISTINCT FirstName, LastName, 'Active now'
FROM dbo.Users UP
INNER JOIN Portal.dbo.UserStatistics US
ON US.UserID = UP.UserID
INNER JOIN Portal.dbo.Pages
ON pages.id = US.PageID
WHERE US.DateTimeLastUpdated >= dateadd(minute, -5, getdate())
SELECT DISTINCT FirstName, LastName,
CASE when US.DateTimeLastUpdated >= dateadd(minute, -5, getdate()) then 'Active now' else cast(datediff(h,getdate(), US.DateTimeLastUpdated) as varchar) end
FROM dbo.Users UP
INNER JOIN Portal.dbo.UserStatistics US
ON US.UserID = UP.UserID
INNER JOIN Portal.dbo.Pages
ON pages.id = US.PageID
You can use a case statement with a group by clause:
SELECT
FirstName,
LastName,
case when max(US.DateTimeLastUpdated) >= dateadd(minute, -5, getdate()) then
'Active now'
when max(US.DateTimeLastUpdated) >= dateadd(day, -2, getdate()) then
'Active 3 days'
...
end as [last viewed]
FROM
dbo.Users UP
INNER JOIN
Portal.dbo.UserStatistics US
ON US.UserID = UP.UserID
INNER JOIN
Portal.dbo.Pages
ON pages.id = US.PageID
GROUP BY
FirstName,
LastName
You need to aggregate to get the latest access per user, and then work with that, so something a little like:
SELECT up.FirstName,
up.LastName,
CASE
WHEN MAX(US.DateTimeLastUpdated) >= dateadd(minute, -5, getdate()) THEN 'Now'
ELSE CAST(MAX(DATEDIFF(H, US.DateTimeLastUpdated, GETDATE())) AS VARCHAR) + ' hours ago'
AS LastActive
FROM dbo.Users UP
INNER JOIN Portal.dbo.UserStatistics US
ON US.UserID = UP.UserID
INNER JOIN Portal.dbo.Pages
ON pages.id = US.PageID
GROUP BY up.FirstName,
up.LastName