How to finish this LAG calculation in Oracle - sql

I have month and value columns in a table,like
Month Value Market
2010/01 100 1
2010/02 200 1
2010/03 300 1
2010/04 400 1
2010/05 500 1
2010/01 100 2
2010/02 200 2
2010/03 300 2
2010/04 400 2
2010/05 500 2
What I want to do is get new Month and Value combinations using (value in month(n-1)+value in month(n))/2=value in month n, also this calculation is based on market column, it group by market number. So, for the above example, the new month and value combination should be
Month Value Market
2010/01 null 1
2010/02 (100+200)/2 1
2010/03 (200+300)/2 1
2010/04 (300+400)/2 1
2010/05 (400+500)/2 1
2010/01 null 2
2010/02 (100+200)/2 2
2010/03 (200+300)/2 2
2010/04 (300+400)/2 2
2010/05 (400+500)/2 2
Do you know how to achieve it in Oracle? Thank you!

If there is no gap in your data, you can use LAG:
SQL> WITH DATA AS (
2 SELECT DATE '2010-01-01' mon, 100 val FROM dual UNION ALL
3 SELECT DATE '2010-02-01' mon, 200 val FROM dual UNION ALL
4 SELECT DATE '2010-03-01' mon, 300 val FROM dual UNION ALL
5 SELECT DATE '2010-04-01' mon, 400 val FROM dual UNION ALL
6 SELECT DATE '2010-05-01' mon, 500 val FROM dual
7 )
8 SELECT mon, (LAG(val) OVER (ORDER BY mon) + val) / 2 avg_val FROM DATA;
MON AVG_VAL
----------- ----------
01/01/2010
01/02/2010 150
01/03/2010 250
01/04/2010 350
01/05/2010 450
However, if there is a gap the result might not be what you expect. In that case, you can either use a self-join or narrow the windowing clause:
SQL> WITH DATA AS (
2 SELECT DATE '2010-01-01' mon, 100 val FROM dual UNION ALL
3 SELECT DATE '2010-02-01' mon, 200 val FROM dual UNION ALL
4 SELECT DATE '2010-03-01' mon, 300 val FROM dual UNION ALL
5 /* gap ! */
6 SELECT DATE '2010-05-01' mon, 400 val FROM dual UNION ALL
7 SELECT DATE '2010-06-01' mon, 500 val FROM dual
8 )
9 SELECT mon, (first_value(val)
10 OVER (ORDER BY mon
11 RANGE BETWEEN INTERVAL '1' MONTH PRECEDING
12 AND INTERVAL '1' MONTH PRECEDING)
13 + val) / 2 avg_val
14 FROM DATA;
MON AVG_VAL
----------- ----------
01/01/2010
01/02/2010 150
01/03/2010 250
01/05/2010
01/06/2010 450

This does it:
SQL> select month,
2 (value+lag(value) over (order by month))/2 as value
3* from t1
MONTH VALUE
---------- ----------
2010/01
2010/02 150
2010/03 250
2010/04 350
2010/05 450
5 rows selected.

Related

How to count number of records for each week, from last month activity on a table?

