Customer partitioning in sql query - mysql

I have a table with following format -
Customer_id Purchase_date
c1 2015-01-11
c2 2015-02-12
c3 2015-11-12
c1 2016-01-01
c2 2016-12-29
c4 2016-11-28
c4 2015-03-15
... ...
The table essentially contains customer_id with their purchase_date. The customer_id is repetitive based on the purchase made on purchase_date. The above is just a sample data and the table contains about 100,000 records.
Is there a way to partition the customer based on pre-defined category data
Category Partitioning
- Category-1: Customer who has not made purchase in last 10 weeks, but made a purchase before that
- Category-2: Customer who as not made a purchase in last 5 weeks, but made purchase before that
- Category-3: Customer who has made one or more purchase in last 4 weeks or it has been 8 weeks since the first purchase
- Category-4: Customer who has made only one purchase in the last 1 week
- Category-5: Customer who has made only one purchase
What I'm looking for is a query that tells customer and their category -
Customer_id Category
C1 Category-1
... ...
The query can adhere to - oracle, postgres, sqlserver

From your question it seems that a customer can fall in multiple categories. So lets find out the customers in each category and then take UNION of the results.
SELECT DISTINCT Customer_Id, 'CATEGORY-1' AS Category FROM mytable GROUP BY
Customer_Id HAVING DATEDIFF(ww,MAX(Purchase_date),GETDATE()) > 10
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-2' AS Category FROM mytable GROUP BY
Customer_Id HAVING DATEDIFF(ww,MAX(Purchase_date),GETDATE()) > 5
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-3' AS Category FROM mytable GROUP BY
Customer_Id HAVING DATEDIFF(ww,MAX(Purchase_date),GETDATE()) < 4 OR
DATEDIFF(ww,MIN(Purchase_date),GETDATE()) =8
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-4' AS Category FROM mytable WHERE
DATEDIFF(ww,Purchase_date,GETDATE())<=1 GROUP BY Customer_Id having
COUNT(*) =1
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-5' AS Category FROM mytable GROUP BY
Customer_Id HAVING COUNT(*) =1
ORDER BY Category
Hope this serves your purpose.
Thanks

you can use something like this
with myTab as (
SELECT Customer_id ,MIN(Purchase_date) AS Min_Purchase_date,MAX(Purchase_date) AS Max_Purchase_date
, SUM(CASE WHEN Purchase_date>= DATEADD(WEEk ,-1,GETDATE()) THEN 1 ELSE 0 END ) AS Count_LastWeek
, COUNT(*) AS Count_All
FROM Purchases_Table
GROUP BY Customer_id
)
SELECT Customer_id
, CASE WHEN Max_Purchase_date < DATEADD(WEEK,-10,GETDATE()) THEN 'Category-1'
WHEN Max_Purchase_date < DATEADD(WEEK,-5,GETDATE()) THEN 'Category-2'
WHEN Max_Purchase_date >= DATEADD(WEEK,-4,GETDATE())
OR DATEDIFF(WEEK, Min_Purchase_date,Max_Purchase_date) >= 8 THEN 'Category-3'
WHEN Count_LastWeek = 1 THEN 'Category-4'
WHEN Count_All = 1 THEN 'Category-5'
ELSE 'No Category'
END
FROM myTab

Related

MySQL Query to get each sales per month

I have 2 tables in Mysql. I want to regroup and count the Number of Orderid per month for each customer. If there is no order, I would like to add 0.
Customer Table
CustomerID
1
2
3
Order Table
OrderId CustomerID Date
1 1 2022-01-02
2 1 2022-01-04
3 2 2022-02-03
4 2 2022-03-03
Expect results
CustomerID Date CountOrderID
1 2022-01 2
2 2022-01 1
3 2022-01 0
1 2022-02 0
2 2022-02 1
3 2022-02 0
1 2022-03 0
2 2022-03 1
3 2022-03 0
How I can do this in Mysql?
SELECT customer.CustomerID,
year_month.y_m AS `Date`,
COUNT(order.OrderId) AS CountOrderID
FROM customer
CROSS JOIN (
SELECT DISTINCT DATE_FORMAT(`date`, '%Y-%m') AS y_m
FROM order
) AS year_month
LEFT JOIN order ON order.CustomerID = customer.CustomerID
AND DATE_FORMAT(order.`date`, '%Y-%m') = year_month.y_m
GROUP BY 1, 2;
If order table does not contains for some year and month then according row won't present in the output. If you need in it then you'd generate calendar table instead of year_month subquery.
you can reduce the number of cte's I added more here to explain the steps:
first you need the format year and month, for that I used DATE_FORMAT() function
since you need to have all the combination of dates and the year month you need a cross join. This will produce all the distinct dates with all the distinct customer id's. In other words all the pairs between dates and customer id
once you have a table with all the combinations you need to pass the actual data with the left join this will produce null where you actually don't have rows and hence will produce 0 when the count is performed
the last step is simply count function
with main as (
select distinct DATE_FORMAT(date,'%Y-%m') as year_month from order
),
calendar as (
select * from customer
cross join main
),
joining_all as (
select
calendar.*,
order. OrderId
left join order
on calendar.CustomerID = order.CustomerID
and calendar.year_month = DATE_FORMAT(order.date,'%Y-%m')
)
select
CustomerID,
year_month as Date,
count(OrderId) as CountOrderID
from joining_all
group by 1,2
maybe the shorter version can work with the code below. if runs into syntax you can use the one above
with main as (
select distinct DATE_FORMAT(date,'%Y-%m') as year_month from order
cross join customer
)
select
main.CustomerID,
main.year_month as Date,
count(order.OrderId) as CountOrderID
from main
left join order
on main.CustomerID = order.CustomerID
and main.year_month = DATE_FORMAT(order.date,'%Y-%m')
group by 1,2

