Joining two tables on columns that don't equal - sql

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

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 take out values from 2 different CTE results

I have 2 result sets from different CTE selects, and i need to take out the value of 2 columns in first table from the value of 2 columns in second table.
Query description: In first result i find count of total new customers, and in the second result i get the customers that have any sales ( joining the factsales table ) and what i need is to find the count of new customers that dont have any invoices ( revenue made ). So from my logic that will mean the total number of customers from the first result - the total number of customers that have any revenue ( meaning they can be found in the factSales table ). So the count of customers without any revenue are found in customer table, but arent in factsales table. I hope this simplifies my issue and expectations. Thanks for the help provided.
use dwh01;
-- Total number of new customers
with cte1 as(
SELECT
d.[Year],
d.[month],
case when c.branchid = '1080' then c.customerid end as New_Customers_Per_Month_1080,
case when c.branchid = '1081' then c.customerid end as New_Customers_Per_Month_1081
FROM [dwh01].[live].[DimCustomer] c
inner join live.DimDate d -- Date join
on d.DayDate = c.Createdate
where d.DayDate >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-12, 0) and d.DayDate <= DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) and c.IsActive = 'Y'
)
select
c.[Year],
c.[month],
count(distinct c.New_Customers_Per_Month_1080) as New_Customers_1080,
count(distinct c.New_Customers_Per_Month_1081) as New_Customers_1081
from cte1 c
group by c.[Year], c.[month]
order by c.[year] asc, c.[month] asc
;
-- new customers that have any revenue
with cte1 as(
SELECT
d.[Year],
d.[month],
case when c.branchid = '1080' then c.customerid end as New_Customers_Per_Month_1080,
case when c.branchid = '1081' then c.customerid end as New_Customers_Per_Month_1081
FROM live.FactSales fs
inner join [live].[DimCustomer] c
on fs.customerkey = c.CustomerKey
inner join live.DimDate d -- Date join
on d.DayDate = c.Createdate
where d.DayDate >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-12, 0) and d.DayDate <= DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) and c.IsActive = 'Y'
)
select
c.[Year],
c.[month],
count(distinct c.New_Customers_Per_Month_1080) as New_Customers_1080,
count(distinct c.New_Customers_Per_Month_1081) as New_Customers_1081
from cte1 c
group by c.[Year], c.[month]
order by c.[year] asc, c.[month] asc
Result from first table:
Expected result:
You can define any number of common table expression sequentially. Here I have created two cte named cte1 and cte2 first then joined both with [year] and [month] column and used group by clause to get what you want.
with cte1 as(
SELECT
d.[Year],
d.[month],
case when c.branchid = '1080' then c.customerid end as New_Customers_Per_Month_1080,
case when c.branchid = '1081' then c.customerid end as New_Customers_Per_Month_1081
FROM [dwh01].[live].[DimCustomer] c
inner join live.DimDate d -- Date join
on d.DayDate = c.Createdate
where d.DayDate >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-12, 0) and d.DayDate <= DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) and c.IsActive = 'Y'
) , cte2 as(
SELECT
d.[Year],
d.[month],
case when c.branchid = '1080' then c.customerid end as New_Customers_Per_Month_1080,
case when c.branchid = '1081' then c.customerid end as New_Customers_Per_Month_1081
FROM live.FactSales fs
inner join [live].[DimCustomer] c
on fs.customerkey <> c.CustomerKey
inner join live.DimDate d -- Date join
on d.DayDate = c.Createdate
where d.DayDate >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-12, 0) and d.DayDate <= DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) and c.IsActive = 'Y'
)
select
c.[Year],
c.[month],
(count(distinct c.New_Customers_Per_Month_1080)-count(distinct c2.New_Customers_Per_Month_1080)) as New_Customers_1080,
(count(distinct c.New_Customers_Per_Month_1081)-count(distinct c2.New_Customers_Per_Month_1081)) as New_Customers_1081
from cte1 c inner join cte2 c2
on c.[year]=c2.[year] and c.[month]=c2.[month]
group by c.[Year], c.[month]
order by c.[year] asc, c.[month] asc
You can have two CTEs defined first and later you can join them, as given below:
-- Total number of new customers
;with cte1 as(
SELECT
d.[Year],
d.[month],
case when c.branchid = '1080' then c.customerid end as New_Customers_Per_Month_1080,
case when c.branchid = '1081' then c.customerid end as New_Customers_Per_Month_1081
FROM [dwh01].[live].[DimCustomer] c
inner join live.DimDate d -- Date join
on d.DayDate = c.Createdate
where d.DayDate >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-12, 0) and d.DayDate <= DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) and c.IsActive = 'Y'
) , cte2 as(
SELECT
d.[Year],
d.[month],
case when c.branchid = '1080' then c.customerid end as New_Customers_Per_Month_1080,
case when c.branchid = '1081' then c.customerid end as New_Customers_Per_Month_1081
FROM live.FactSales fs
inner join [live].[DimCustomer] c
on fs.customerkey <> c.CustomerKey
inner join live.DimDate d -- Date join
on d.DayDate = c.Createdate
where d.DayDate >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-12, 0) and d.DayDate <= DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) and c.IsActive = 'Y'
)
SELECT cte1.Year
, cte1.Month
, (cte1.New_Customers_Per_Month_1080 - cte2.New_Customers_Per_Month_1080) AS New_Customers_1080
,, (cte1.New_Customers_Per_Month_1081 - cte2.New_Customers_Per_Month_1081) AS New_Customers_1081
FROM cte1
INNER JOIN cte2
ON cte2.Year = cte1.Year AND cte2.Month = cte1.Month

