SQL Server Query Indicating Amount sold over 3 different time periods - sql

I am trying to write a SQL query that selects from a table some data. The data I want includes how many times an item has sold and the goal is to have columns have that indicate how many times the item has sold in 30,60, and 90 days. I can write 3 separate queries that do the job but I would like to write one query so the data is all on one resulting table. This is what I have so far:
DECLARE #30daysago DATETIME = DATEADD(DAY,-30,CAST(GETDATE() AS DATE));
DECLARE #90daysago DATETIME = DATEADD(DAY,-90,CAST(GETDATE() AS DATE));
Set NOCOUNT ON
Select company_info_1 from setup
select ii.itemnum, i.itemname,i.price, d.description, count(ii.itemnum)
from invoice_itemized ii
join invoice_totals IT on it.invoice_number=ii.invoice_number
join inventory i on i.itemnum=ii.itemnum
join departments d on d.dept_id=i.dept_id
where it.datetime > #30daysago and len(ii.itemnum) >4
group by ii.itemnum, i.itemname, d.description, i.price
order by count(ii.itemnum) desc

Use a single query with conditional aggregation:
SELECT
ii.itemnum,
i.itemname,
i.price,
d.description,
COUNT(ii.itemnum) AS total,
COUNT(CASE WHEN it.datetime > DATEADD(DAY, -30, CAST(GETDATE() AS DATE))
THEN ii.itemnum END) AS 30daysago,
COUNT(CASE WHEN it.datetime > DATEADD(DAY, -90, CAST(GETDATE() AS DATE))
THEN ii.itemnum END) AS 90daysago
FROM invoice_itemized ii
INNER JOIN invoice_totals IT
ON it.invoice_number = ii.invoice_number
INNER JOIN inventory I
ON i.itemnum = ii.itemnum
INNER JOIN departments d
ON d.dept_id = i.dept_id
WHERE
LEN(ii.itemnum) > 4
GROUP BY
ii.itemnum,
i.itemname,
d.description,
i.price
ORDER BY
ii.itemnum DESC;

Related

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.

Invalid subquery, trying to get the customer ID that goes along with the invoice

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

SQL - Tracking Monthly Sales

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.

SQL Server - Subtracting results of two SQL Queries

I'm trying to results of two queries using Subquery in SQL Server.
What I'm trying to achieve is to Calculate number of customers that are part of "ProductX Promotion" and subtract the count of customers that are part of "ProductX Promotion" and have actually purchased ProductX within last 60 days. This gives me the count of enrolled customers who have not made ProductX purchase for better insights for marketing.
Initially I only had COUNT() hence it was easy to subtract the two. Now requirement is that the count broken down by State and Zip level of the customer. The problem arises here as I cannot use direct Subtraction, I tried NOT EXISTS but didn't work, I tried JOINs but still no luck. I know the solution is pretty simple but I cannot think of any.
Here's the original code with comments on what particular columns mean,
SELECT
(SELECT COUNT (DISTINCT (c.CustomerNumber))
FROM Customer c
INNER JOIN
TransactionDetail t
ON
c.CustomerNumber = t.CustomerNumber
WHERE c.ProductXPref = 1 --Indicates if Customer was part of ProductX promotion program.
AND t.TransactionDate > DATEADD(d, -60, getdate()))
-
(SELECT COUNT (DISTINCT c.CustomerNumber)
FROM Customer c
INNER JOIN
TransactionDetail t
ON
c.CustomerNumber = t.CustomerNumber
WHERE t.ProductXIndicator = 1 --Indicates if ProductX was purchased
AND t.TransactionDate > DATEADD(DD, -60, getdate())
AND c.ProductXPref = 1 --Indicates if Customer was part of ProductX promotion program.
) AS 'Column Name' INTO #TempTable1
Here's what I'm trying to implement,
SELECT
(SELECT c.Zip, c.State, COUNT (DISTINCT (c.CustomerNumber))
FROM Customer c
INNER JOIN
TransactionDetail t
ON
c.CustomerNumber = t.CustomerNumber
WHERE c.ProductXPref = 1
AND t.TransactionDate > DATEADD(d, -60, getdate())
GROUP BY c.Zip, c.State)
-
(SELECT c.Zip, c.State, COUNT (DISTINCT c.CustomerNumber)
FROM Customer c
INNER JOIN
TransactionDetail t
ON
c.CustomerNumber = t.CustomerNumber
WHERE t.ProductXIndicator = 1
AND t.TransactionDate > DATEADD(DD, -60, getdate())
AND c.ProductXPref = 1
GROUP BY c.Zip, c.State) AS 'Column Name' INTO #TempTable1
This is the error I see,
Msg 116, Level 16, State 1, Line 129
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
Any pointers will help. Thanks.
I think you are making this much more complicated than it needs to be -- the first query can be written as
SELECT COUNT(DISTINCT c.CustomerNumber)
FROM Customer c
INNER JOIN TransactionDetail t ON c.CustomerNumber = t.CustomerNumber
WHERE
c.ProductXPref = 1 --Indicates if Customer was part of ProductX promotion program.
AND t.TransactionDate > DATEADD(d, -60, getdate()))
AND t.ProductXIndicator <> 1
As you can see if you just don't include these rows in the original count it is the same as doing another query and subtracting.
So the second is simply
SELECT c.Zip, c.State, COUNT(DISTINCT c.CustomerNumber)
FROM Customer c
INNER JOIN TransactionDetail t ON c.CustomerNumber = t.CustomerNumber
WHERE
c.ProductXPref = 1 --Indicates if Customer was part of ProductX promotion program.
AND t.TransactionDate > DATEADD(d, -60, getdate()))
AND t.ProductXIndicator <> 1
GROUP BY c.Zip, c.State
This should work:
SELECT
c.Zip, c.State,COUNT (DISTINCT c.CustomerNumber)
FROM
Customer c
INNER JOIN TransactionDetail t
ON
c.CustomerNumber = t.CustomerNumber AND
c.ProductXPref = 1 --Indicates if Customer was part of ProductX promotion program.
AND t.TransactionDate > DATEADD(DD, -60, getdate())
WHERE t.ProductXIndicator <> 1
GROUP BY c.Zip, c.State
after some validation the numbers did not match what I expect those to be, hence I have tried different logic which worked fine and returned what I wanted. So posting the answer here, again thanks for all the help.
SELECT d.State, d.Zip, COUNT(DISTINCT(d.CustomerNumber)) AS 'Active Customers'
INTO #ProductDifference
FROM Customer c
INNER JOIN
TransactionDetail t
ON
c.CustomerNumber = t.CustomerNumber
WHERE d.ProductXPref = 1
AND t.TransactionDate > DATEADD(d, -60, getdate())
AND c.CustomerNumber
NOT IN
(SELECT DISTINCT d.[CustomerNumber]
FROM Customer d
INNER JOIN
TransactionDetail f
ON
d.CustomerNumber = f.CustomerNumber
WHERE f.ProductXIndicator = 1
AND f.TransactionDate > DATEADD(DD, -60, getdate())
AND d.ProductXPref = 1)
GROUP BY c.State, c.Zip

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.