I'm working with Oracle and I have a table with a column of type TIMESTAMP. I was wondering how can I extract the records from last 4 weeks of activity on the database, partitioned by week.
Following rows are inserted on week 1
kc 2 04-10-2021
vc 3 06-10-2021
vk 4 07-10-2021
Following rows are inserted on week2
cv 1 12-10-2021
ck 5 14-10-2021
Following rows are inserted on week3
vv 7 19-10-2021
Following rows are inserted on week4
vx 7 29-10-2021
Table now has
SQL>select * from tab;
NAME VALUE TIMESTAMP
-------------------- ----------
kc 2 04-10-2021
vc 3 06-10-2021
vk 4 07-10-2021
cv 1 12-10-2021
ck 5 14-10-2021
vv 7 19-10-2021
vx 7 29-10-2021
I would like a query which would give me the number of rows added each week, in the last 4 weeks.
This is what I would like to see
numofrows week
--------- -----
3 1
2 2
1 3
1 4
One option is to use to_char function and its iw parameter:
SQL> with test (name, datum) as
2 (select 'kc', date '2021-10-04' from dual union all
3 select 'vc', date '2021-10-06' from dual union all
4 select 'vk', date '2021-10-07' from dual union all
5 select 'cv', date '2021-10-12' from dual union all
6 select 'ck', date '2021-10-14' from dual union all
7 select 'vv', date '2021-10-19' from dual union all
8 select 'vx', DATE '2021-10-29' from dual
9 )
10 select to_char(datum, 'iw') week,
11 count(*)
12 from test
13 where datum >= add_months(sysdate, -1) --> the last month
14 group by to_char(datum, 'iw');
WE COUNT(*)
-- ----------
42 1
43 1
40 3
41 2
SQL>
Line #13: I intentionally used "one month" instead of "4 weeks" as I thought (maybe wrongly) that you, actually, want that (you know, "a month has 4 weeks" - not exactly, but close, sometimes not close enough).
If you want 4 weeks, what is that, then? Sysdate minus 28 days (as every week has 7 days)? Then you'd modify line #13 to
where datum >= trunc(sysdate - 4*7)
Or, maybe it is really the last 4 weeks:
SQL> with test (name, datum) as
2 (select 'kc', date '2021-10-04' from dual union all
3 select 'vc', date '2021-10-06' from dual union all
4 select 'vk', date '2021-10-07' from dual union all
5 select 'cv', date '2021-10-12' from dual union all
6 select 'ck', date '2021-10-14' from dual union all
7 select 'vv', date '2021-10-19' from dual union all
8 select 'vx', DATE '2021-10-29' from dual
9 ),
10 temp as
11 (select to_char(datum, 'iw') week,
12 count(*) cnt,
13 row_number() over (order by to_char(datum, 'iw') desc) rn
14 from test
15 group by to_char(datum, 'iw')
16 )
17 select week, cnt
18 from temp
19 where rn <= 4
20 order by week;
WE CNT
-- ----------
40 3
41 2
42 1
43 1
SQL>
Now you have several options, see which one fits the best (if any).
I "simulated" missing data (see TEST CTE), created a calendar (calend) and ... did the job. Read comments within code:
SQL> with test (name, datum) as
2 -- sample data
3 (select 'vv', date '2021-10-19' from dual union all
4 select 'vx', DATE '2021-10-29' from dual
5 ),
6 calend as
7 -- the last 31 days; 4 weeks are included, obviously
8 (select max_datum - level + 1 datum
9 from (select max(a.datum) max_datum from test a)
10 connect by level <= 31
11 ),
12 joined as
13 -- joined TEST and CALEND data
14 (select to_char(c.datum, 'iw') week,
15 t.name
16 from calend c left join test t on t.datum = c.datum
17 ),
18 last4 as
19 -- last 4 weeks
20 (select week, count(name) cnt,
21 row_number() over (order by week desc) rn
22 from joined
23 group by week
24 )
25 select week, cnt
26 from last4
27 where rn <= 4
28 order by week;
WE CNT
-- ----------
40 0
41 0
42 1
43 1
SQL>

SQL - Join with window

