SQL Server - Subtracting results of two SQL Queries - sql

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

Related

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 Server Query Indicating Amount sold over 3 different time periods

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;

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.

Date range in WHERE clause from 90 days ago to today's date

I have written the following query, but in the WHERE clause I have used specific dates for the date range. I want the date range to be calculated automatically--that is, a period of 90 days up to today's date. How can I do it?
SELECT Orders.Day,
Orders.Month,
Orders.Year,
Orders.Orders_Count,Orders.PaymentAmount,
Orders.Total_Payment_Authorized,
Orders.ShipCity,
Orders.Total_Payment_Received,
Orders.SalesTax1,
Orders.SalesTax2,
Orders.SalesTax3,
Orders.TotalShippingCost,
Orders.OrderDetails_Count,
Orders.Quantity,
Orders.COGS,
Orders.Profit,
Orders.ProfitMargin
FROM (SELECT Orders.Day,Orders.Month,Orders.Year,Orders.Orders_Count, Orders.PaymentAmount,Orders.Total_Payment_Authorized,Orders.ShipCity,Orders.Total_Payment_Received,Orders.SalesTax1,Orders.SalesTax2,Orders.SalesTax3, Orders.TotalShippingCost,OrderDetails.OrderDetails_Count, OrderDetails.Quantity,OrderDetails.COGS,OrderDetails.Profit,OrderDetails.ProfitMargin
FROM (SELECT DATEPART(DD, Orders.OrderDate) AS Day, DATEPART(MM, Orders.OrderDate) AS Month,DATEPART(YY, Orders.OrderDate) AS Year, Max(Orders.OrderDate) As OrderDate ,Max(Orders.ShipDate) As ShipDate, Count(Orders.OrderID) As Orders_Count, Sum(Orders.PaymentAmount) AS PaymentAmount, Sum(Orders.Total_Payment_Authorized) AS Total_Payment_Authorized,Orders.ShipCity AS ShipCity,
Sum(Orders.Total_Payment_Received) AS Total_Payment_Received, Sum(Orders.SalesTax1) AS SalesTax1,Sum(Orders.SalesTax2) AS SalesTax2 , Sum(Orders.SalesTax3) AS SalesTax3, Sum(Orders.TotalShippingCost) AS TotalShippingCost
FROM (((Orders WITH(NOLOCK) LEFT JOIN PaymentMethods WITH(NOLOCK) ON Orders.PaymentMethodID = PaymentMethods.PaymentMethodID)
LEFT JOIN ShippingMethods WITH(NOLOCK) ON Orders.ShippingMethodID = ShippingMethods.ShippingMethodID)
LEFT JOIN Customers WITH(NOLOCK) ON Orders.CustomerID = Customers.CustomerID)
WHERE Orders.OrderStatus <> 'Cancelled' GROUP BY Orders.ShipCity, DATEPART(DD, Orders.OrderDate),DATEPART(MM, Orders.OrderDate),DATEPART(YY, Orders.OrderDate)) Orders
INNER JOIN (SELECT DATEPART(DD, Orders.OrderDate) AS Day,DATEPART(MM, Orders.OrderDate) AS Month,DATEPART(YY, Orders.OrderDate) AS Year, Count(OrderDetails.OrderDetailID) As OrderDetails_Count,
Sum(OrderDetails.Quantity) AS Quantity, Sum(OrderDetails.Vendor_Price * OrderDetails.Quantity) AS COGS,
Case When SUM(Vendor_Price) IS NULL Then null Else Sum((Case When OrderDetails.ProductCode like 'DSC-%' Then
OrderDetails.ProductPrice - ISNULL(OrderDetails.Vendor_Price,0)
Else OrderDetails.ProductPrice - OrderDetails.Vendor_Price End) * OrderDetails.Quantity) End AS Profit,
CASE SUM(OrderDetails.ProductPrice * OrderDetails.Quantity) WHEN 0 THEN 0
ELSE ROUND(((SUM(OrderDetails.ProductPrice * OrderDetails.Quantity) - SUM(OrderDetails.Vendor_Price * OrderDetails.Quantity)) / SUM(OrderDetails.ProductPrice * OrderDetails.Quantity)) * 100, 1) END AS ProfitMargin
FROM (((Orders WITH(NOLOCK) LEFT JOIN PaymentMethods WITH(NOLOCK) ON Orders.PaymentMethodID = PaymentMethods.PaymentMethodID)
LEFT JOIN ShippingMethods WITH(NOLOCK) ON Orders.ShippingMethodID = ShippingMethods.ShippingMethodID) LEFT JOIN Customers
WITH(NOLOCK) ON Orders.CustomerID = Customers.CustomerID) LEFT JOIN OrderDetails WITH(NOLOCK) ON Orders.OrderID = OrderDetails.OrderID
WHERE Orders.OrderStatus <> 'Cancelled' AND DATEDIFF(dd, Orders.OrderDate, '03/01/2012') <= 0 AND DATEDIFF(dd, Orders.OrderDate, '06/30/2012') >= 0 AND Orders.ShipPostalCode IN ('96704','96710','96718','96719','96720','96721','96725','96726','96727','96728','96737','96738','96739','96740','96743','96745','96749','96750','96755','96760','96764','96771','96772','96773','96774','96776','96777','96778','96780','96781','96783','96785')
GROUP BY Orders.ShipCity, DATEPART(DD, Orders.OrderDate),DATEPART(MM, Orders.OrderDate), DATEPART(YY, Orders.OrderDate)) OrderDetails ON Orders.Day = OrderDetails.Day AND Orders.Month = OrderDetails.Month AND Orders.Year = OrderDetails.Year) Orders
ORDER BY Orders.Day DESC, Orders.Month DESC,Orders.Year DESC
If it is Microsoft SQL 2000 or up (05, 08, 10) then simply use:
Where YourDate >= (GetDate() - 90)
select * from mytable where mydate > date_sub(now(), interval 90 day)
If you want everything from "the start of the month 3 months before the current one" until "the end of the current month", which is what you currently actually have, you can use DATEDIFF and DATEADD together:
WHERE
Orders.OrderDate >= DATEADD(month,DATEDIFF(month,20010101,CURRENT_TIMESTAMP),'20001001') and
Orders.OrderDate < DATEADD(month,DATEDIFF(month,20010101,CURRENT_TIMESTAMP),'20010201')
Although I'm not sure what it means for an OrderDate to be in the future.
The date literals are carefully selected to have the relationship between them that we're seeking - e.g. 20001001 is the start of the month 3 months before 20010101. 20010201 is the start of the month 1 month ahead of 20010101.

