Flaggin active customers - Atleast one transaction every month - sql

Once the customer is registered, between date_registered and current date - if the customer has made atleast one transaction every month, then flag it as active or else flag it has inactive
Note: Every customer has different date_registered
I tried this but doesn't work since few of the customers were onboarded in the middle of the year
Eg -
-------------------------------------
txn_id | txn_date | name | amount
-------------------------------------
101 2018-05-01 ABC 100
102 2018-05-02 ABC 200
-------------------------------------
(case when count(distinct case when txn_date >= '2018-05-01' and txn_date < '2019-06-01' then last_day(txn_date) end) = 13
then 'active' else 'inactive'
end) as flag
from t;
Final output
----------------
name | flag
----------------
ABC active
BCF inactive

You can use filtering on an aggregation query:
select customer,
count(distinct last_day(txn_date)) as num_months
from (select t.*, min(date_registered) over (partition by customer) as min_dr
from t
) t
group by customer, min_dr
having count(distinct last_day(txn_date)) = months_between(last_day(current_date), last_day(min_dr)) + 1;
Note: This may give unexpected results toward the beginning of a month, if customers do not all have transactions on the first day of the month.
EDIT:
If you want a flag, just move the HAVING logic to the SELECT:
select customer,
(case when count(distinct last_day(txn_date)) = months_between(last_day(current_date), last_day(min_dr)) + 1
then 'Active' else 'Inactive'
end) as active_flag
from (select t.*, min(date_registered) over (partition by customer) as min_dr
from t
) t
group by customer, min_dr;

Related

How do I get transactions amount > 1000 of all months in SQL

I have been trying to pull customers who have transaction amount greater than 1000 in all months. This is what I have tried so far. But it doesn't look like it's working when I do individual customer test.
Select customer
,extract(month from trans_date) as mth
,extract(year from trans_date) as yr
,sum(trans_amount) as amt
, case when mth in (8) and amt > 1000 then 1 else 0 end as aug
, case when mth in (9) and amt > 1000 then 1 else 0 end as sep
, case when mth in (10) and amt > 1000 then 1 else 0 end as oct
, case when mth in (11) and amt > 1000 then 1 else 0 end as nov
, case when mth in (12) and amt > 1000 then 1 else 0 end as de_c
from transaction
group by 1,2,3
having (aug = 1 and sep = 1 and oct=1 and nov=1 and de_c = 1)
Select customer
,extract(month from trans_date) as mth
,extract(year from trans_date) as yr
,sum(trans_amount) as amt
from transaction
-- filter only those months you want to check, e.g.
where trans_date between date '2021-08-01' and date '2021-12-31'
group by 1,2,3
-- check that every month there was an individual transaction over 1000
qualify
min(max(trans_amount))
over (partition by customer) > 1000
Edit:
Same logic to get just the customer without detail rows:
select customer
from
(
Select customer, max(trans_amount) as maxamt
from transaction
-- filter only those months you want to check, e.g.
where trans_date between date '2021-08-01' and date '2021-12-31'
group by
customer
,trunc(trans_date, 'mon') -- for every month
) as dt
group by customer
-- check that every month there was an individual transaction over 1000
having min(maxamt) > 1000
You may want to try using Over (partition by) something like this.
Select customer
,extract(month from trans_date) as mth
,extract(year from trans_date) as yr
,sum(trans_amount) over (partition by customer , extract(month from trans_date)) as
total
From transaction
Order by total desc
Assuming your data is one record per customer per month.
To get unique customers where trans_amt > 1000 in every month :
select customer
from transaction
group by customer
having count(1) = count(case when trans_amt > 1000 then 1 else 0 end)
To get all records only for customers where trans_amt > 1000 in every month :
select customer, trans_date, trans_amt
from transaction
qualify count(1) over (partition by customer) = count(case when trans_amt > 1000 then 1 else 0 end) over (partition by customer)

How to get value from a query of another table to create a new column (postgresql)