I have this dataset of product sales:
PRODUCT SALES
YearMonth Client_ID
202103 1
202008 1
201807 1
202101 2
202011 2
201802 2
201801 2
And I have this dataset of Financial Sales
FINANCIAL SALES
YearMonth Client_ID
202104 1
202009 1
201607 1
202104 2
202012 2
201512 2
I want to create a column in the dataset PRODUCT SALES named "If_financial_sales" where it takes the value 1 when the client in product sales bougth a financial product in the last 24 months, and 0 otherwise. Those 24 months are counting from the YearMonth of PRODUCT SALES (not from today). For example, If the client 2 on 201802 from product sales date bought a financial sale in the last 24 months (from 201801 to 201601 = 24 months) then If_financial_sales = 1 for this client in that month.
Output expected:
PRODUCT SALES
YearMonth Client_ID If_Financial_Sales
202103 1 1
202008 1 0
201807 1 1
202101 2 1
202011 2 0
201802 2 0
201801 2 1
I have been trying this with ROW_NUMBER(), but it seems to work better with some joins I have seen that uses equality operatos on the keys when joining (>, <, =) but I don't know what their name is neither how to use them.
Try this:
-- Auxiliary data for testing purposes
with product_sales (YearMonth, Client_ID) as (
select 202103, 1 from dual union all
select 202008, 1 from dual union all
select 201807, 1 from dual union all
select 202101, 2 from dual union all
select 202011, 2 from dual union all
select 201802, 2 from dual union all
select 201801, 2 from dual),
financial_sales (YearMonth, Client_ID) as (
select 202104, 1 from dual union all
select 202009, 1 from dual union all
select 201607, 1 from dual union all
select 202104, 2 from dual union all
select 202012, 2 from dual union all
select 201512, 2 from dual)
-- Actual query
SELECT
ps.*,
CASE
WHEN EXISTS (
SELECT
1
FROM
financial_sales fs
WHERE
to_date(fs.yearmonth, 'YYYYMM') < to_date(ps.yearmonth, 'YYYYMM')
AND to_date(fs.yearmonth, 'YYYYMM') >= to_date(ps.yearmonth, 'YYYYMM') - INTERVAL '25' MONTH
AND fs.client_id = ps.client_id
) THEN
1
ELSE
0
END if_financial_sales
FROM
product_sales ps;
Output:
YearMonth Client_ID If_financial_sales
----------------------
202103 1 1
202008 1 0
201807 1 1
202101 2 1
202011 2 0
201802 2 0
201801 2 1

Finding dates when accounts reach zero

Thanks for taking the time to examine my issue.
I'm trying to figure out a way to return dates when an account reaches 0
Sample data:
DATE ACCOUNT AMOUNT
11/01 001 100
11/02 002 50
11/03 001 -100
11/07 001 20
11/15 002 -50
11/20 001 -20
Wanted results:
Account ZeroDate
001 11/03
002 11/15
001 11/20
So far I haven't been able to figure out anything that works. Might you be able to point me in the right direction?
Thanks again in advance!
You can use analytic functions to compute the running balance
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select date '2011-11-01' dt, 1 account, 100 amt from dual union all
3 select date '2011-11-02', 2, 50 from dual union all
4 select date '2011-11-03', 1, -100 from dual union all
5 select date '2011-11-07', 1, 20 from dual union all
6 select date '2011-11-15', 2, -50 from dual union all
7 select date '2011-11-20', 1, -20 from dual
8 )
9 select dt,
10 account,
11 amt,
12 sum(amt) over (partition by account order by dt) current_balance
13* from x
SQL> /
DT ACCOUNT AMT CURRENT_BALANCE
--------- ---------- ---------- ---------------
01-NOV-11 1 100 100
03-NOV-11 1 -100 0
07-NOV-11 1 20 20
20-NOV-11 1 -20 0
02-NOV-11 2 50 50
15-NOV-11 2 -50 0
6 rows selected.
and then use the running balance to find the zero dates.
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select date '2011-11-01' dt, 1 account, 100 amt from dual union all
3 select date '2011-11-02', 2, 50 from dual union all
4 select date '2011-11-03', 1, -100 from dual union all
5 select date '2011-11-07', 1, 20 from dual union all
6 select date '2011-11-15', 2, -50 from dual union all
7 select date '2011-11-20', 1, -20 from dual
8 )
9 select account,
10 dt zero_date
11 from (
12 select dt,
13 account,
14 amt,
15 sum(amt) over (partition by account order by dt) current_balance
16 from x
17 )
18* where current_balance = 0
SQL> /
ACCOUNT ZERO_DATE
---------- ---------
1 03-NOV-11
1 20-NOV-11
2 15-NOV-11
create table myacct (dt varchar2(5)
, account varchar2(3)
, amount number
)
;
insert into myacct values ('11/01', '001', 100);
insert into myacct values ('11/02', '002', 50);
insert into myacct values ('11/03', '001', -100);
insert into myacct values ('11/07', '001', 20);
insert into myacct values ('11/15', '002', -50);
insert into myacct values ('11/20', '001', -20);
commit;
/* results wanted:
Account ZeroDate
001 11/03
002 11/15
001 11/20 */
select account "Account", dt "ZeroDate"
from myacct
where amount <= 0
;
/* results from above query:
Account ZeroDate
001 11/03
002 11/15
001 11/20
*/