Follow on - Multiple Sums in SQL Query

Thanks to Quassnoi before who gave me the answer I was looking for - this led to a seperate problem though. Current code:
SELECT i.CONCOM, COALESCE (SUM(t.LOGMINS), 0) AS TotalWithoutNew
FROM INQUIRY AS i INNER JOIN
TIMELOG AS t ON t.INQUIRY_ID = i.INQUIRY_ID INNER JOIN
PROD AS P ON i.PROD_ID = P.PROD_ID INNER JOIN
CATEGORY AS C ON P.CATEGORY_ID = C.CATEGORY_ID
WHERE (DATEPART(month, i.ESCDATE) = DATEPART(month, GETDATE()) - 1) AND (DATEPART(year, i.ESCDATE) = DATEPART(year, DATEADD(m, - 1, GETDATE()))) AND
(C.CATEGORY_ID <> '30')
GROUP BY i.CONCOM
ORDER BY TotalWithoutNew DESC
This brings back exactly what I want (C.CATEGORY_ID <> 30) is not included in the initial column marked as TotalWithoutNew. I also need the value WITH it in as well though. Is there any way to have another column called TotalWithNew that includes all CATEGORY_IDs? I am certain learning a lot of new query language today!
SELECT i.CONCOM,
COALESCE (SUM(CASE WHEN C.CATEGORY_ID = '30' THEN 0 ELSE t.LOGMINS END), 0) AS TotalAllID,
COALESCE (SUM(t.LOGMINS), 0) AS TotalWithoutNew
FROM INQUIRY AS i INNER JOIN
TIMELOG AS t ON t.INQUIRY_ID = i.INQUIRY_ID INNER JOIN
PROD AS P ON i.PROD_ID = P.PROD_ID INNER JOIN
CATEGORY AS C ON P.CATEGORY_ID = C.CATEGORY_ID
WHERE (DATEPART(month, i.ESCDATE) = DATEPART(month, GETDATE()) - 1) AND (DATEPART(year, i.ESCDATE) = DATEPART(year, DATEADD(m, - 1, GETDATE())))
GROUP BY i.CONCOM
ORDER BY TotalWithoutNew DESC
Note: I haven't tried but I hope you get the idea of using CASE WHEN ....
SELECT i.CONCOM
, COALESCE(SUM(CASE WHEN C.CATEGORY_ID <> 30 THEN t.LOGMINS END), 0)
AS TotalWithoutNew
, COALESCE(SUM(t.LOGMINS), 0) AS TotalWithNew
FROM INQUIRY AS i INNER JOIN
TIMELOG AS t ON t.INQUIRY_ID = i.INQUIRY_ID INNER JOIN
PROD AS P ON i.PROD_ID = P.PROD_ID INNER JOIN
CATEGORY AS C ON P.CATEGORY_ID = C.CATEGORY_ID
WHERE (DATEPART(month, i.ESCDATE) = DATEPART(month, GETDATE()) - 1)
AND (DATEPART(year, i.ESCDATE) = DATEPART(year, DATEADD(m, - 1, GETDATE())))
GROUP BY i.CONCOM
ORDER BY TotalWithoutNew DESC