Reputation: 5471
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
order_date DATE,
customer VARCHAR(255)
);
INSERT INTO customers
(order_date, customer)
VALUES
('2020-05-10', 'user_01'),
('2020-05-15', 'user_01'),
('2020-05-18', 'user_02'),
('2020-05-26', 'user_03'),
('2020-06-12', 'user_04'),
('2020-06-19', 'user_05'),
('2020-06-23', 'user_06'),
('2021-05-09', 'user_01'),
('2021-05-17', 'user_07'),
('2021-06-03', 'user_04'),
('2021-06-18', 'user_05'),
('2021-06-20', 'user_08');
Expected Result:
churn_date | customer |
--------------|--------------|----
2021-05-18 | user_02 |
2021-05-26 | user_03 |
--------------|--------------|-----
2021-06-23 | user_06 |
I want to extract all customers that
a) had an order exactly 12 months ago and
b) have not ordered again since this order.
For a single month I am able to to this with this query:
SELECT
(c1.order_date + interval '12 month')::date as churn_date,
c1.customer
FROM customers c1
WHERE c1.order_date BETWEEN '2020-05-01 00:00:00' AND '2020-05-31 23:59:59'
AND NOT EXISTS
(SELECT
c2.customer
FROM customers c2
WHERE c2.order_date BETWEEN '2020-06-01 00:00:00' AND '2021-05-31 23:59:59'
AND c2.customer = c1.customer)
ORDER BY 1,2;
However, instead of querying each month seperately I would prefer a solution that iterates through the data and gives me the result for multiple months.
How do I need to modify my query to achieve this?
Upvotes: 1
Views: 313
Reputation: 1269623
Check if a customer has not placed another order in the 12 months after his last order
This answers the question. Just use GROUP BY
with HAVING
:
SELECT c.customer,
MAX(c.order_date + interval '12 month')::date as churn_date
FROM customers c
GROUP BY c.customer
HAVING MAX(c.order_date) < CURRENT_DATE - interval '12 month';
Apparently, the question that you want answered is a bit different from the one you describe in English. It is something more along the lines of:
For each calendar month, which users had there last order in the calendar month exactly 12 months before that. The "churn date" is then exactly 12 months after the last purchase date.
A little bit different, but the query is:
SELECT gs.month, c.customer, MAX(c.order_date + interval '12 month')::date as churn_date
FROM customers c CROSS JOIN
GENERATE_SERIES('2021-05-01'::date, '2021-06-01'::date, interval '1 month') gs(month)
GROUP BY gs.month, c.customer
HAVING DATE_TRUNC('month', MAX(c.order_date)) = DATE_TRUNC('month', gs.month) - interval '12 month';
Here is a db<>fiddle.
Upvotes: 1