Oracle SQL Query for monthwise report

Guys i have a table called A which has columns as
-----
A
-----
S_DATE
DUR
VIEWS
AMOUNT
The data in the table A has the following rows which is DateWise.
20101023 12 1 10000.0
20101101 2 4 200.0
20101114 4 5 -9
20101201 6 10 150.0
20101219 1 12 130.0
My requirement is that i want to write an SQL query that does monthwise *report*. For example from webGUI , when i select on the dropdown for month as DEC and select year as 2010 . I need to create show the sum of DUR,VIEWS and AMOUNT from 1st of November 2010 to 1st of December 2010.Any Ideas on how to do it.
The result for DEC2010 should be like below
**DEC2010 12 19 341**
this would work (although of course due to your requirement the first day of november will be in both the november and december monthly reports ?!):
SQL> VARIABLE p_months VARCHAR2(3);
SQL> VARIABLE p_year NUMBER;
SQL> EXEC :p_months := 'DEC'; :p_year := 2010;
PL/SQL procedure successfully completed.
SQL> WITH tableA AS (
2 SELECT DATE '2010-10-23's_date , 12 dur, 1 views, 10000.0 amount FROM DUAL
3 UNION ALL SELECT DATE '2010-11-01', 2, 4 , 200.0 FROM DUAL
4 UNION ALL SELECT DATE '2010-11-14', 4, 5 , -9 FROM DUAL
5 UNION ALL SELECT DATE '2010-12-01', 6, 10, 150.0 FROM DUAL
6 UNION ALL SELECT DATE '2010-12-19', 1, 12, 130.0 FROM DUAL
7 )
8 SELECT :p_months||:p_year month,
9 SUM(dur),
10 SUM(views),
11 SUM(amount)
12 FROM tableA
13 WHERE s_date >= add_months(to_date(:p_months||:p_year, 'MONYYYY'), -1)
14 AND s_date < to_date(:p_months||:p_year, 'MONYYYY') + 1;
MONTH SUM(DUR) SUM(VIEWS) SUM(AMOUNT)
---------- ---------- ---------- -----------
DEC2010 12 19 341

a sql query to compare results of following queries

