Here is my SQL, I am trying to get the DISTINCT item ids that have not been invoiced in the past two years that also have a qty on hand greater than 0. I am having an issue getting the last customer id that was invoiced for each distinct item id.
SELECT DISTINCT
im.item_id,
MAX(il.date_created),
(SELECT TOP 1 customer_id
FROM invoice_hdr ih2
WHERE ih.invoice_no = ih2.invoice_no) AS customer_id,
inv_loc.qty_on_hand,
ih.sales_location_id,
MAX(il.commission_cost)
FROM
invoice_hdr ih
INNER JOIN
invoice_line il ON ih.invoice_no = il.invoice_no
INNER JOIN
inv_mast im ON il.inv_mast_uid = im.inv_mast_uid
INNER JOIN
inv_loc ON im.inv_mast_uid = inv_loc.inv_mast_uid
WHERE
inv_loc.qty_on_hand > '0'
GROUP BY
im.item_id, inv_loc.qty_on_hand, ih.sales_location_id
HAVING
(MAX(il.date_created) < DATEADD(year, -2, GETDATE()))
I get this error:
Column 'invoice_hdr.invoice_no' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I understand the error, but no matter what I try I cannot get it to work.
I was able to get the correct data with the following query:
SELECT DISTINCT il.item_id,
ih.customer_id,
il.customer_part_number,
il.date_created,
SUM(inv_loc.qty_on_hand) AS [Qty On Hand],
ih.sales_location_id,
MAX(il.commission_cost) AS [Max Commission Cost]
FROM invoice_hdr ih
JOIN invoice_line il ON ih.invoice_no = il.invoice_no
JOIN inv_loc ON il.inv_mast_uid = inv_loc.inv_mast_uid
WHERE (SELECT TOP 1 il2.date_created FROM invoice_line il2 WHERE il2.item_id = il.item_id ORDER BY il2.date_created DESC) < DATEADD(YEAR, -2, GETDATE())
AND (SELECT TOP 1 il2.invoice_no FROM invoice_line il2 WHERE il2.item_id = il.item_id ORDER BY il2.date_created DESC) = ih.invoice_no
GROUP BY il.item_id,
ih.customer_id,
il.date_created,
ih.sales_location_id,
customer_part_number
HAVING SUM(inv_loc.qty_on_hand) > 0
ORDER BY il.item_id
I need to find the first and last Order for each Customer by OrderDate, and the name and SKU of the item with the highest business volume in each of those orders. For reference, the Customer table has >150k records, and Orders and OrderDetails (these are the Items) a lot more.
Note: Both Orders and their respective items should be in the same row as the Customer
Orders
OrderID OrderDate CustomerID BusinessVolumeTotal Subtotal
13212 '2021-09-06' 512312 500.00 25.60
OrderDetails
OrderID ItemCode ItemDescription BusinessVolume
13212 'SKW-BS' 'Some item' 450.00
In my first query I attempted to stick to joining instead of subqueries, which resulted in this
select distinct(c.CustomerID), c.FirstName + ' ' + c.LastName as Name,
cs.CustomerStatusDescription as Status,
ct.CustomerTypeDescription as Type, pv.Volume80 as G3,
fo.OrderID,fo.OrderDate,fo.SubTotal,fo.Country, fod.ItemCode, fod.ItemDescription, fopt.PriceTypeID,
lo.OrderID,lo.OrderDate,lo.SubTotal,lo.Country, lod.ItemCode, lod.ItemDescription, lopt.PriceTypeID
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID = c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID = c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID = c.CustomerID
left join Orders fo on fo.CustomerID = c.CustomerID -- First Order
left join Orders lo on lo.CustomerID = c.CustomerID -- Last Order
left join OrderDetails fod on fod.OrderID = fo.OrderID
left join OrderDetails lod on lod.OrderID = lo.OrderID
left join PriceTypes fopt on fo.PriceTypeID = fopt.PriceTypeID
left join PriceTypes lopt on lo.PriceTypeID = lopt.PriceTypeID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2,3)
and pv.PeriodTypeID = 2
/* First Order */
and fo.OrderID = (select top 1(OrderID) from Orders where CustomerID = c.CustomerID and OrderStatusID>=7 order by OrderDate )
and fod.ItemID = (select top 1(ItemID) from OrderDetails where OrderID = fo.OrderID order by BusinessVolume)
/* Last Order */
and lo.OrderID = (select top 1(OrderID) from Orders where CustomerID = c.CustomerID and OrderStatusID>=7 order by OrderDate desc)
and lod.ItemID = (select top 1(ItemID) from OrderDetails where OrderID = lo.OrderID order by BusinessVolume desc)
and pv.PeriodID = (select PeriodID from Periods where PeriodTypeID=2 and StartDate <= #now and EndDate >= #now)
But this ends up executing for about 6-7 minutes. From the explain plan, it looks like most of it is taken up by a Key Lookup for Orders based on OrderStatusID >= 7.
So I tried to use window functions to achieve the same:
select distinct(c.CustomerID), c.FirstName + ' ' + c.LastName as Name, cs.CustomerStatusDescription as Status,
ct.CustomerTypeDescription as Type, pv.Volume80 as G3,
fal.*
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID = c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID = c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID = c.CustomerID
left join(
select
CustomerID,
max(case when MinDate = 1 then OrderID end) FirstOrderID,
max(case when MinDate = 1 then OrderDate end) FirstOrderDate,
max(case when MinDate = 1 then BusinessVolumeTotal end) FirstBVTotal,
max(case when MinDate = 1 then PriceTypeDescription end) FirstPriceType,
max(case when MinDate = 1 then ItemCode end) FirstItemCode,
max(case when MinDate = 1 then ItemDescription end) FirstItemDescription,
max(case when MaxDate = 1 then OrderID end) LastOrderID,
max(case when MaxDate = 1 then OrderDate end) LastOrderDate,
max(case when MaxDate = 1 then BusinessVolumeTotal end) LastBVTotal,
max(case when MaxDate = 1 then PriceTypeDescription end) LastPriceType,
max(case when MaxDate = 1 then ItemCode end) LastItemCode,
max(case when MaxDate = 1 then ItemDescription end) LastItemDescription
from
(
select distinct o.CustomerID,
o.OrderID,
o.OrderDate,
o.BusinessVolumeTotal,
PT.PriceTypeDescription,
RANK() over (partition by o.CustomerID order by OrderDate) as MinDate,
RANK() over (partition by o.CustomerID order by OrderDate desc) as MaxDate,
FIRST_VALUE(ItemCode) over (partition by od.OrderID order by BusinessVolume desc) as ItemCode,
FIRST_VALUE(ItemDescription) over (partition by od.OrderID order by BusinessVolume desc) as ItemDescription
from Orders o
left join OrderDetails od on od.OrderID = o.OrderID
left join PriceTypes PT on o.PriceTypeID = PT.PriceTypeID
where o.OrderStatusID >= 7
) fal
group by CustomerID
) fal on c.CustomerId = fal.CustomerID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2,3)
and pv.PeriodTypeID = 2
/* CurrentG3 */
and pv.PeriodID = (select PeriodID from Periods where PeriodTypeID=2 and StartDate <= #now and EndDate >= #now)
Alas, this ended up executing even longer. I need a way to optimize this if possible.
Secondary query
I do also need a count and sum of volume per Order in the last 3, 6 and 12 months. I currently do this programatically as secondary queries after the original returns a result, and I forward the CustomerIDs, like this:
select count(OrderID) as Cnt, sum(BusinessVolumeTotal) as Bv, CustomerID
from Orders where OrderStatusID > 6 and OrderTypeID in (1,4,8,11)
and OrderDate >= #timeAgo and CustomerID in #ids group by CustomerID
Times 3, because 3, 6 and 12 months. Ideally, I would also like to make this part of the original but I don't really have a good idea on how to do it, especially with how convoluted the joining is with the orders.
So ideally I'd end up with a result table like this
CustomerID Name CustomerStatus CustomerType FirstOrderID FirstOrderDate FirstBVTotal FirstItemCode FirstItemDesc FirstPriceType LastOrderID LastOrderDate LastBVTotal LastItemCode LastItemDesc LastPriceType ThreeMonthCount ThreeMonthTotal SixMonthCount SixMonthTotal TwelveMonthCount TwelveMonthTotal
512312 'Jane Doe' 'Active' 'Retail' 13212 '2020-06-06' 50.00 'Item1' 'Item 1 desc' 'Retail' 14321 '2021-09-01' 200.00 'Item2' 'Item 2 desc' 'Retail' 45 4305.00 76 8545.60 183 21542.95
Any help and advice on how to optimize or reduce the query, as well as anything you believe Im doing wrong would be GREATLY appreciated.
P.S. I don't know if the title is fitting and if I'd be able to change it later, it's been a while since I've used SO to ask a question.
UPDATE
Actual Execution Plan for Query 1:
https://www.brentozar.com/pastetheplan/?id=SJd56RSmK
Actual Execution Plan for Query 2:
https://www.brentozar.com/pastetheplan/?id=BJ7QHk87Y
I think you need to keep in mind two main points with this type of query:
The key to good performance with window functions is to not introduce an unnecessary sort. So while you can use ROW_NUMBER to get the first order in either direction, you should not use another opposing ROW_NUMBER to get the last. Rather use LEAD to check if the next row exists, thereby telling you if this is the last row. You can then use conditional aggregation.
There are generally two ways to calculate first/last: a row-numbering solution, as above, or an APPLY, which picks out the exact one you need.
I think that for the OrderDetails we should use an apply, because there are only two orders per customer that we need to find. This does need good indexing, so if OrderDetails is not well indexed, then you may want to switch to a row-numbering solution for this also.
select
c.CustomerID,
c.FirstName + ' ' + c.LastName as Name,
cs.CustomerStatusDescription as Status,
ct.CustomerTypeDescription as Type,
pv.Volume80 as G3,
o.FirstOrderID,
o.FirstOrderDate,
o.FirstSubTotal,
o.FirstCountry,
fod.ItemCode as FirstItemCode,
fod.ItemDescription as FirstItemDescription,
fopt.PriceTypeDescription as FirstPriceTypeDescription,
o.LastOrderID,
o.LastOrderDate,
o.LastSubTotal,
o.LastCountry,
lod.ItemCode as LastItemCode,
lod.ItemDescription as LastItemDescription,
lopt.PriceTypeDescription as LastPriceTypeDescription
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID = c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID = c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID = c.CustomerID
and pv.PeriodTypeID = 2
and pv.PeriodID = (
select top 1 PeriodID
from Periods p
where p.PeriodTypeID = 2
and p.StartDate <= #now
and p.EndDate >= #now
)
left join (
select
o.CustomerID,
min(case when rn = 1 then OrderID end) as FirstOrderId,
min(case when rn = 1 then OrderDate end) as FirstOrderDate,
min(case when rn = 1 then SubTotal end) as FirstSubTotal,
min(case when rn = 1 then Country end) as FirstCountry,
min(case when nx is null then OrderID end) as LastOrderId,
min(case when nx is null then OrderDate end) as LastOrderDate,
min(case when nx is null then SubTotal end) as LastSubTotal,
min(case when nx is null then Country end) as LastCountry,
count(case when o.OrderDate >= DATEADD(month, -3, GETDATE()) then 1 end) as ThreeMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -3, GETDATE()) then BusinessVolumeTotal end) as ThreeMonthTotal,
count(case when o.OrderDate >= DATEADD(month, -6, GETDATE()) then 1 end) as SixMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -6, GETDATE()) then BusinessVolumeTotal end) as SixMonthTotal,
count(case when o.OrderDate >= DATEADD(month, -12, GETDATE()) then 1 end) as TwelveMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -12, GETDATE()) then BusinessVolumeTotal end) as TwelveMonthTotal
from (
select *,
ROW_NUMBER() over (partition by o.CustomerID order by OrderDate) as rn,
LEAD(OrderID) over (partition by o.CustomerID order by OrderDate) as nx
from Orders o
where o.OrderStatusID >= 7
and o.OrderTypeID in (1,4,8,11)
and o.OrderDate >= #timeAgo
) o
group by o.CustomerID
) o on o.CustomerID = c.CustomerID
outer apply (
select top 1
od.ItemCode,
od.ItemDescription
from OrderDetails od
order by od.BusinessVolume desc
where od.OrderID = o.FirstOrderId
) fod
outer apply (
select top 1
od.ItemCode,
od.ItemDescription
from OrderDetails od
order by od.BusinessVolume desc
where od.OrderID = o.LastOrderId
) lod
left join PriceTypes fopt on fopt.PriceTypeID = o.FirstPriceTypeID
left join PriceTypes lopt on lopt.PriceTypeID = o.LastPriceTypeID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2,3);
I'm also going to give you a row-numbering version, as judging by your execution plan, it may actually be better. You need to try both
select
c.CustomerID,
c.FirstName + ' ' + c.LastName as Name,
cs.CustomerStatusDescription as Status,
ct.CustomerTypeDescription as Type,
pv.Volume80 as G3,
o.FirstOrderID,
o.FirstOrderDate,
o.FirstSubTotal,
o.FirstCountry,
o.FirstItemCode,
o.FirstItemDescription,
o.FirstPriceTypeDescription,
o.LastOrderID,
o.LastOrderDate,
o.LastSubTotal,
o.LastCountry,
o.LastItemCode,
o.LastItemDescription,
o.LastPriceTypeDescription
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID = c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID = c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID = c.CustomerID
and pv.PeriodTypeID = 2
and pv.PeriodID = (
select top 1 PeriodID
from Periods p
where p.PeriodTypeID = 2
and p.StartDate <= #now
and p.EndDate >= #now
)
left join (
select
o.CustomerID,
min(case when rn = 1 then o.OrderID end) as FirstOrderId,
min(case when rn = 1 then o.OrderDate end) as FirstOrderDate,
min(case when rn = 1 then o.SubTotal end) as FirstSubTotal,
min(case when rn = 1 then o.Country end) as FirstCountry,
min(case when rn = 1 then od.ItemCode end) as FirstItemCode,
min(case when rn = 1 then od.ItemDescription end) as FirstItemDescription,
min(case when rn = 1 then opt.PriceTypeDescription end) as FirstPriceTypeDescription,
min(case when nx is null then o.OrderID end) as LastOrderId,
min(case when nx is null then o.OrderDate end) as LastOrderDate,
min(case when nx is null then o.SubTotal end) as LastSubTotal,
min(case when nx is null then o.Country end) as LastCountry,
min(case when nx is null then od.ItemCode end) as LastItemCode,
min(case when nx is null then od.ItemDescription end) as LastItemDescription,
min(case when nx is null then opt.PriceTypeDescription end) as LastPriceTypeDescription,
count(case when o.OrderDate >= DATEADD(month, -3, GETDATE()) then 1 end) as ThreeMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -3, GETDATE()) then BusinessVolumeTotal end) as ThreeMonthTotal,
count(case when o.OrderDate >= DATEADD(month, -6, GETDATE()) then 1 end) as SixMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -6, GETDATE()) then BusinessVolumeTotal end) as SixMonthTotal,
count(case when o.OrderDate >= DATEADD(month, -12, GETDATE()) then 1 end) as TwelveMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -12, GETDATE()) then BusinessVolumeTotal end) as TwelveMonthTotal
from (
select *,
ROW_NUMBER() over (partition by o.CustomerID order by OrderDate) as rn,
LEAD(OrderID) over (partition by o.CustomerID order by OrderDate) as nx
from Orders o
where o.OrderStatusID >= 7
and o.OrderTypeID in (1,4,8,11)
and o.OrderDate >= #timeAgo
) o
left join PriceTypes opt on opt.PriceTypeID = o.PriceTypeID
join (
select *,
ROW_NUMBER() over (partition by od.OrderID order by od.BusinessVolume desc) as rn
from OrderDetails od
) od on od.OrderID = o.OrderId
where rn = 1 or nx is null
) o on o.CustomerID = c.CustomerID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2,3);
Good indexing is essential to good performance. I would expect roughly the following indexes on your tables, either clustered or non-clustered (clustered indexed INCLUDE every other column automatically), you can obviously add other INCLUDE columns if needed:
Customers (CustomerID) INCLUDE (FirstName, LastName)
CustomerTypes (CustomerTypeID) INCLUDE (CustomerTypeDescription)
CustomerStatuses (CustomerStatusID) INCLUDE (CustomerTypeDescription)
PeriodVolumes (CustomerID) INCLUDE (Volume80)
Periods (PeriodTypeID, StartDate, PeriodID) INCLUDE (EndDate) -- can swap Start and End
Orders (CustomerID, OrderDate) INCLUDE (OrderStatusID, SubTotal, Country, BusinessVolumeTotal)
OrderDetails (OrderID, BusinessVolume) INCLUDE (ItemCode ItemDescription)
PriceTypes (PriceTypeID) INCLUDE (PriceTypeDescription)
You should think carefully about INNER vs LEFT joins, because the optimizer can more easily move around an INNER join.
Note also, that DISTINCT is not a function, it is calculated over an entire set of columns. Generally, one can assume that if a DISTINCT is in the query then the joins have not been thought through properly.
I am trying to compare sales from different quarters by stores in adventureworks 2014.
My code is as following
SELECT store.BusinessEntityID as "StoreID", "Name"
from sales.SalesOrderHeader
inner join sales.customer
on sales.SalesOrderHeader.CustomerID = sales.customer.CustomerID
inner join sales.store
on sales.customer.StoreID= sales.store.BusinessEntityID
where (OrderDate between '2014-01-01' and '2014-03-31')
and (OnlineOrderFlag = 0)
group by store.BusinessEntityID, "Name"
having format(round(sum(subtotal),2),'###,###,###.##') > format(round(sum(subtotal),2),'###,###,###.##') IN
(
SELECT store.BusinessEntityID as "StoreID",
format(round(sum(subtotal),2),'###,###,###.##') "Ventes2013_Q4"
from sales.SalesOrderHeader
inner join sales.customer on sales.SalesOrderHeader.CustomerID = sales.customer.CustomerID
inner join sales.store on sales.customer.StoreID= sales.store.BusinessEntityID
where (orderdate between '2013-10-01' and '2013-12-31') and OnlineOrderFlag = 0
group by store.BusinessEntityID
)
Both my main query and my subquery work individually, but I am not able to specify the column i want to compare it with in the second part of the having. When trying IN it gives me an incorrect syntax, and it does not work when using the column name either. It also did not work when adding a sales column in the main query
Can anyone point me in the right direction?
This is a simple way to do it (translating from your code)
SELECT StoreID, Name
FROM (
SELECT store.BusinessEntityID as "StoreID", "Name", sum(stubtotal) as sum
from sales.SalesOrderHeader
inner join sales.customer on sales.SalesOrderHeader.CustomerID = sales.customer.CustomerID
inner join sales.store on sales.customer.StoreID= sales.store.BusinessEntityID
where (OrderDate between '2014-01-01' and '2014-03-31') and (OnlineOrderFlag = 0)
group by store.BusinessEntityID, "Name"
) first
JOIN (
SELECT BusinessEntityID, sum(subtotal) as sum
from sales.SalesOrderHeader
inner join sales.customer on sales.SalesOrderHeader.CustomerID = sales.customer.CustomerID
inner join sales.store on sales.customer.StoreID= sales.store.BusinessEntityID
where (orderdate between '2013-10-01' and '2013-12-31') and OnlineOrderFlag = 0
group by store.BusinessEntityID
) as sub on sub.BusinessEntityID = furst.BusinessEntityID
AND sub.sum < first.sum
However there is a better way in sql server... one sec.
This will give to you totals by quarter and store:
SELECT
store.BusinessEntityID as StoreID,
Name,
year(OrderDate) as y,
quarter(orderdate) as q,
sum(stubtotal) as sum
from sales.SalesOrderHeader
inner join sales.customer on sales.SalesOrderHeader.CustomerID = sales.customer.CustomerID
inner join sales.store on sales.customer.StoreID= sales.store.BusinessEntityID
group by store.BusinessEntityID, Name, year(OrderDate), quarter(orderdate)
Now create a view
CREATE VIEW quarterview as
SELECT
store.BusinessEntityID as StoreID,
Name,
year(OrderDate) as y,
quarter(orderdate) as q,
sum(stubtotal) as sum
from sales.SalesOrderHeader
inner join sales.customer on sales.SalesOrderHeader.CustomerID = sales.customer.CustomerID
inner join sales.store on sales.customer.StoreID= sales.store.BusinessEntityID
group by store.BusinessEntityID, Name, year(OrderDate), quarter(orderdate)
"I want to show the stores that have more sales in 2014Q1 than in 2013Q4"
SELECT *
FROM quarterview first
WHERE q = 1 and year = 2014
then
SELECT *
FROM quarterview first
JOIN quarterview second ON second.q = 4 and second.year=2013 and first.sum > second.sum
WHERE first.q = 1 and first.year = 2014
With your fab new quarter view all the client request are easy to do with just a couple of lines of code.
With Ventes2014_Q1 as(
SELECT store.BusinessEntityID as "StoreID", "Name",format(round(sum(subtotal),2),'###,###,###.##') "Ventes2014_Q1"
from sales.SalesOrderHeader
inner join sales.customer
on sales.SalesOrderHeader.CustomerID = sales.customer.CustomerID
inner join sales.store
on sales.customer.StoreID= sales.store.BusinessEntityID
where (OrderDate between '2014-01-01' and '2014-03-31')
and (OnlineOrderFlag = 0)
group by store.BusinessEntityID, "Name"
),Ventes2013_Q4
(
SELECT store.BusinessEntityID as "StoreID",
format(round(sum(subtotal),2),'###,###,###.##') "Ventes2013_Q4"
from sales.SalesOrderHeader
inner join sales.customer on sales.SalesOrderHeader.CustomerID = sales.customer.CustomerID
inner join sales.store on sales.customer.StoreID= sales.store.BusinessEntityID
where (orderdate between '2013-10-01' and '2013-12-31') and OnlineOrderFlag = 0
group by store.BusinessEntityID
)
select "StoreID", "Name" from Ventes2014_Q1
where "StoreID" exists
(select "StoreID" from Ventes2013_Q4 where Ventes2013_Q4."StoreID"=Ventes2014_Q1."StoreID" and Ventes2014_Q1."Ventes2014_Q1">Ventes2013_Q4."Ventes2013_Q4")
I am writing a query to summarize sales by month. My problem is that my query only returns records for months with sales. For example, I am looking over a 15 month range. But for one specific part in the example below only 3 of the 15 months had sales.
I'm hoping to have 15 records show up and the other ones have 0's for sales. The reason I am hoping for the additional records is I want to take the standard deviation of this, and dropping records impacts that calculation.
Sample Code:
SELECT I.PartNumber as PartNumber,
YEAR(O.CreateDate) as CreateDateYear,
MONTH(O.CreateDate) as CreateDateMonth,
COUNT(*) as TotalDemand
FROM OrderDetails OD
INNER JOIN Orders O on O.Id = OD.OrderId
INNER JOIN Items I on I.Id = OD.ItemId
WHERE
O.CreateDate >= '1-1-2016'
AND O.CreateDate <= '3-31-2017'
AND I.PartNumber = '5144831-2'
GROUP BY I.PartNumber, YEAR(O.CreateDate) , MONTH(O.CreateDate);
Sample Current Output:
Part # | Year | Month | Demand
5144831-2 2017 1 1
5144831-2 2017 2 3
5144831-2 2016 3 1
Desired Output:
I would want an additional row such as:
5144831-2 2016 11 0
To show there were no sales in Nov 2016.
I do have a temp table #_date_array2 with the possible months/years, I think I need help incorporating a LEFT JOIN.
If you want to use left join, you would not be able to use it directly with the inner join. You can do the inner join inside the parenthesis and then do the left join outside to avoid messing with the results of left join. Try this:
SELECT Z.PartNumber as PartNumber,
YEAR(O.CreateDate) as CreateDateYear,
MONTH(O.CreateDate) as CreateDateMonth,
COUNT(Z.OrderId) as TotalDemand
FROM Orders O
LEFT JOIN
(
SELECT OrderId, PartNumber
FROM
OrderDetails OD
INNER JOIN Items I ON I.Id = OD.ItemId
AND I.PartNumber = '5144831-2'
) Z
ON O.Id = Z.OrderId
AND O.CreateDate >= '1-1-2016'
AND O.CreateDate <= '3-31-2017'
GROUP BY Z.PartNumber, YEAR(O.CreateDate) , MONTH(O.CreateDate);
To get a count of 0 for months with no order, avoid using count(*) and use count(OrderId) as given above.
Note - You will have to make sure the Orders table has all months and years available i.e. if there is no CreateDate value of, say, November 2016 in the Orders table(left table in the join), the output will also not produce this month's entry.
Edit:
Can you try this:
SELECT Z.PartNumber as PartNumber,
YEAR(O.CreateDate) as CreateDateYear,
MONTH(O.CreateDate) as CreateDateMonth,
COUNT(O.OrderId) as TotalDemand
FROM Orders O
RIGHT JOIN
(
SELECT OrderId, PartNumber
FROM
OrderDetails OD
INNER JOIN Items I ON I.Id = OD.ItemId
AND I.PartNumber = '5144831-2'
) Z
ON O.Id = Z.OrderId
AND O.CreateDate >= '1-1-2016'
AND O.CreateDate <= '3-31-2017'
GROUP BY Z.PartNumber, YEAR(O.CreateDate) , MONTH(O.CreateDate);
Assuming you have sales of something in every month, the simplest solution is to switch to conditional aggregation:
SELECT '5144831-2' as PartNumber,
YEAR(O.CreateDate) as CreateDateYear,
MONTH(O.CreateDate) as CreateDateMonth,
SUM(CASE WHEN I.PartNumber = '5144831-2' THEN 1 ELSE 0 END) as TotalDemand
FROM OrderDetails OD INNER JOIN
Orders O
ON O.Id = OD.OrderId INNER JOIN
Items I
ON I.Id = OD.ItemId
WHERE O.CreateDate >= '2016-01-01' AND
O.CreateDate <= '2017-03-31'
GROUP BY YEAR(O.CreateDate) , MONTH(O.CreateDate);
Note: This is something of a hack for solving the problem. More robust solutions involve generating the dates and using LEFT JOIN (or similar functionality). However, this is often the fastest way to get the result.
based on all of your comments on other posts etc it seems like you have a table that has a date range you want and you want to be able to run the analysis for multiple/all of the part numbers. So the main issue is you will need a cartesian join between your date table and partnumbers that were sold during that time in order to accomplish you "0"s when not sold.
;WITH cteMaxMinDates AS (
SELECT
MinDate = MIN(DATEFROMPARTS(CreateDateYear,CreateDateMonth,1))
,MaxDate = MAX(DATEFROMPARTS(CreateDateYear,CreateDateMonth,1))
FROM
#_date_array2
)
;WITH cteOrderDetails AS (
SELECT
d.CreateDateYear
,d.CreateDateMonth
,I.PartNumber
FROM
#_date_array2 d
INNER JOIN Orders o
ON d.CreateDateMonth = MONTH(o.CreateDate)
AND d.CreateDateYear = YEAR(o.CreateDate)
INNER JOIN OrderDetails od
ON o.Id = od.OrderId
INNER JOIN Items i
ON od.ItemId = i.Id
AND i.PartNumber = '5144831-2'
)
, cteDistinctParts AS (
SELECT DISTINCT PartNumber
FROM
cteOrderDetails
)
SELECT
d.CreateDateYear
,d.CreateDateMonth
,I.PartNumber
,COUNT(od.PartNumber) as TotalDemand
FROM
#_date_array2 d
CROSS JOIN cteDistinctParts p
LEFT JOIN cteOrderDetails od
ON d.CreateDateYear = od.CreateDateYear
AND d.CreateDateMonth = od.CreateDateMonth
AND p.PartNumber = od.PartNumber
GROUP BY
d.CreateDateYear
,d.CreateDateMonth
,I.PartNumber
To get ALL part numbers simply remove AND i.PartNumber = '5144831-2' join condition.
The exact error I am getting is: "Unknown column 'trans_paid' in 'where clause'"
My query ($from_date and $to_date are correctly formatted):
SELECT
o.order_id,
o.order_po_no,
o.order_ship_date,
acct.acct_company,
SUM( ROUND( i.item_qty * i.item_price, 2 ) ) AS item_amount, (
SELECT SUM( trans_amount )
FROM transactions
WHERE order_id = o.order_id
AND trans_pending =0
AND trans_date >= '$from_date'
AND trans_date <= '$to_date'
) AS trans_paid
FROM orders AS o
INNER JOIN accounts AS acct ON o.acct_id = acct.acct_id
INNER JOIN items AS i ON o.order_id = i.order_id
WHERE (o.order_status =7 or o.order_status = 4)
AND trans_paid IS NOT NULL
AND acct.is_wholesale =1
AND acct.acct_company LIKE '%".$_POST['company']."%'
GROUP BY o.order_id
You can't reference aliased columns in the SELECT cause from the WHERE clause. You'll have to wrap it in a subquery if you want to filter on it.
Standard SQL doesn't allow you to refer to aggregate columns in the WHERE clause. You need to move the trans_paid condition to a HAVING clause to make it work.
Change
WHERE (o.order_status =7 or o.order_status = 4)
AND trans_paid IS NOT NULL
AND acct.is_wholesale =1
AND acct.acct_company LIKE '%".$_POST['company']."%'
GROUP BY o.order_id
to
WHERE (o.order_status =7 or o.order_status = 4)
AND acct.is_wholesale =1
AND acct.acct_company LIKE '%".$_POST['company']."%'
GROUP BY o.order_id
HAVING trans_paid IS NOT NULL
An alias name to be used in the Where clause has to be declared in the FROM clause...You simply renamed a column not actually rename an object such as a table.
use this query:
SELECT
o.order_id,
o.order_po_no,
o.order_ship_date,
acct.acct_company,
SUM( ROUND( i.item_qty * i.item_price, 2 ) ) AS item_amount,
trans_paid
FROM orders AS o
LEFT JOIN
(SELECT SUM( trans_amount ) AS trans_paid
FROM
WHERE trans_pending =0
AND trans_date >= '$from_date'
AND trans_date <= '$to_date'
) temp
USING order_id
INNER JOIN accounts AS acct ON o.acct_id = acct.acct_id
INNER JOIN items AS i ON o.order_id = i.order_id
WHERE (o.order_status =7 or o.order_status = 4)
AND trans_paid IS NOT NULL
AND acct.is_wholesale =1
AND acct.acct_company LIKE '%".$_POST['company']."%'
GROUP BY o.order_id