Difficult order / group by / count

I've got 3 simple tables, but my query is difficult
Sellers table :
seller_id | name
1 john
2 paul
5 fred
6 robert
etc ...
Transactions table (only 3 values for the moment) :
trans_id | name
1 BUY
2 SELL
3 EXCHANGE
Operations table :
seller_id | trans_id | datetime
2 1 ....
2 2 ....
6 1 ....
2 3 ....
6 1 ....
This tables records all the sellers' transactions and their moment.
I would like to obtain, in the last day, or in a time interval
seller name, number of buy-transaction order by number of buy-transaction in the interval
seller name, number of buy-or-sell-transaction order by number buy-or-sell-transaction in the interval
I've tried many things, strange things taht mysql does'nt like, but I can't succeed ... thanks !
Here is a solution for your first query, ie "seller name, number of buy-transaction order by number of buy-transaction in the interval" :
SELECT
S.name,
COUNT(1) AS total
FROM operations O
JOIN sellers S on O.seller_id = S.seller_id
JOIN transactions T on O.trans_id = T.trans_id
WHERE O.datetime >= CURDATE() AND T.name = 'BUY'
GROUP BY S.name
ORDER BY total
Obviously, the second query is nearly the same, the where clause just changes a little, see the following :
SELECT
S.name,
COUNT(1) AS total
FROM operations O
JOIN sellers S on O.seller_id = S.seller_id
JOIN transactions T on O.trans_id = T.trans_id
WHERE O.datetime >= CURDATE()-1 AND T.name IN ('BUY','SELL')
GROUP BY S.name
ORDER BY total
To be honest you could even remove the join with transactions table and use O.trans_id in your where clause.
SEE DEMO HERE
Here solution for your first query
Select a.name, a.count from
(select seller.name name, Count(Transaction.Tran_id) count
from seller, Transaction, Operation
where seller.Seller_id = Operation.Seller_Id
and Transaction.Tran_id = Operation.Tran_id
and Transaction.Tran_id=1
and Operation.datetime between (datetime 1 & datetime2)
group by seller.name) a
order by a.count
Second solution is :-
Select a.name, a.count from
(select seller.name name, Count(Transaction.Tran_id) count
from seller, Transaction, Operation
where seller.Seller_id = Operation.Seller_Id
and Transaction.Tran_id = Operation.Tran_id
and Transaction.Tran_id in (1, 2)
and Operation.datetime between (datetime 1 & datetime2)
group by seller.name) a
order by a.count

Mysql Select Sum but last three records

I have table with fields Customer date and amount
I want to sum Amount grouped by customer except the last two amounts of every customer by date
sample data
customer date amount
a 2020-10-1 100
a 2020-10-2 150
a 2020-10-3 30
a 2020-10-4 20
b 2020-10-1 1
b 2020-10-5 13
b 2020-10-7 50
b 2020-10-9 18
desired result
Customer Amount
A 150
B 14
something like
select Customer ,
SUM(amount- last 2 amount)
From TableA
Group By Customer
One option uses window functions, available in MySQL 8.0:
select customer, sum(amount) total_amount
from (
select a.*, row_number() over(partition by customer order by date desc) rn
from tablea a
) a
where rn > 2
group by customer
In earlier versions, an alternative uses a correlated subquery that returns the third latest date per customer for filtering:
select customer, sum(amount) total_amount
from tablea a
where date <= (select a1.date from tablea a1 where a1.customer = a.customer order by a1.date desc limit 2, 1)
group by customer

How can I find MIN value for each group and select the full row?