SELECT pd_end_dt,SUM(nrx_cnt) Total_Count
FROM wkly_lnd.lnd_wkly_plan_rx_summary
WHERE pd_end_dt >= '01-Sep-08' AND pd_end_dt < '30-Sep-08'
GROUP BY pd_end_dt
SELECT pd_end_dt,SUM(nrx_cnt) Total_Count
FROM wkly_lnd.lnd_wkly_plan_rx_summary
WHERE pd_end_dt >= '01-Sep-07' AND pd_end_dt < '30-Sep-07'
GROUP BY pd_end_dt
the result set on running each query will be like
09/28/2007 00:00:00 702,457.36
09/21/2007 00:00:00 703,604.59
09/07/2007 00:00:00 636,619.92
09/14/2007 00:00:00 698,082.03
similarly for previous year
I need to calculate the difference of units sold as compared to last year and also to add one column which will find percentage change
There are a lot of things unsaid. I hope you receive clearer requirements in your daily work ...
Anyway, here is a simulation of your situation. It's based on the assumption that the days with data (one per week) are the same in 2007 as in 2008:
SQL> create table lnd_wkly_plan_rx_summary (pd_end_dt,nrx_cnt)
2 as
3 select date '2008-09-07', 100000 from dual union all
4 select date '2008-09-07', 536619.92 from dual union all
5 select date '2008-09-14', 698082.03 from dual union all
6 select date '2008-09-21', 403604.59 from dual union all
7 select date '2008-09-21', 200000 from dual union all
8 select date '2008-09-21', 100000 from dual union all
9 select date '2008-09-28', 702457.36 from dual union all
10 select date '2007-09-07', 400000 from dual union all
11 select date '2007-09-14', 450000 from dual union all
12 select date '2007-09-21', 500000 from dual union all
13 select date '2007-09-28', 550000 from dual union all
14 select date '2007-09-28', 100000 from dual
15 /
Tabel is aangemaakt.
And your original queries, slightly modified.
SQL> SELECT pd_end_dt
2 , SUM(nrx_cnt) Total_Count
3 FROM lnd_wkly_plan_rx_summary
4 WHERE pd_end_dt >= date '2008-09-01'
5 AND pd_end_dt < date '2008-09-30'
6 GROUP BY pd_end_dt
7 /
PD_END_DT TOTAL_COUNT
------------------- -----------
07-09-2008 00:00:00 636619,92
14-09-2008 00:00:00 698082,03
21-09-2008 00:00:00 703604,59
28-09-2008 00:00:00 702457,36
4 rijen zijn geselecteerd.
SQL> SELECT pd_end_dt
2 , SUM(nrx_cnt) Total_Count
3 FROM lnd_wkly_plan_rx_summary
4 WHERE pd_end_dt >= date '2007-09-01'
5 AND pd_end_dt < date '2007-09-30'
6 GROUP BY pd_end_dt
7 /
PD_END_DT TOTAL_COUNT
------------------- -----------
07-09-2007 00:00:00 400000
14-09-2007 00:00:00 450000
21-09-2007 00:00:00 500000
28-09-2007 00:00:00 650000
4 rijen zijn geselecteerd.
And the query with which you can compare the 2007 and 2008 data:
SQL> select to_char(pd_end_dt,'dd-mm') day_and_month
2 , sum(case trunc(pd_end_dt,'yyyy') when date '2007-01-01' then nrx_cnt end) sum2007
3 , sum(case trunc(pd_end_dt,'yyyy') when date '2008-01-01' then nrx_cnt end) sum2008
4 , sum(case trunc(pd_end_dt,'yyyy') when date '2008-01-01' then nrx_cnt end)
5 - sum(case trunc(pd_end_dt,'yyyy') when date '2007-01-01' then nrx_cnt end) difference
6 , ( sum(case trunc(pd_end_dt,'yyyy') when date '2008-01-01' then nrx_cnt end)
7 - sum(case trunc(pd_end_dt,'yyyy') when date '2007-01-01' then nrx_cnt end)
8 ) / sum(case trunc(pd_end_dt,'yyyy') when date '2008-01-01' then nrx_cnt end) * 100 percentage_difference
9 from lnd_wkly_plan_rx_summary
10 where ( ( pd_end_dt >= date '2007-09-01'
11 and pd_end_dt < date '2007-09-30'
12 )
13 or ( pd_end_dt >= date '2008-09-07'
14 and pd_end_dt < date '2008-09-30'
15 )
16 )
17 group by to_char(pd_end_dt,'dd-mm')
18 /
DAY_A SUM2007 SUM2008 DIFFERENCE PERCENTAGE_DIFFERENCE
----- ---------- ---------- ---------- ---------------------
07-09 400000 636619,92 236619,92 37,1681615
14-09 450000 698082,03 248082,03 35,5376617
21-09 500000 703604,59 203604,59 28,9373595
28-09 650000 702457,36 52457,36 7,46769313
4 rijen zijn geselecteerd.
Although rather verbose, I think it speaks for itself.
You may like the following rewrite, since it doesn't repeat the aggregate functions as much as in the query above:
SQL> select day_and_month
2 , sum2007
3 , sum2008
4 , sum2008-sum2007 difference
5 , 100*(sum2008-sum2007)/sum2008 percentage_difference
6 from ( select to_char(pd_end_dt,'dd-mm') day_and_month
7 , sum(case trunc(pd_end_dt,'yyyy') when date '2007-01-01' then nrx_cnt end) sum2007
8 , sum(case trunc(pd_end_dt,'yyyy') when date '2008-01-01' then nrx_cnt end) sum2008
9 from lnd_wkly_plan_rx_summary
10 where ( pd_end_dt >= date '2007-09-01'
11 and pd_end_dt < date '2007-09-30'
12 )
13 or ( pd_end_dt >= date '2008-09-07'
14 and pd_end_dt < date '2008-09-30'
15 )
16 group by to_char(pd_end_dt,'dd-mm')
17 )
18 /
DAY_A SUM2007 SUM2008 DIFFERENCE PERCENTAGE_DIFFERENCE
----- ---------- ---------- ---------- ---------------------
07-09 400000 636619,92 236619,92 37,1681615
14-09 450000 698082,03 248082,03 35,5376617
21-09 500000 703604,59 203604,59 28,9373595
28-09 650000 702457,36 52457,36 7,46769313
4 rijen zijn geselecteerd.
Hope this helps.
Regards,
Rob.
if you want to compare the results of the query year-on-year (ie for each day with the day of the preceeding year), you can group by the day of the year to_char('dd-mon'):
SQL> WITH lnd_wkly_plan_rx_summary AS (
2 SELECT DATE '2007-09-28' pd_end_dt, 702457.36 nrx_cnt FROM dual
3 UNION ALL SELECT DATE '2007-09-21', 703604.59 FROM dual
4 --
5 UNION ALL SELECT DATE '2008-09-28' pd_end_dt, 702557.36 nrx_cnt FROM dual
6 UNION ALL SELECT DATE '2008-09-21', 703404.59 FROM dual
7 )
8 SELECT to_char(pd_end_dt, 'dd-mon') pd_end_dt,
9 SUM(CASE
10 WHEN to_char(pd_end_dt, 'yyyy') = '2007' THEN
11 nrx_cnt
12 END) Total_2007,
13 SUM(CASE
14 WHEN to_char(pd_end_dt, 'yyyy') = '2008' THEN
15 nrx_cnt
16 END) Total_2008,
17 SUM(CASE
18 WHEN to_char(pd_end_dt, 'yyyy') = '2008' THEN
19 nrx_cnt
20 ELSE
21 -nrx_cnt
22 END) delta
23 FROM lnd_wkly_plan_rx_summary
24 WHERE ((pd_end_dt >= DATE '2007-09-01' AND pd_end_dt < DATE '2007-09-30') OR
25 (pd_end_dt >= DATE '2008-09-01' AND pd_end_dt < DATE '2008-09-30'))
26 GROUP BY to_char(pd_end_dt, 'dd-mon');
PD_END_DT TOTAL_2007 TOTAL_2008 DELTA
------------ ---------- ---------- ----------
28-sep 702457,36 702557,36 100
21-sep 703604,59 703404,59 -200
Why dont you look at the analytic functions provided with Oracle? I am assuming you are using Oracle as you have tagged your question with the Oracle tag. You can refer to http://www.oracle-base.com/articles/misc/LagLeadAnalyticFunctions.php.
I am simplifying your data set to look like this
09/08/2007 100
09/08/2008 200
09/09/2007 350
09/09/2008 400
09/10/2007 150
09/10/2008 175
These are your total counts on the 8,9 and 10th of September in the years 2007 and 2008
You can use the following query:
Assuming table to be T(end_date,cnt) (your names are too long! sorry )
Select end_date, cnt,
LAG(cnt,1,0) over (order by
to_number(to_char(end_dt,'dd')),to_number(to_char(end_dt,'mm'))) cntPrev,
cnt - LAG(cnt,1,0) over (order by
to_number(to_char(end_dt,'dd')),to_number(to_char(end_dt,'mm'))) cntDiff
from T
In simpler terms(this will not work if you copy, paste).
Let X=LAG(cnt,1,0) over (order by
to_number(to_char(end_dt,'dd')),to_number(to_char(end_dt,'mm')))
Your query is
Select end_date, cnt, X cntPrev, cnt-X cntDiff
from T;