I am new to postgres and I want to be able to set value to Y if order (order table) is a first month order (first month order table)
first month order table is as per below. It will only show the order placed by user the first time in the month:
customer_id | order_date | order_id
--------------------------------------------------
a1 | December 6, 2015, 8:30 PM | orderA1
order table is as per below. It shows all the order records:
customer_id | order_date | order_id
-----------------------------------------------------
a1 | December 6, 2020, 8:30 PM | orderA1
a1 | December 7, 2020, 8:30 PM | orderA2
a2 | December 11, 2020, 8:30 PM | orderA3
To get the first month order column in the order table, I tried using case as below. But then it will give the error more than one row returned by a subquery.
SELECT DISTINCT ON (order_id) order_id, customer_id,
(CASE when (select distinct order_id from first_month_order_table) = order_id then 'Y' else 'N'
END)
FROM order_table
ORDER BY order_id;
I also tried using count but then i understand that this is quite inefficient and overworks the database i think.
SELECT DISTINCT ON (order_id) order_id, customer_id,
(CASE when (select count order_id from first_month_order_table) then 'Y' else 'N'
END)
FROM order_table
ORDER BY order_id;
How can I determine if the order is first month order and set the value as Y for every order in the order table efficiently?
Use the left join as follows:
SELECT o.order_id, o.customer_id,
CASE when f.order_id is not null then 'Y' else 'N' END as flag
FROM order_table o left join first_month_order_table f
on f.order_id = o.order_id
ORDER BY o.order_id;
If you have all orders in the orders table, you don't need the second table. Just use window functions. The following returns a boolean, which I find much more convenient than a character flag:
select o.*,
(row_number() over (partition by customer_id, date_trunc('month', order_date order by order_date) = 1) as flag
from orders o;
If you want a character flag, then you need case:
select o.*,
(case when row_number() over (partition by customer_id, date_trunc('month', order_date order by order_date) = 1
then 'Y' else 'N'
end) as flag
from orders o;

How to sum cumulative days an account is active from a table with multiple date ranges per account?

I have a transactional table that has account, TN number, provision status, transaction status, and date. An account is considered active if it has at least one TN number that provisioned successfully and no de-provision status.
Each account can be active for a period of time, then de-provision the last active TN and it becomes inactive. But that account can be active again if aTN is provisioned successfully.
I need to sum the cumulative days an account has been active with at least one TN in a provisioned state.
Here is a sample of my transaction table.
ACCOUNT TN_NUMBER STATUS TRANSACTION_STATUS DATE
------- --------- -------------- ------------------ ----------
1234 8005551212 Provisioned Success 2019-05-17
1234 8665558989 Provisioned Success 2019-05-25
1234 8005551212 De-provisioned Success 2019-05-27
1234 8665558989 De-provisioned Failed 2019-06-03
1234 8665558989 De-provisioned Success 2019-06-05
1234 8005551212 Provisioned Success 2019-06-01
5678 8005557777 Provisioned Success 2019-01-01
5678 8005557777 De-provisioned Success 2019-05-01
Account 1234 started 2019-05-17, deprovisioned the last TN on that account on 2019-06-05. (14 days active)
Then that account was active again starting 2019-06-01 and remains active. (61 days active).
Account 5678 was active 4 days.
This needs to be a daily query on 1.7 million accounts.
Use a cumulative sum to get the number of provisioned accounts at each point in time. Then assign a "grouping" by counting the number of unprovisioned accounts less than each row:
select t.*,
sum(case when num_provisioned <= 0 then 1 else 0 end) over (partition by account order by date) as grouping
from (select t.*,
sum(case when transaction_status = 'Success' and status = 'Provisioned'
then 1
when transaction_status = 'Success' and status = 'Provisioned'
then 1
when transaction_status = 'Success' and status = 'Unprovisioned'
then -1
else 0
end) over (partition by account order by date) as num_provisioned
from t
) t
With this information, it is then a matter of a couple of aggregations and a lead() (to get the next deprovisioning):
with g as (
select t.*,
sum(case when num_provisioned <= 0 then 1 else 0 end) over (partition by account order by date) as grouping
from (select t.*,
sum(case when transaction_status = 'Success' and status = 'Provisioned'
then 1
when transaction_status = 'Success' and status = 'Provisioned'
then 1
when transaction_status = 'Success' and status = 'Unprovisioned'
then -1
else 0
end) over (partition by account order by date) as num_provisioned
from t
) t
)
select account,
sum(datediff(day, min_date, coalesce(max_date, getdate()))) as num_days
from (select account, grouping, max(num_provisioned) as num_provisioned,
min(date) as min_date, max(date) as max_date,
lead(min(date)) over (partition by account order by min(date)) as next_min_date
from g
group by account, grouping
) g
where num_provisioned > 0
group by account;
You can use simple aggregation:
select t1.Account,
sum(datediff(day,t1.[date],t2.[date])) as NumDays
from #t1 as t1
inner join #t1 as t2 ON t1.Account = t2.Account and t1.TN_NUMBER = t2.TN_NUMBER
where t1.status = 'Provisioned'
and t1.TRANSACTION_STATUS = 'Success'
and t2.status = 'De-provisioned'
and t2.TRANSACTION_STATUS = 'Success'
and t1.[Date] <= t2.[Date]
group by t1.Account
Can you check your question : 2019-06-05 is after 2019-06-01 so the account 1234 is provisioned without interruption from 2019-05-17 until today, and there is 4 monts (not days) between 2019-01-01 and 2019-05-01.
Here is my script. I think it's quicker if you have a lot of data.
WITH PRO AS (
SELECT *
FROM DBO.[TRANSACTION]
WHERE STATUS = 'Provisioned'
AND TRANSACTION_STATUS = 'Success' ),
DEPRO AS (
SELECT *
FROM DBO.[TRANSACTION]
WHERE STATUS = 'De-provisioned'
AND TRANSACTION_STATUS = 'Success' ),
-- Create interval for transactions
INTERVAL AS (
SELECT T1.ACCOUNT, T1.DATE AS BEGIN_DATE, ISNULL(T2.DATE,CONVERT (DATE, GETDATE())) AS END_DATE
FROM PRO T1
LEFT JOIN DEPRO T2
ON T1.TN_NUMBER = T2.TN_NUMBER
AND T1.DATE < T2.DATE ) ,
-- Create concatenated intervals
INTERVAL_CONCAT (ACCOUNT, BEGIN_DATE, END_DATE) AS (
SELECT ACCOUNT,BEGIN_DATE, END_DATE
FROM INTERVAL
UNION ALL
SELECT I1.ACCOUNT, I2.BEGIN_DATE, I1.END_DATE
FROM INTERVAL I1
INNER JOIN INTERVAL_CONCAT I2
ON I1.ACCOUNT = I2.ACCOUNT
AND I1.BEGIN_DATE < I2.END_DATE
AND I1.END_DATE > I2.END_DATE ),
-- Remove duplicates
INTERVAL_RN AS (
SELECT *, RN=ROW_NUMBER()OVER(PARTITION BY ACCOUNT, BEGIN_DATE, END_DATE ORDER BY ACCOUNT)
FROM INTERVAL_CONCAT ),
INTERVAL_WO_DUP AS (
SELECT ACCOUNT, BEGIN_DATE, END_DATE
FROM INTERVAL_RN
WHERE RN=1 ),
-- Remove underlying intervals
INTERVAL_CLEANING AS (
SELECT *
FROM INTERVAL_WO_DUP
EXCEPT
SELECT I2.*
FROM INTERVAL_WO_DUP I1
INNER JOIN INTERVAL_WO_DUP I2
ON I2.BEGIN_DATE < I1.END_DATE
AND I2.BEGIN_DATE>= I1.BEGIN_DATE
AND I2.END_DATE <=I1.END_DATE
AND NOT (I2.BEGIN_DATE = I1.BEGIN_DATE AND I2.END_DATE=I1.END_DATE))
SELECT ACCOUNT, SUM(DATEDIFF(D,BEGIN_DATE,END_DATE)) AS PROVISIONED_DURATION
FROM INTERVAL_CLEANING
GROUP BY ACCOUNT
ResultSet with your data
ACCOUNT PROVISIONED_DURATION
------- --------------------
1234 95
5678 120

SQL - How to get columns from row values in the same column (SQL Server 2016)

I need to derive columns from the row values of one column.
Here's the row data.
CustomerID Activity Date
10001 Active 2018-06-21
10001 Inactive 2018-06-25
10001 Active 2018-08-22
10001 Inactive 2018-10-06
And here's the output that I am trying to get to:
CustomerID ActiveDate InactiveDate
10001 2018-06-21 2018-06-25
10001 2018-08-22 2018-10-06
Please help! Thanks!
You can try to make row number in subquery group by CustomerID,Activity, then do condition aggregate function.
SELECT CustomerID,
MAX(CASE WHEN Activity = 'Active' THEN Date END) ActiveDate,
MAX(CASE WHEN Activity = 'Inactive' THEN Date END) InactiveDate
FROM (
SELECT *,ROW_NUMBER() OVER(PARTITION BY CustomerID,Activity ORDER BY Date ) rn
FROM T
)t1
group by CustomerID,rn
sqlfiddle
You logic is a little unclear. If you want the next "inactive" date:
select CustomerID, date as active_date, inactive_date
from (select t.*,
min(case when activity = 'Inactive' then date end) over (partition by CustomerID order by date desc) as inactive_date
from t
) t
where activity = 'Active';

Calculate Running total in a new column based Adding or Subtracting condition using SQL

I am trying to calculate running total based on the value plus/minus in another column by Account and Date.
Example
Data
ID Account Date Operation Qty Running_Total
1 A 01/01/2018 plus 10 10
2 A 01/02/2018 plus 20 30
3 A 01/03/2018 minus 5 20
4 A 01/03/2018 minus 5 20
5 A 01/04/2018 plus 30 50
6 B 01/01/2018 plus 15 15
the total
Code:
select ID, Date, Operation, Total,
case when Operation = 'Use Table B' then TableB.RunningTotalQty
else
SUM( case when Operation = 'plus' then Qty
else case when Operation = 'minus' then -Qty end)
OVER (PARTITION BY Account ORDER BY Date) end
From TableA A left Join TableB B
on A.ID = B.ID ...
THIS ANSWERS THE ORIGINAL VERSION OF THE QUESTION.
The case goes inside the sum():
select ID, Date, Operation, Total,
sum(case when Operation = 'plus' then qty else - qty end) over
(partition by Account order by Date) as Running_Total
From TableA ;
This assumes only two operations. If you have more:
select ID, Date, Operation, Total,
sum(case when Operation = 'plus' then qty
then Operation = 'minus' then - qty
else 0
end) over
(partition by Account order by Date) as Running_Total
From TableA ;