Count Number of games per product type

How can I count NumOfGames% per product type like % = TOTAL/PERPRODUT_TYPE.
Here is my query for the TOTAL NumOfGames
SELECT
DPT.[Name] [ProductType],
SUM([FinishedGameCycleCount]) [NumOfGames]
FROM [WarehouseMgmt].[FactGameAgr] FWA
JOIN [WarehouseMgmt].[DimPlayer] DPL ON FWA.[PlayerId] = DPL.[Id]
JOIN [WarehouseMgmt].[DimGame] DG ON FWA.[GameId] = DG.[Id]
JOIN [WarehouseMgmt].[DimProductType] DPT ON DPT.Id = FWA.ProductTypeId
WHERE [WarehouseMgmt].[GetDateTimeFromTimeId](TimeId) >= Dateadd(Month, Datediff(Month, 0, DATEADD(m, -6, current_timestamp)), 0)
GROUP BY DPT.[Name]
Use SUM() OVER()
SELECT
DPT.[Name] [ProductType],
SUM([FinishedGameCycleCount]) [NumOfGames],
SUM([FinishedGameCycleCount]) *100. / SUM(SUM([FinishedGameCycleCount])) OVER() [percentage]
FROM [WarehouseMgmt].[FactGameAgr] FWA
JOIN [WarehouseMgmt].[DimPlayer] DPL ON FWA.[PlayerId] = DPL.[Id]
JOIN [WarehouseMgmt].[DimGame] DG ON FWA.[GameId] = DG.[Id]
JOIN [WarehouseMgmt].[DimProductType] DPT ON DPT.Id = FWA.ProductTypeId
WHERE [WarehouseMgmt].[GetDateTimeFromTimeId](TimeId) >= Dateadd(Month, Datediff(Month, 0, DATEADD(m, -6, current_timestamp)), 0)
GROUP BY DPT.[Name]

SELECT Agents Who Have Not Been Evaluated (by week)

Trying to pull list of agents that have not been Evaluated (Scored) in the past week. I'm getting the Agent_Name, but when checking list of Evaluations, their names are appearing in the Evaluation list.
select agent.firstname + ' ' + agent.lastname Agent_Name
from dbo.agent agent
left outer join dbo.crr crr
on agent.id = crr.agentfk
left outer join dbo.evaluation eval
on crr.id = eval.crrfk
where eval.crrfk is null
and crr.localtime >= Dateadd(Day, Datediff(Day, 0, Dateadd(D, -7, Current_Timestamp)), 0);
You need to move the condition on crr to the on clause:
select agent.firstname + ' ' + agent.lastname Agent_Name
from dbo.agent agent left outer join
dbo.crr crr
on agent.id = crr.agentfk and
crr.localtime >= Dateadd(Day, Datediff(Day, 0, Dateadd(D, -7, Current_Timestamp)), 0) left outer join
dbo.evaluation eval
on crr.id = eval.crrfk
where eval.crrfk is null ;
I'm not sure why you need the evaluation table, if the data is in crr. I mean, this should do the same thing (assuming you are using the right date):
select (a.firstname + ' ' + a.lastname) as Agent_Name
from dbo.agent a left outer join
dbo.crr
on a.id = crr.agentfk and
crr.localtime >= Dateadd(Day, Datediff(Day, 0, Dateadd(D, -7, Current_Timestamp)), 0)
where crr.id is null ;

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