I have 3 tables as shown below.
1.
Services
--------------------------
ServiceID (PK,int)
SalesID (FK, int)
ItemName (varchar)
FirstServiceDate (datetime)
2.
Sales
------------------
SalesID (PK,int)
CustomerID (FK,int)
SerialNo (varchar)
ModelNo (varchar)
and 3.
Customer
--------------------
CustomerID (PK,int)
CustName (varchar)
I am trying to get the details from service, sales and customer tables 1 week before FirstServiceDate's date. I tried below query. but it is not working as expected.
Select B.SalesID, C.CustomerID, C.CustomerName, B.SerialNo, B.ModelNo,
A.FirstServiceDate from Services A inner join Sales B on
A.SalesID = B.SalesID inner join Customer C on
B.CustomerID = C.CustomerID and
FirstServiceDate = DATEADD(week, 1, GETDATE())
If the FirstServiceDate is 2 March 2019, then 1 week before (i.e., on 23rd Feb - today). I should get notification.
Any help would be much appreciated.
If the FirstServiceDate column is of type DateTime then you need to convert it to Date:
select
B.SalesID, C.CustomerID, C.CustomerName,
B.SerialNo, B.ModelNo, A.FirstServiceDate
from Services A inner join Sales B
on A.SalesID = B.SalesID
inner join Customer C
on B.CustomerID = C.CustomerID
where convert(date, A.FirstServiceDate) = convert(date, dateadd(week, 1, getdate()))
this way the time part of the dates is eliminated and only the date part is compared.
Edit:
If you want the services scheduled for the next week, change the where part like this:
where
convert(date, A.FirstServiceDate)
between
convert(date, getdate())
and
convert(date, dateadd(week, 1, getdate()))
Related
I have a table 'CSALES' having columns such as customerid,transactiondate,quantity,price. I'm trying to find customers who have not been active in 1 month from a list of dates present in the transactiondate column. I've tried the following code but I'm unsure about the approach and the code is giving a compilation error
SELECT C.CUSTOMERID
FROM CSALES C
WHERE C.CUSTOMERID NOT IN
(
SELECT CS.CUSTOMERID FROM CSALES as CS
WHERE CS.TRANSACTIONDATE > DATEADD(month, -1, MAX(CS.TRANSACTIONDATE )
);
I'm getting the following error
SQL compilation error: Invalid aggregate function in where clause [MAX(CS.TRANSACTIONDATE)]
What changes should I make in the code to reflect the requirement? Would MAX(date) be a right approach ?
SELECT CUSTOMERID
FROM
CSSALES
GROUP BY CUSTOMERID
HAVING
MAX(TRANSACTIONDATE) < ADD_MONTHS(CURRENT_DATE(),-1)
Shawnt00 is right the max date in the transaction table is irrelevant if you just want any customer that hasn't been active in 1 calendar month.
In snowflake use CURRENT_DATE() to get the date portion of Today then ADD_MONTHS(date,int) to get months. Other functions work two but these are pretty easy. If you only want customers to remove duplicate CUSTOMERIDS group by the column.
I think I am about to just repeat Matt's code, but...
With a CTE for some test data:
WITH CSALES(CUSTOMERID, TRANSACTIONDATE) as (
SELECT * FROM VALUES
(1, '2022-05-08'::date), -- to recent
(1, '2021-05-08'::date),
(2, '2021-05-08'::date), -- old enough
(2, '2020-05-08'::date)
)
We can use HAVING for a post aggregation filter.
SELECT C.CUSTOMERID, MAX(C.TRANSACTIONDATE) as last_trans
FROM CSALES C
GROUP BY 1
HAVING last_trans < DATEADD(month,-1,current_date());
As Matt noted there are few ways to find the "one month ago today" he used ADD_MONTHS, I have used DATEADD
CUSTOMERID
LAST_TRANS
2
2021-05-08
Now this code works the same as:
SELECT CUSTOMERID
FROM (
SELECT C.CUSTOMERID, MAX(C.TRANSACTIONDATE) as last_trans
FROM CSALES C
GROUP BY 1
)
WHERE last_trans < DATEADD(month,-1,current_date());
which gives:
CUSTOMERID
2
Albeit we now have hidden away the last transaction, if that was what was wanted, and added some extra select layers for no high level value.
And thus if we want to hide the last_tran in the HAVING version, we can because we have already working code, we can just push the MAX into the HAVING (and we have Matt's code)
SELECT C.CUSTOMERID
FROM CSALES C
GROUP BY 1
HAVING MAX(C.TRANSACTIONDATE) < DATEADD(month,-1,current_date());
which gives for the demo code:
CUSTOMERID
2
Date Options:
There are a couple ways to alter date/time, depending how you like to order you logic, I tend to prefer DATEADD:
SELECT
current_date() as cd_a,
CURRENT_DATE as cd_b,
DATEADD(month, -1, cd_a) as one_month_ago_a,
ADD_MONTHS(cd_a, -1) as one_month_ago_b;
gives:
CD_A
CD_B
ONE_MONTH_AGO_A
ONE_MONTH_AGO_B
2022-05-07
2022-05-07
2022-04-07
2022-04-07
SELECT
C.CUSTOMERID
FROM
CSALES C
GROUP BY
C.CUSTOMERID
HAVING
MAX(C.TRANSACTIONDATE)
<
DATEADD(
month,
-1,
(SELECT MAX(TRANSACTIONDATE) FROM CSALES)
)
Or, assuming you have a customer table...
SELECT
*
FROM
CUSTOMER C
WHERE
NOT EXISTS (
SELECT *
FROM CSALES CS
WHERE CS.CUSTOMERID = C.ID
AND CS.TRANSACTIONDATE >= DATEADD(
month,
-1,
(SELECT MAX(TRANSACTIONDATE) FROM CSALES)
)
)
Demo : dbfiddle
there are multiple possibilities, you must check which is faster
SELECT C.CUSTOMERID
FROM CSALES C
WHERE C.CUSTOMERID NOT IN
(
SELECT CS.CUSTOMERID FROM CSALES as CS CROSS JOIN (SELECT MAX(TRANSACTIONDATE) maxdate FROM CSALES) t1
WHERE CS.TRANSACTIONDATE > DATEADD(month, -1, maxdate)
);
GO
| CUSTOMERID |
| ---------: |
| 4 |
SELECT DISTINCT C.CUSTOMERID
FROM CSALES C CROSS JOIN (SELECT MAX(TRANSACTIONDATE) maxdate FROM CSALES) t1
WHERE NOT EXISTS (SELECT 1 FROM CSALES WHERE CUSTOMERID = c.CUSTOMERID AND TRANSACTIONDATE > DATEADD(month, -1, maxdate))
;
GO
| CUSTOMERID |
| ---------: |
| 4 |
db<>fiddle here
I am trying to write a query that returns:
The total amount of transactions that occurred before a date range, for a particular customer.
The total amount of transactions that occurred within a date range, for a particular customer.
The total amount of payments that occurred before a date range, for a particular customer.
The total amount of payments that occurred within a date range, for a particular customer.
To that end, I've come up with the following query.
declare #StartDate DATE = '2016-08-01'
declare #EndDate DATE = '2016-08-31'
declare #BillingCategory INT = 0
select c.Id, c.Name, c.StartingBalance,
(select coalesce(sum(Amount), 0) from Transactions t where t.CustomerId = c.Id and t.[Date] < #StartDate) xDebits,
(select coalesce(sum(Amount), 0) from Transactions t where t.CustomerId = c.Id and t.[Date] >= #StartDate and t.[Data] <= #EndDate) Debits,
(select coalesce(sum(Amount), 0) from Payments p where p.CustomerId = c.Id and p.[Date] < #StartDate) xCredits,
(select coalesce(sum(Amount), 0) from Payments p where p.CustomerId = c.Id and p.[Date] >= #StartDate and p.[Date] <= #EndDate) Credits
from customers c
where c.BillingCategory in (0,1,2,3,4,5)
This query seems to give the results I want. I used the subqueries because I couldn't seem to figure out how to accomplish the same thing using JOINs. But I have a few questions.
Does this query retrieve the transaction and payment data for every single customer before filtering it according to my WHERE condition? If so, that seems like a big waste. Can that be improved?
I'd also like to add a GROUP BY to total each payment and transaction column by BillingCategory. But how can you add a GROUP BY clause here when the SELECTed columns are limited to aggregate functions if they are not in the GROUP BY clause?
The Transactions and Payments tables both have foreign keys to the Customers table.
Sample data (not real)
Customers:
Id Name BillingCategory
----- ------- ---------------
1 'ABC' 0
2 'DEF' 1
3 'GHI' 0
Transactions:
Id CustomerId Date Amount
----- ---------- ------------ ------
1 2 '2016-08-01' 124.90
2 2 '2016-08-04' 37.23
3 1 '2016-08-27' 450.02
Payments:
Id CustomerId Date Amount
----- ---------- ------------ ------
1 1 '2016-09-01' 50.00
2 1 '2016-09-23' 75.00
3 2 '2016-09-01' 100.00
You could build your sums seperately for Transactions and Payments in a CTE and then join them together:
WITH
CustomerTransactions AS
(
SELECT CustomerId,
SUM(CASE WHEN [Date] < #StartDate THEN 1 ELSE 0 END * COALESCE(Amount, 0)) AS xDebits,
SUM(CASE WHEN [Date] >= #StartDate THEN 1 ELSE 0 END * COALESCE(Amount, 0)) AS Debits
FROM Transactions
GROUP BY CustomerId
),
CustomerPayments AS,
(
SELECT CustomerId,
SUM(CASE WHEN [Date] < #StartDate THEN 1 ELSE 0 END * COALESCE(Amount, 0)) AS xCredits,
SUM(CASE WHEN [Date] >= #StartDate THEN 1 ELSE 0 END * COALESCE(Amount, 0)) AS Credits
FROM Payments
GROUP BY CustomerId
)
SELECT C.Id, c.Name, c.StartingBalance,
COALESCE(T.xDebits, 0) AS xDebits,
COALESCE(T.Debits, 0) AS Debits,
COALESCE(P.xCredits, 0) AS xCredits,
COALESCE(P.Credits, 0) AS Credits
FROM Custormers C
LEFT OUTER JOIN CustomerTransactions T ON T.CustomerId = C.Id
LEFT OUTER JOIN CustomerPayments P ON P.CustomerId = C.Id
WHERE C.BillingCategory IN(0, 1, 2, 3, 4, 5);
You can do with sub-queries to be more efficient. Pre-query grouped by each customer for only those customers who qualify by the categories in question. These sub-queries will always result in an at-most, 1 record per customer so you don't get a Cartesian result. Get that for your debits and credits and re-join back to your master list of customers with a left-join in case one side or the other (debits/credits) may not exist.
declare #StartDate DATE = '2016-08-01'
declare #EndDate DATE = '2016-08-31'
declare #BillingCategory INT = 0
select
c.ID,
c.Name,
c.StartingBalance,
coalesce( AllDebits.xDebits, 0 ) DebitsPrior,
coalesce( AllDebits.Debits, 0 ) Debits
coalesce( AllCredits.xCredits, 0 ) CreditsPrior,
coalesce( AllCredits.Credits, 0 ) Credits
from
customers c
LEFT JOIN
( select t.CustomerID,
sum( case when t.[Date] < #StartDate then Amount else 0 end ) xDebits,
sum( case when t.[Date] >= #StartDate then Amount else 0 end ) Debits
from
customers c1
JOIN Transactions t
on c1.CustomerID = t.CustomerID
where
c1.BillingCategory in (0,1,2,3,4,5)
group by
t.CustomerID ) AllDebits
on c.CustomerID = AllDebits.CustomerID
LEFT JOIN
( select p.CustomerID,
sum( case when p.[Date] < #StartDate then Amount else 0 end ) xCredits,
sum( case when p.[Date] >= #StartDate then Amount else 0 end ) Credits
from
customers c1
JOIN Payments p
on c1.CustomerID = p.CustomerID
where
c1.BillingCategory in (0,1,2,3,4,5)
group by
p.CustomerID ) AllCredits
on c.CustomerID = AllCredits.CustomerID
where
c.BillingCategory in (0,1,2,3,4,5)
COMMENT ADDITION
With respect to Thomas's answer, yes they are close. My version also adds the join to the customer table for the specific billing category and here is why. I don't know the size of your database, how many customers, how many transactions. If you are dealing with a large amount that DOES have performance impact, Thomas's version is querying EVERY customer and EVERY transaction. My version is only querying the qualified customers by the billing category criteria you limited.
Again, not knowing data size, if you are dealing with 100k records may be no noticeable performance. If you are dealing with 100k CUSTOMERS, could be a totally different story.
#JonathanWood, correct, but my version has each internal subquery inclusive of the cus
I'm trying to find any customers with a total of at least 4 accounts with at least 1 account that has been open in the last 6 months.
So far I'm able to get the customer and total accounts, but I'm not sure how to add a column for the number of new accounts open in the last 6 months.
SELECT CustomerID
,COUNT(AccountID) as 'Total Accounts'
FROM Customer
GROUP BY CustomerID
HAVING COUNT(AccountID) >= 3`
Here are my tables:
and
And here is what my final result should look like. so far I have CustomerID and Total Accounts.
CustomerID | Total Accounts | New Accounts |
-----------+----------------+------------- +
Ben | 3 | 1 |
EDIT: The program I'll be importing this query in doesn't allow case statements.
You can left join the new accounts. Include the filter for startdate in the join condition. For accounts, that don't satisfy that condition no record from account is joined. So the account ID is null for them and won't get counted.
SELECT c.customerid,
count(c.accountid) "Total Accounts",
count(a.accountid) "New Accounts"
FROM customer c
LEFT JOIN account a
ON a.accountid = c.accountid
AND a.startdate >= dateadd(month, -6, getdate())
GROUP BY c.customerid
HAVING count(c.accountid) >= 4
AND count(a.accountid) >= 1;
Count, like most aggregate functions, ignores nulls. I'd use this property by joining the two tables and using a case expression to return only the new accounts:
SELECT CustomerId,
COUNT(*) AS TotalAccounts,
COUNT(CASE WHEN DATEDIFF(MONTH, StartDate, GETDATE()) <= 6 THEN 1 END)
AS NewAccounts
FROM Customer c
JOIN Accounts a ON c.AccountId = a.AccountId
GROUP BY CustomerId
HAVING COUNT(*) >= 4 AND
COUNT(CASE WHEN DATEDIFF(MONTH, StartDate, GETDATE()) <= 6 THEN 1 END) > 0
This query will give you the total account count and the number that is new.
SELECT C.CustomerID,
COUNT(C.AccountID) as Total_Acc,
SUM(CASE WHEN DATEADD(MONTH,-6,GETDATE()) <= A.StarDate) THEN 1 ELSE 0 END) as New_Acct
FROM Customer C
LEFT JOIN Account A ON C.AccountID = A.AccountID
GROUP BY CustomerID
You can then use that in a sub-query to find what you want
eg
SELECT *
FROM (
SELECT C.CustomerID,
COUNT(C.AccountID) as Total_Acc,
SUM(CASE WHEN DATEADD(MONTH,-6,GETDATE()) <= A.StarDate) THEN 1 ELSE 0 END) as New_Acct
FROM Customer C
LEFT JOIN Account A ON C.AccountID = A.AccountID
GROUP BY CustomerID
) SUB
WHERE Total_Acc >= 4 and New_Acct >= 1
The program I'll be importing this query in doesn't allow case
statements.
For your stated goal of finding customers with at least 4 accounts and at least 1 account that has been opened in the last 6 months you don't need the count of recent accounts anyway. It is sufficient to get the latest account date opened and see if that was in the last 6 months.
SELECT c.CustomerId
FROM Customer c
JOIN Accounts a
ON c.AccountId = a.AccountId
GROUP BY c.CustomerId
HAVING COUNT(*) >= 4
AND MAX(a.StartDate) >= DATEADD(MONTH, -6, GETDATE())
Order Table Schema
My Access Database Query
Select * from
(
SELECT reseller.id, Max(orders.[order date]) as OrderDate
FROM Reseller INNER JOIN orders ON Reseller.ID = orders.ResellerID
group by reseller.id
)K
WHERE (((K.[OrderDate]) Not Between (Date()-1) And (Date()-18)))
To find those reseller that did not order for 18 Days.
But this is giving below records
Am I missing something ?
Try this query instead:
SELECT *
FROM
(
SELECT reseller.id, Max(orders.[order date]) AS OrderDate
FROM
Reseller
INNER JOIN
orders
ON Reseller.ID = orders.ResellerID
GROUP BY reseller.id
) K
WHERE DateDiff("d", K.[OrderDate], Date()) > 18
Try this
Select * from
(
SELECT reseller.id, Max(orders.[order date]) as OrderDate
FROM Reseller INNER JOIN orders ON Reseller.ID = orders.ResellerID
group by reseller.id
)
WHERE (((K.[OrderDate]) Not Between ( DateAdd("d", -1, Date())
And DateAdd("d", -18, Date())
)
I think when you do Date() - 1 it is subtracting 1 time internal until like subpart of second from the date, not what you intended to subtract 1 date
ALSO, did not change, but do you really want to make the top limits today - 1 day, or just today?
I have two tables, namely Price List (Table A) and Order Record (Table B)
Table A
SKU Offer Date Amt
AAA 20120115 22
AAA 20120223 24
AAA 20120331 25
AAA 20120520 28
Table B
Customer SKU Order Date
A001 AAA 20120201
B001 AAA 20120410
C001 AAA 20120531
I have to retrieve the correct pricing for each customer based on the order date. The expected output should be like this:-
Customer SKU Order Date Amt
A001 AAA 20120201 22
B001 AAA 20120410 25
C001 AAA 20120531 28
Thanks.
A left join (or NOT EXISTS subquery) can be used to ensure that the join between the two tables uses the "most recent" row from the prices table that is dated on or before the order date. I assume that's the relationship between the tables that you want to achieve:
Setup:
create table Prices (
SKU char(3) not null,
OfferDate date not null,
Amt int not null
)
go
insert into Prices (SKU, OfferDate, Amt) values
('AAA','20120115', 22),
('AAA','20120223', 24),
('AAA','20120331', 25),
('AAA','20120520', 28)
go
create table Orders (
Customer char(4) not null,
SKU char(3) not null,
OrderDate date not null
)
go
insert into Orders (Customer, SKU, OrderDate) values
('A001','AAA','20120201'),
('B001','AAA','20120410'),
('C001','AAA','20120531')
go
Query:
select
o.*, /* TODO - Explicit columns */
p.Amt
from
Orders o
inner join
Prices p
on
o.SKU = p.SKU and
o.OrderDate >= p.OfferDate
left join
Prices p_later
on
o.SKU = p_later.SKU and
o.OrderDate >= p_later.OfferDate and
p_later.OfferDate > p.OfferDate
where
p_later.SKU is null
Next time, do put up what u have tried....
anyways, here is your answer! try...
Select X.Customer , X.SKU , X.OrderDate , Y.Amt from B as X INNER JOIN A as Y ON X.Order Date= Y. Offer Date
good luck...
SELECT o.Customer, o.SKU, o.[Order Date],
(SELECT TOP 1 l.Amt
FROM PriceList l
WHERE l.[Offer Date] <= o.[Order Date] AND o.SKU = l.SKU
ORDER BY l.[Offer Date] DESC) AS Amount
FROM
Orders o
Some things may differ based on database support