Filtered last transaction - sql

first: I have two tables with a primary key((Agent_ID). I want to join both tables, filter Agent_Type =1 and status =1
Second: get the last active year total transaction value monthly wise who is not done any transaction for the last three months.
Agent table
Agent_ID Agent_Type
234 1
456 1
567 1
678 0
Agent_Transaction table
Agent_ID Amount Transaction_Date status
234 70 23/7/2019 1
234 54 11/6/2019 0
234 30 23/5/2019 1
456 56 12/1/2019 1
456 80 15/3/2019 1
456 99 20/2/2019 1
456 76 23/12/2018 1
567 56 10/10/2018 0
567 60 30/6/2018 1
456
select Agent_ID,CONCAT(Extract(MONTH from Agent_Transaction.Transaction_Date),
EXTRACT (YEAR FROM Agent_Transaction.Transaction_Date))as MONTH_YEAR,
SUM(Agent_Transaction.Amount)AS TOTAL
from Agent
inner join Agent_Transaction
on Agent_Transaction.Agent_ID = Agent.Agent_ID
where Agent.Agent_Type='1' AND Agent_Transaction.status='1' AND
(Agent_Transaction.Transaction_Date between ADD_MONTHS(SYSDATE,-3) and SYSDATE)
GROUP BY Agent.Agent_ID,
CONCAT(Extract(MONTH from Agent_Transaction.Transaction_Date),EXTRACT (YEAR FROM Agent_Transaction.Transaction_Date)),
Agent_Transaction.Amount
But I didn't get what I expected.

As far as I understood the requirement, you can use the following query:
SELECT
A.AGENT_ID,
TO_CHAR(TRUNC(ATR.TRANSACTION_DATE, 'MONTH'), 'MONYYYY'), -- YOU CAN USE DIFFERENT FORMAT ACCORDING TO REQUIREMENT
SUM(AMOUNT) AS TOTAL_MONTHWISE_AMOUNT -- MONTHWISE TRANSACTION TOTAL
FROM
AGENT A
JOIN AGENT_TRANSACTION ATR ON ( A.AGENT_ID = ATR.AGENT_ID )
WHERE
-- EXCLUDING THE AGENTS WHICH HAVE DONE NO TRANSACTION IN LAST THREE MONTHS USING FOLLOWING NOT IN
ATR.AGENT_ID NOT IN (
SELECT
DISTINCT ATR_IN1.AGENT_ID
FROM
AGENT_TRANSACTION ATR_IN1
WHERE
ATR_IN1.TRANSACTION_DATE > ADD_MONTHS(SYSDATE, - 3)
AND ATR_IN1.STATUS = 1 -- YOU CAN USE IT ACCORDING TO REQUIREMENT
)
-- FETCHING LAST YEAR DATA
AND EXTRACT(YEAR FROM ATR.TRANSACTION_DATE) = EXTRACT(YEAR FROM ADD_MONTHS(SYSDATE, - 12))
AND A.AGENT_TYPE = 1
AND ATR.STATUS = 1
GROUP BY
A.AGENT_ID,
TRUNC(ATR.TRANSACTION_DATE, 'MONTH');
Please comment if minor changes are required or you need different logic.
Cheers!!
-- Update --
Updated the query after OP described the original issue:
SELECT
AGENT_ID,
TO_CHAR(TRUNC(TRANSACTION_DATE, 'MONTH'), 'MONYYYY'), -- YOU CAN USE DIFFERENT FORMAT ACCORDING TO REQUIREMENT
SUM(AMOUNT) AS TOTAL_MONTHWISE_AMOUNT -- MONTHWISE TRANSACTION TOTAL
FROM
(
SELECT
A.AGENT_ID,
TRUNC(ATR.TRANSACTION_DATE, 'MONTH') AS TRANSACTION_DATE,
MAX(TRUNC(ATR.TRANSACTION_DATE, 'MONTH')) OVER(
PARTITION BY A.AGENT_ID
) AS LAST_TR_DATE,
AMOUNT,
AGENT_TYPE,
STATUS
FROM
AGENT A
JOIN AGENT_TRANSACTION ATR ON ( A.AGENT_ID = ATR.AGENT_ID )
WHERE
A.AGENT_TYPE = 1
AND ATR.STATUS = 1
)
WHERE
-- EXCLUDING THE AGENTS WHICH HAVE DONE NO TRANSACTION IN LAST THREE MONTHS USING FOLLOWING NOT IN
LAST_TR_DATE > ADD_MONTHS(SYSDATE, - 3)
-- FETCHING LAST YEAR DATA
AND TRANSACTION_DATE BETWEEN ADD_MONTHS(LAST_TR_DATE, - 12) AND LAST_TR_DATE
GROUP BY
AGENT_ID,
TRANSACTION_DATE;
Cheers!!
-- Update --
Your exact query should look like this:
SELECT
AGENT_ID,
TO_CHAR(TRUNC(TX_TIME, 'MONTH'), 'MONYYYY') AS MONTHYEAR,
SUM(TX_VALUE) AS TOTALMONTHWISE
FROM
(
SELECT
A.AGENT_ID,
TRUNC(ATR.TX_TIME, 'MONTH') AS TX_TIME, -- changed this alias name
MAX(TRUNC(ATR.TX_TIME, 'MONTH')) OVER(
PARTITION BY A.AGENT_ID
) AS LAST_TR_DATE,
ATR.TX_VALUE,
A.AGENT_TYPE_ID
FROM
TBLEZ_AGENT A
JOIN TBLEZ_TRANSACTION ATR ON ( A.AGENT_ID = ATR.SRC_AGENT_ID )
WHERE
A.AGENT_TYPE_ID = '3'
AND ATR.STATUS = '0'
AND ATR.TX_TYPE_ID = '5'
)
WHERE
LAST_TR_DATE < ADD_MONTHS(SYSDATE, - 3)
AND ( TX_TIME BETWEEN ADD_MONTHS(LAST_TR_DATE, - 12) AND LAST_TR_DATE )
GROUP BY
AGENT_ID,
TX_TIME;
-- UPDATE --
In response to this comment -- **Hi Tejash, How to get the total day-wise to above my scenario? **
SELECT
AGENT_ID,
TX_TIME,
SUM(TX_VALUE) AS TOTALDAYWISE
FROM
(
SELECT
A.AGENT_ID,
TRUNC(ATR.TX_TIME) AS TX_TIME, -- changed TRUNC INPUT PARAMETER -- REMOVED MONTH IN TRUNC
MAX(TRUNC(ATR.TX_TIME)) OVER( -- changed TRUNC INPUT PARAMETER -- REMOVED MONTH IN TRUNC
PARTITION BY A.AGENT_ID
) AS LAST_TR_DATE,
ATR.TX_VALUE,
A.AGENT_TYPE_ID
FROM
TBLEZ_AGENT A
JOIN TBLEZ_TRANSACTION ATR ON ( A.AGENT_ID = ATR.SRC_AGENT_ID )
WHERE
A.AGENT_TYPE_ID = '3'
AND ATR.STATUS = '0'
AND ATR.TX_TYPE_ID = '5'
)
WHERE
LAST_TR_DATE < ADD_MONTHS(SYSDATE, - 3)
AND ( TRUNC(TX_TIME, 'MONTH') BETWEEN ADD_MONTHS(LAST_TR_DATE, - 12) AND LAST_TR_DATE )
-- changed TRUNC INPUT PARAMETER -- ADDED MONTH IN TRUNC
GROUP BY
AGENT_ID,
TX_TIME;
Cheers!!

SELECT
A.AGENT_ID,
TO_CHAR(TRUNC(ATR.TX_TIME, 'MONTH'), 'MONYYYY') AS MONTHYEAR,
SUM(ATR.TX_VALUE) AS TOTALMONTHWISE
FROM
(
SELECT
A.AGENT_ID,
TRUNC(ATR.TX_TIME, 'MONTH') AS TRANSC_DATE,
MAX(TRUNC(ATR.TX_TIME, 'MONTH')) OVER(
PARTITION BY A.AGENT_ID
) AS LAST_TR_DATE,
ATR.TX_VALUE,
A.AGENT_TYPE_ID
FROM
TBLEZ_AGENT A
JOIN TBLEZ_TRANSACTION ATR ON ( A.AGENT_ID = ATR.SRC_AGENT_ID )
WHERE
A.AGENT_TYPE_ID = '3'
AND ATR.STATUS = '0'
AND ATR.TX_TYPE_ID = '5'
)
WHERE
LAST_TR_DATE > ADD_MONTHS(SYSDATE, - 3)
AND ( TX_TIME BETWEEN ADD_MONTHS(LAST_TR_DATE, - 12) AND LAST_TR_DATE )
GROUP BY
A.AGENT_ID,TRUNC(ATR.TX_TIME, 'MONTH')

Related

Oracle SQL Hierarchy Summation

I have a table TRANS that contains the following records:
TRANS_ID TRANS_DT QTY
1 01-Aug-2020 5
1 01-Aug-2020 1
1 03-Aug-2020 2
2 02-Aug-2020 1
The expected output:
TRANS_ID TRANS_DT BEGBAL TOTAL END_BAL
1 01-Aug-2020 0 6 6
1 02-Aug-2020 6 0 6
1 03-Aug-2020 6 2 8
2 01-Aug-2020 0 0 0
2 02-Aug-2020 0 1 1
2 03-Aug-2020 1 0 1
Each trans_id starts with a beginning balance of 0 (01-Aug-2020). For succeeding days, the beginning balance is the ending balance of the previous day and so on.
I can create PL/SQL block to create the output. Is it possible to get the output in 1 SQL statement?
Thanks.
Try this following script using CTE-
Demo Here
WITH CTE
AS
(
SELECT DISTINCT A.TRANS_ID,B.TRANS_DT
FROM your_table A
CROSS JOIN (SELECT DISTINCT TRANS_DT FROM your_table) B
),
CTE2
AS
(
SELECT C.TRANS_ID,C.TRANS_DT,SUM(D.QTY) QTY
FROM CTE C
LEFT JOIN your_table D
ON C.TRANS_ID = D.TRANS_ID
AND C.TRANS_DT = D.TRANS_DT
GROUP BY C.TRANS_ID,C.TRANS_DT
ORDER BY C.TRANS_ID,C.TRANS_DT
)
SELECT F.TRANS_ID,F.TRANS_DT,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT < F.TRANS_DT
) BEGBAL,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT = F.TRANS_DT
) TOTAL ,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT <= F.TRANS_DT
) END_BAL
FROM CTE2 F
You can as well do like this (I would assume it's a bit faster): Demo
with
dt_between as (
select mindt + level - 1 as trans_dt
from (select min(trans_dt) as mindt, max(trans_dt) as maxdt from t)
connect by level <= maxdt - mindt + 1
),
dt_for_trans_id as (
select *
from dt_between, (select distinct trans_id from t)
),
qty_change as (
select distinct trans_id, trans_dt,
sum(qty) over (partition by trans_id, trans_dt) as total,
sum(qty) over (partition by trans_id order by trans_dt) as end_bal
from t
right outer join dt_for_trans_id using (trans_id, trans_dt)
)
select
trans_id,
to_char(trans_dt, 'DD-Mon-YYYY') as trans_dt,
nvl(lag(end_bal) over (partition by trans_id order by trans_dt), 0) as beg_bal,
nvl(total, 0) as total,
nvl(end_bal, 0) as end_bal
from qty_change q
order by trans_id, trans_dt
dt_between returns all the days between min(trans_dt) and max(trans_dt) in your data.
dt_for_trans_id returns all these days for each trans_id in your data.
qty_change finds difference for each day (which is TOTAL in your example) and cumulative sum over all the days (which is END_BAL in your example).
The main select takes END_BAL from previous day and calls it BEG_BAL, it also does some formatting of final output.
First of all, you need to generate dates, then you need to aggregate your values by TRANS_DT, and then left join your aggregated data to dates. The easiest way to get required sums is to use analitic window functions:
with dates(dt) as ( -- generating dates between min(TRANS_DT) and max(TRANS_DT) from TRANS
select min(trans_dt) from trans
union all
select dt+1 from dates
where dt+1<=(select max(trans_dt) from trans)
)
,trans_agg as ( -- aggregating QTY in TRANS
select TRANS_ID,TRANS_DT,sum(QTY) as QTY
from trans
group by TRANS_ID,TRANS_DT
)
select -- using left join partition by to get data on daily basis for each trans_id:
dt,
trans_id,
nvl(sum(qty) over(partition by trans_id order by dates.dt range between unbounded preceding and 1 preceding),0) as BEGBAL,
nvl(qty,0) as TOTAL,
nvl(sum(qty) over(partition by trans_id order by dates.dt),0) as END_BAL
from dates
left join trans_agg tr
partition by (trans_id)
on tr.trans_dt=dates.dt;
Full example with sample data:
alter session set nls_date_format='dd-mon-yyyy';
with trans(TRANS_ID,TRANS_DT,QTY) as (
select 1,to_date('01-Aug-2020'), 5 from dual union all
select 1,to_date('01-Aug-2020'), 1 from dual union all
select 1,to_date('03-Aug-2020'), 2 from dual union all
select 2,to_date('02-Aug-2020'), 1 from dual
)
,dates(dt) as ( -- generating dates between min(TRANS_DT) and max(TRANS_DT) from TRANS
select min(trans_dt) from trans
union all
select dt+1 from dates
where dt+1<=(select max(trans_dt) from trans)
)
,trans_agg as ( -- aggregating QTY in TRANS
select TRANS_ID,TRANS_DT,sum(QTY) as QTY
from trans
group by TRANS_ID,TRANS_DT
)
select
dt,
trans_id,
nvl(sum(qty) over(partition by trans_id order by dates.dt range between unbounded preceding and 1 preceding),0) as BEGBAL,
nvl(qty,0) as TOTAL,
nvl(sum(qty) over(partition by trans_id order by dates.dt),0) as END_BAL
from dates
left join trans_agg tr
partition by (trans_id)
on tr.trans_dt=dates.dt;
You can use a recursive query to generate the overall date range, cross join it with the list of distinct tran_id, then bring the table with a left join. The last step is aggregation and window functions:
with all_dates (trans_dt, max_dt) as (
select min(trans_dt), max(trans_dt) from trans group by trans_id
union all
select trans_dt + interval '1' day, max_dt from all_dates where trans_dt < max_dt
)
select
i.trans_id,
d.trans_dt,
coalesce(sum(sum(t.qty)) over(partition by i.trans_id order by d.trans_dt), 0) - coalesce(sum(t.qty), 0) begbal,
coalesce(sum(t.qty), 0) total,
coalesce(sum(sum(t.qty)) over(partition by i.trans_id order by d.trans_dt), 0) endbal
from all_dates d
cross join (select distinct trans_id from trans) i
left join trans t on t.trans_id = i.trans_id and t.trans_dt = d.trans_dt
group by i.trans_id, d.trans_dt
order by i.trans_id, d.trans_dt

Comparing with other rows in table

Hi brilliant thinkers,
I want to create a CASE condition to give me a "yes" for active_users that is if there exists within 60 days, a more recent uuid_ts for the same anonymous_id.
SELECT t1.anonymous_id user_id,
t1.uuid_ts activity_date,
t2.uuid_ts signup_date,
-- Activity Lifetime: difference of number of days signed up to last activity
DATE_DIFF(CAST(t2.uuid_ts AS DATE), CAST(t1.uuid_ts AS DATE), DAY) AS activity_lifetime,
-- New Users: If month of activity is same as sign_up month
(CASE WHEN DATE_DIFF(CAST(t1.uuid_ts AS DATE), CAST(t2.uuid_ts AS DATE), MONTH)=0 THEN TRUE ELSE FALSE END) AS new_user,
-- Active Users: If month of activity is greater than sign_up month AND activity is found
(CASE WHEN DATE_DIFF(CAST(t1.uuid_ts AS DATE), CAST(t2.uuid_ts AS DATE), MONTH)>0
-- ** ____ NEED HELP HERE ____ **
AND anonymous_id NOT IN (SELECT anonymous_id FROM datascience.last_user_activity)
AND DATE_ADD(activity_date, INTERVAL 60 DAY) > (S)
FROM datascience.last_user_activity AS t1
INNER JOIN datascience.full_signup_completed AS t2
ON t2.anonymous_id = t1.anonymous_id
WHERE DATE(t1.uuid_ts) IS NOT NULL AND DATE(t2.uuid_ts) IS NOT NULL
ORDER BY activity_lifetime DESC
SAMPLE DATA:
anon_id|signup_date|activity_date|
__________________________________
123 |01-01-2019 |02-01-2019 |
123 |01-01-2019 |02-02-2019 |
123 |01-01-2019 |02-03-2019 |
123 |01-01-2019 |02-04-2019 |
WANTED:
anon_id|signup_date|activity_date| active
__________________________________
123 |01-01-2019 |02-01-2019 | yes
123 |01-01-2019 |02-02-2019 | yes
123 |01-01-2019 |02-03-2019 | no
123 |01-01-2019 |02-04-2019 | no
if a future date exists in the same row, within the range of 60 days, then the field active shows "yes", else a "no".
Still not 100% sure this is what you are looking for, but I hope it helps:
WITHIN 60 days:
(The output would be "yes, yes, yes, no" since 02-04-2019 > 02-03-2019 and within 60 days)
WITH
sample_data AS (
SELECT
'123' AS anon_id, DATE('2019-01-01') AS signup_date,
DATE('2019-01-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-02-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-03-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-04-02') AS activity_date)
SELECT
anon_id,
signup_date,
activity_date,
(CASE
WHEN EXISTS( SELECT 'found' FROM sample_data t2 WHERE t2.anon_id = t1.anon_id AND t2.activity_date > t1.activity_date AND t2.activity_date <= DATE_ADD(t1.activity_date, INTERVAL 60 DAY)) THEN 'yes'
ELSE
'no'
END
) AS active
FROM
sample_data t1
ORDER BY 1,2,3
60 DAYS or BEYOND:
(The output would be "yes, no, no, no" since February has 28 days and March 31, so between 02-02-2019 and 02-04-2019 there are 59 days)
WITH
sample_data AS (
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-01-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-02-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-03-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-04-02') AS activity_date)
SELECT
anon_id,
signup_date,
activity_date,
(CASE
WHEN EXISTS( SELECT 'found' FROM sample_data t2 WHERE t2.anon_id = t1.anon_id AND t2.activity_date >= DATE_ADD(t1.activity_date, INTERVAL 60 DAY)) THEN 'yes'
ELSE
'no'
END
) AS active
FROM
sample_data t1
ORDER BY 1,2,3
Your question/logic/dates are a bit unclear, but I think the following query should point you in the right direction.
with joined as (
-- Join your tables and handle casting here (only have to do it once)
select
anonymous_id,
date(full_signup_completed.uuid_ts) as signup_date,
extract(month from full_signup_completed.uuid_ts) as signup_month,
date(last_user_activity.uuid_ts) as activity_date,
extract(month from last_user_activity.uuid_ts) as activity_month
from datascience.full_signup_completed
left join datascience.last_user_activity using(anonymous_id)
where full_signup_completed.uuid_ts is not null and last_user_activity.uuid_ts is not null
),
activity60 as (
-- for each activity date, is there a future activity date within 60 days?
select j1.anonymous_id,j1.activity_date, true as has_activity_within_60_days
from joined j1
cross join joined j2
where j1.anonymous_id = j2.anonymous_id and date_diff(j2.activity_date, j1.activity_date, day) <= 60
group by 1,2
),
final as (
-- Get all of your logic
select
joined.*,
date_diff(activity_date,signup_date, day) as activity_lifetime,
signup_month = activity_month as new_user, -- Evaluates to T/F
(activity_month > signup_month) and has_activity_within_60_days as your_custom_field -- Evaluates to aT/F
from joined
inner join activity60 using(anonymous_id,activity_date)
)
select * from final
order by activity_lifetime desc
In your example, are your dates in DD-MM-YYYY format? If not, i'm not sure how the 60 day constraint makes sense.

Interview question:How to get last 3 month aggregation at column level?

This is the question i was being asked at Apple onsite interview and it blew my mind. Data is like this:
orderdate,unit_of_phone_sale
20190806,3000
20190704,3789
20190627,789
20190503,666
20190402,765
I had to write a query to get the result for each month sale, we should have last 3 month sales values. Let me put the expected output here.
order_monnth,M-1_Sale, M-2_Sale, M-3_Sale
201908,3000,3789,789,666
201907,3789,789,666,765
201906,789,666,765,0
201905,666,765,0,0
201904,765,0,0
I could only got the month wise sale and and used case statement by hardcoding month which was wrong. I banged my head to write this sql, but i could not.
Can anyone help on this. It will be really helpful for me to prepare for sql interviews
Update: This is what i tried
with abc as(
select to_char(order_date,'YYYYMM') as yearmonth,to_char(order_date,'YYYY') as year,to_char(order_date,'MM') as moth, sum(unit_of_phone_sale) as unit_sale
from t1 group by to_char(order_date,'YYYYMM'),to_char(order_date,'YYYY'),to_char(order_date,'MM'))
select yearmonth, year, case when month=01 then unit_sale else 0 end as M1_Sale,
case when month=02 then unit_sale else 0 end as M2_Sale...
case when month=12 then unit_sale else 0 end as M12_Sale
from abc
You will first of all need to sum the month's data and then use the LAG function to get previous months' data as following:
SELECT
ORDER_MONTH,
LAG(UNIT_OF_PHONE_SALE, 1) OVER(
ORDER BY
ORDER_MONTH
) AS "M-1_Sale",
LAG(UNIT_OF_PHONE_SALE, 2) OVER(
ORDER BY
ORDER_MONTH
) AS "M-2_Sale",
LAG(UNIT_OF_PHONE_SALE, 3) OVER(
ORDER BY
ORDER_MONTH
) AS "M-3_Sale"
FROM
(
SELECT
TO_CHAR(ORDERDATE, 'YYYYMM') AS ORDER_MONTH,
SUM(UNIT_OF_PHONE_SALE) AS UNIT_OF_PHONE_SALE
FROM
DATAA
GROUP BY
TO_CHAR(ORDERDATE, 'YYYYMM')
)
ORDER BY
ORDER_MONTH DESC;
Output:
ORDER_ M-1_Sale M-2_Sale M-3_Sale
------ ---------- ---------- ----------
201908 3789 789 666
201907 789 666 765
201906 666 765
201905 765
201904
db<>fiddle demo
Cheers!!
-- Update --
For the requirement mentioned in the comments, Following query will work for it.
CTE AS (
SELECT
TRUNC(ORDERDATE, 'MONTH') AS ORDER_MONTH,
SUM(UNIT_OF_PHONE_SALE) AS UNIT_OF_PHONE_SALE
FROM
DATAA
GROUP BY
TRUNC(ORDERDATE, 'MONTH')
)
SELECT
TO_CHAR(C.ORDER_MONTH,'YYYYMM') as ORDER_MONTH,
NVL(C1.UNIT_OF_PHONE_SALE, 0) AS "M-1_Sale",
NVL(C2.UNIT_OF_PHONE_SALE, 0) AS "M-2_Sale",
NVL(C3.UNIT_OF_PHONE_SALE, 0) AS "M-3_Sale"
FROM
CTE C
LEFT JOIN CTE C1 ON ( C1.ORDER_MONTH = ADD_MONTHS(C.ORDER_MONTH, - 1) )
LEFT JOIN CTE C2 ON ( C2.ORDER_MONTH = ADD_MONTHS(C.ORDER_MONTH, - 2) )
LEFT JOIN CTE C3 ON ( C3.ORDER_MONTH = ADD_MONTHS(C.ORDER_MONTH, - 3) )
ORDER BY
C.ORDER_MONTH DESC
Output:
db<>fiddle demo of updated answer.
Cheers!!
I think LEAD function can help here -
SELECT TO_CHAR(orderdate, 'YYYYMM') "DATE"
,unit_of_phone_sale M_1_Sale
,LEAD(unit_of_phone_sale,1,0) OVER(ORDER BY TO_CHAR(orderdate, 'YYYYMM') DESC) M_2_Sale
,LEAD(unit_of_phone_sale,2,0) OVER(ORDER BY TO_CHAR(orderdate, 'YYYYMM') DESC) M_3_Sale
,LEAD(unit_of_phone_sale,3,0) OVER(ORDER BY TO_CHAR(orderdate, 'YYYYMM') DESC) M_4_Sale
FROM table_sales
Here is the DB Fiddle
You can use this query:
select a.order_month, a.unit_of_phone_sale,
LEAD(unit_of_phone_sale, 1, 0) OVER (ORDER BY rownum) AS M_1,
LEAD(unit_of_phone_sale, 2, 0) OVER (ORDER BY rownum) AS M_2,
LEAD(unit_of_phone_sale, 3, 0) OVER (ORDER BY rownum) AS M_3
from (
select TO_CHAR(orderdate, 'YYYYMM') order_month,
unit_of_phone_sale,
rownum
from Y
order by order_month desc) a

Retrieve records by continuation of days in oracle

I want to retrieve records where cash deposits are more than 4 totaling to 1000000 during a day and continues for more than 5 days.
I have came up with below query.
SELECT COUNT(a.txamt) AS "txcount"
, SUM(a.txamt) AS "txsum"
, b.custcd
, a.txdate
FROM tb_transactions a
INNER JOIN tb_accounts b
ON a.acctno = b.acctno
WHERE a.cashflowtype = 'CR'
GROUP BY b.custcd, a.txdate
HAVING COUNT(a.txamt)>4 and SUM(a.txamt)>='1000000'
ORDER BY a.txdate;
But I'm stuck on how to fetch the records if the pattern continues for 5 days.
How to achieve the desired result?
Something like:
SELECT *
FROM (
SELECT t.*,
COUNT( txdate ) OVER ( PARTITION BY custcd
ORDER BY txdate
RANGE BETWEEN INTERVAL '0' DAY PRECEDING
AND INTERVAL '4' DAY FOLLOWING ) AS
num_days
FROM (
select count(a.txamt) as "txcount",
sum(a.txamt) as "txsum",
b.custcd,
a.txdate
from tb_transactions a inner join tb_accounts b on a.acctno=b.acctno
where a.cashflowtype='CR'
group by b.custcd, a.txdate
having count(a.txamt)>4 and sum(a.txamt)>=1000000
) t
)
WHERE num_days = 5
order by a.txdate;

SQL Query Help (Advanced - for me!)

I have a question about a SQL query I am trying to write.
I need to query data from a database.
The database has, amongst others, these 3 fields:
Account_ID #, Date_Created, Time_Created
I need to write a query that tells me how many accounts were opened per hour.
I have written said query, but there are times that there were 0 accounts created, so these "hours" are not populated in the results.
For example:
Volume Date__Hour
435 12-Aug-12 03
213 12-Aug-12 04
125 12-Aug-12 06
As seen in the example above, hour 5 did not have any accounts opened.
Is there a way that the result can populate the hour but and display 0 accounts opened for this hour?
Example of how I want my results to look like:
Volume Date_Hour
435 12-Aug-12 03
213 12-Aug-12 04
0 12-Aug-12 05
125 12-Aug-12 06
Thanks!
Update: This is what I have so far
SELECT count(*) as num_apps, to_date(created_ts,'DD-Mon-RR') as app_date, to_char(created_ts,'HH24') as app_hour
FROM accounts
WHERE To_Date(created_ts,'DD-Mon-RR') >= To_Date('16-Aug-12','DD-Mon-RR')
GROUP BY To_Date(created_ts,'DD-Mon-RR'), To_Char(created_ts,'HH24')
ORDER BY app_date, app_hour
To get the results you want, you will need to create a table (or use a query to generate a "temp" table) and then use a left join to your calculation query to get rows for every hour - even those with 0 volume.
For example, assume I have a table with app_date and app_hour fields. Also assume that this table has a row for every day/hour you wish to report on.
The query would be:
SELECT NVL(c.num_apps,0) as num_apps, t.app_date, t.app_hour
FROM time_table t
LEFT OUTER JOIN
(
SELECT count(*) as num_apps, to_date(created_ts,'DD-Mon-RR') as app_date, to_char(created_ts,'HH24') as app_hour
FROM accounts
WHERE To_Date(created_ts,'DD-Mon-RR') >= To_Date('16-Aug-12','DD-Mon-RR')
GROUP BY To_Date(created_ts,'DD-Mon-RR'), To_Char(created_ts,'HH24')
ORDER BY app_date, app_hour
) c ON (t.app_date = c.app_date AND t.app_hour = c.app_hour)
I believe the best solution is not to create some fancy temporary table but just use this construct:
select level
FROM Dual
CONNECT BY level <= 10
ORDER BY level;
This will give you (in ten rows):
1
2
3
4
5
6
7
8
9
10
For hours interval just little modification:
select 0 as num_apps, (To_Date('16-09-12','DD-MM-RR') + level / 24) as created_ts
FROM dual
CONNECT BY level <= (sysdate - To_Date('16-09-12','DD-MM-RR')) * 24 ;
And just for the fun of it adding solution for you(I didn't try syntax, so I'm sorry for any mistake, but the idea is clear):
SELECT SUM(num_apps) as num_apps, to_date(created_ts,'DD-Mon-RR') as app_date, to_char(created_ts,'HH24') as app_hour
FROM(
SELECT count(*) as num_apps, created_ts
FROM accounts
WHERE To_Date(created_ts,'DD-Mon-RR') >= To_Date('16-09-12','DD-MM-RR')
UNION ALL
select 0 as num_apps, (To_Date('16-09-12','DD-MM-RR') + level / 24) as created_ts
FROM dual
CONNECT BY level <= (sysdate - To_Date('16-09-12','DD-MM-RR')) * 24 ;
)
GROUP BY To_Date(created_ts,'DD-Mon-RR'), To_Char(created_ts,'HH24')
ORDER BY app_date, app_hour
;
You can also use a CASE statement in the SELECT to force the value you want.
It can be useful to have a "sequence table" kicking around, for all sorts of reasons, something that looks like this:
create table dbo.sequence
(
id int not null primary key clustered ,
)
Load it up with million or so rows, covering positive and negative values.
Then, given a table that looks like this
create table dbo.SomeTable
(
account_id int not null primary key clustered ,
date_created date not null ,
time_created time not null ,
)
Your query is then as simple as (in SQL Server):
select year_created = years.id ,
month_created = months.id ,
day_created = days.id ,
hour_created = hours.id ,
volume = t.volume
from ( select * ,
is_leap_year = case
when id % 400 = 0 then 1
when id % 100 = 0 then 0
when id % 4 = 0 then 1
else 0
end
from dbo.sequence
where id between 1980 and year(current_timestamp)
) years
cross join ( select *
from dbo.sequence
where id between 1 and 12
) months
left join ( select *
from dbo.sequence
where id between 1 and 31
) days on days.id <= case months.id
when 2 then 28 + years.is_leap_year
when 4 then 30
when 6 then 30
when 9 then 30
when 11 then 30
else 31
end
cross join ( select *
from dbo.sequence
where id between 0 and 23
) hours
left join ( select date_created ,
hour_created = datepart(hour,time_created ) ,
volume = count(*)
from dbo.SomeTable
group by date_created ,
datepart(hour,time_created)
) t on datepart( year , t.date_created ) = years.id
and datepart( month , t.date_created ) = months.id
and datepart( day , t.date_created ) = days.id
and t.hour_created = hours.id
order by 1,2,3,4
It's not clear to me if created_ts is a datetime or a varchar. If it's a datetime, you shouldn't use to_date; if it's a varchar, you shouldn't use to_char.
Assuming it's a datetime, and borrowing #jakub.petr's FROM Dual CONNECT BY level trick, I suggest:
SELECT count(*) as num_apps, to_char(created_ts,'DD-Mon-RR') as app_date, to_char(created_ts,'HH24') as app_hour
FROM (select level-1 as hour FROM Dual CONNECT BY level <= 24) h
LEFT JOIN accounts a on h.hour = to_number(to_char(a.created_ts,'HH24'))
WHERE created_ts >= To_Date('16-Aug-12','DD-Mon-RR')
GROUP BY trunc(created_ts), h.hour
ORDER BY app_date, app_hour