I have a PROJECTS table with PROJECT_ID, CUSTOMER_ID, COMPANY_ID and COST columns.
One customer could have one or several projects in different companies.
What I want is to choose a customer, who generates the smallest income for each company.
For example if the table looks like this
project_id customer_id company_id cost
1 1 1 1000
2 1 1 100
3 2 1 3000
4 1 2 300
5 2 2 100
, the expected answer is:
(COMPANY_ID) 1 | (CUSTOMER_ID) 1 | (COST) 1100
(COMPANY_ID) 2 | (CUSTOMER_ID) 2 | (COST) 100
Because the first customer generates 1000 + 100 = 1100 in total.
My query looks like this:
SELECT TABLE1.company_id, TABLE1.customer_id, MIN(profit)
FROM (
SELECT company_id, customer_id, SUM(projects.cost) AS profit
FROM projects
GROUP BY company_id,customer_id
) AS TABLE1
GROUP BY TABLE1.company_id;
It counts the MIN profit, but the ID's in CUSTOMER_ID column are always wrong. How can I build a connection between customers' IDs and their total profit for each company? Is it possible?
Thanks for helping.
One method for doing this is a "hack", because it uses string operations to get the value you want:
SELECT cc.company_id,
SUBSTRING_INDEX(GROUP_CONCAT(cc.customer_id ORDER BY profit ASC), ',', 1) as customer_id,
MIN(profit)
FROM (SELECT p.company_id, p.customer_id, SUM(p.cost) AS profit
FROM projects p
GROUP BY p.company_id, p.customer_id
) cc
GROUP BY cc.company_id;
An alternative in MySQL is something like this:
SELECT p.company_id, p.customer_id, SUM(p.cost) AS profit
FROM projects p
GROUP BY p.company_id, p.customer_id
HAVING SUM(p.cost) = (SELECT SUM(p2.cost)
FROM projects p2
WHERE p2.company_id = p.company_id
ORDER BY SUM(p2.cost) ASC
LIMIT 1
);
The two versions are subtly different:
The first will always return one customer, even if there are ties.
The first will convert the company_id to a string.
The first can run into overflow conditions, because the length of the intermediate result for group_concat() is controlled by a system parameter.
Use HAVING and ALL
SELECT p.company_id,
p.customer_id,
SUM(p.cost) AS profit
FROM projects p
GROUP BY p.company_id, p.customer_id
HAVING SUM(p.cost) <= ALL(
SELECT SUM(p2.cost)
FROM projects p2
WHERE p2.company_id = p.company_id
GROUP BY p2.customer_id
)
E.g.:
SELECT a.*
FROM
( SELECT customer_id
, company_id
, SUM(cost) total
FROM my_table
GROUP
BY customer_id
, company_id
) a
JOIN
( SELECT company_id
, MIN(total) min_total
FROM
( SELECT customer_id
, company_id
, SUM(cost) total
FROM my_table
GROUP
BY customer_id
, company_id
) x
GROUP
BY company_id
) b
ON b.company_id = a.company_id
AND b.min_total = a.total;

How to get rows with max date when grouping in MySQL?

I have a table with prices and dates on product:
id
product
price
date
I create a new record when price change. And I have a table like this:
id product price date
1 1 10 2014-01-01
2 1 20 2014-02-17
3 1 5 2014-03-28
4 2 25 2014-01-05
5 2 12 2014-02-08
6 2 30 2014-03-12
I want to get last price for all products. But when I group with "product", I can't get a price from a row with maximum date.
I can use MAX(), MIN() or COUNT() function in request, but I need a result based on other value.
I want something like this in final:
product price date
1 5 2014-03-28
2 30 2014-03-12
But I don't know how. May be like this:
SELECT product, {price with max date}, {max date}
FROM table
GROUP BY product
Alternatively, you can have subquery to get the latest get for every product and join the result on the table itself to get the other columns.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT product, MAX(date) mxdate
FROM tableName
GROUP BY product
) b ON a.product = b.product
AND a.date = b.mxdate
I think the easiest way is a substring_index()/group_concat() trick:
SELECT product,
substring_index(group_concat(price order by date desc), ',', 1) as PriceOnMaxDate
max(date)
FROM table
GROUP BY product;
Another way, that might be more efficient than a group by is:
select p.*
from table t
where not exists (select 1
from table t2
where t2.product = t.product and
t2.date > t.date
);
This says: "Get me all rows from the table where the same product does not have a larger date." That is a fancy way of saying "get me the row with the maximum date for each product."
Note that there is a subtle difference: the second form will return all rows that on the maximum date, if there are duplicates.
Also, for performance an index on table(product, date) is recommended.
You can use a subquery that groups by product and return the maximum date for every product, and join this subquery back to the products table:
SELECT
p.product,
p.price,
p.date
FROM
products p INNER JOIN (
SELECT
product,
MAX(date) AS max_date
FROM
products
GROUP BY
product) m
ON p.product = m.product AND p.date = m.max_date
SELECT
product,
price,
date
FROM
(SELECT
product,
price,
date
FROM table_name ORDER BY date DESC) AS t1
GROUP BY product;