How to calculate payment from user's registration date plus 30 days IN ORACLE SQL - sql

How to calculate payments by a certain period.
Specifically, I wonder if the user registered on the x date, how much payment he has in 30 days from that date
There are two tables:
create table user (user_id, contact, registration_date) as
select 1, 111 111, 1/18/2022 3:57:32 PM from dual union all
select 2, 222 222, 8/12/2021 12:00:12 AM from dual union all
select 3, 333 333, 12/11/2015 5:08:35 PM from dual union all
select 4, 444 444, 5/25/2020 10:59:10 AM from dual;
create table transaction (day, user_id, payment) as
select 1/20/2022, 1, 5 from dual union all
select 1/30/2022, 1, 8 from dual union all
select 2/20/2022, 1, 6 from dual union all
select 8/12/2021 , 2, 10 from dual union all
select 8/15/2021 , 2, 5 from dual union all
select 9/25/2021 , 2, 12 from dual union all
select 12/11/2015 , 3, 18 from dual union all
select 12/20/2015 , 3, 10 from dual union all
select 1/1/2016 , 3, 10 from dual union all
select 5/26/2020 , 4, 7 from dual union all
select 6/1/2020 , 4, 2 from dual;
I wonder something like this, but this query does not work,
select t.user_id,u.registration_date,sum(t.payment) from transaction t
left join user u on t.user_id = u.user_id
where t.day >= u.registration_date and t.day <= u.registration_date + interval '30' days
group by t.user_id
My expected table:
user_id
registration_date
payment
1
1/18/2022 3:57:32 PM
13
2
8/12/2021 12:00:12 AM
15
3
12/11/2015 5:08:35 PM
38
4
5/25/2020 10:59:10 AM
9

First of all, you probably want to outer join the transactions to the users not vice versa. Thus you also show users with no trasactions in the 30 days after registration.
Then, Oracle requires you to either put u.registration_date in GROUP BY or pseudo aggregate it (e.g. MIN(u.registration_date)). This doesn't comply with standard SQL, but it seems that Oracle hasn't mananged yet to properly detect functional dependencies, so they simply don't offer this feature.
At last, the registration date, despite its name, is not a date but a datetime. In order to compare it with the transaction date, truncate it. Then decide whether you want to include the 30th after the registration date or not (i.e. either add 30 or 31 days and use < anyway).
select u.user_id, u.registration_date, sum(t.payment)
from users u
left join transactions t
on t.user_id = u.user_id
and t.day >= trunc(u.registration_date)
and t.day < trunc(u.registration_date) + interval '31' day
group by u.user_id, u.registration_date
order by u.user_id;
Demo: https://dbfiddle.uk/?rdbms=oracle_21&fiddle=a6d8d13c994eff6a1c0f8afa6fcb176f

Looks like
SQL> SELECT u.user_id, u.registration_date, SUM (t.payment) payment
2 FROM tuser u JOIN transaction t ON t.user_id = t.user_id
3 WHERE t.day BETWEEN u.registration_date
4 AND u.registration_date + INTERVAL '30' DAY
5 GROUP BY u.user_id, u.registration_date
6 ORDER BY u.user_id;
USER_ID REGISTRATI PAYMENT
---------- ---------- ----------
1 01/18/2022 13
2 08/12/2021 15
3 12/11/2015 38
4 05/25/2020 9
SQL>

Your query doesn't work because you forgot to add the u.registration_date in your GROUP BY clause. When you do a GROUP BY, all fields mentioned in the SELECT need to be either added to the GROUP BY or be used in a aggregate function like SUM, MIN, MAX, ...

Related

How to get min and max from 2 tables in SQL

I am Trying to get start date from min ID (ID=1) and end date from max ID (ID=3) but i am not sure how i can retrieve. Following is my data -
Table1 and Table2 are source table. I am trying to get output like 3rd table.
My requirement is get start date from first record of ID and End Date from last record of ID, we can recognize first and and last record with the help of ID field. If ID is min means first record and ID is max then last record
Please help me!
Here's one option; presuming you use Oracle (regarding you use Oracle SQL Developer), the x inline view selects
start_date which belongs to name with the lowest ID column value for that name (i.e. first_value partition by name order by id)
end_date which belongs to name with the highest ID column value for that name (i.e. first_value partition by name order by id DESC)
SQL> with
2 -- sample data
3 t1 (pid, name) as
4 (select 123, 'xyz' from dual union all
5 select 234, 'pqr' from dual
6 ),
7 t2 (id, name, start_date, end_date) as
8 (select 1, 'xyz', date '2020-01-01', date '2020-07-20' from dual union all
9 select 2, 'xyz', date '2020-02-01', date '2020-05-30' from dual union all
10 select 3, 'xyz', date '2020-06-30', date '2020-07-30' from dual union all
11 --
12 select 1, 'pqr', date '2020-04-30', date '2020-09-30' from dual union all
13 select 2, 'pqr', date '2020-05-30', date '2020-09-30' from dual union all
14 select 3, 'pqr', date '2020-06-30', date '2020-07-01' from dual
15 )
16 select a.pid,
17 x.name,
18 max(x.start_date) start_date,
19 max(x.end_date) end_date
20 from t1 a join
21 (
22 -- start_date: always for the lowest T2.ID value row
23 -- end_date : always for the highest T2.ID value row
24 select b.name,
25 first_value(b.start_date) over (partition by b.name order by b.id ) start_date,
26 first_value(b.end_date) over (partition by b.name order by b.id desc) end_date
27 from t2 b
28 ) x
29 on a.name = x.name
30 group by a.pid,
31 x.name
32 order by a.pid;
PID NAME START_DATE END_DATE
---------- ---- ---------- ----------
123 xyz 01/01/2020 07/30/2020
234 pqr 04/30/2020 07/01/2020
SQL>

Not able to group data according to month given in a date using SQL

I have the following set of sample data from a database
Period Company Metric Values
01/01/18 A Vol 2
02/01/18 A Vol 4
04/02/18 A Vol 5
05/02/18 B Vol 6
06/03/18 B Vol 4
07/04/18 C Vol 1
08/05/18 C Vol 6
I wish to display a total of "Values" for each company according to month.
For Example 'company A' has a total value of 6 for first month and value of 5 for second month
As a first step, I tried working as
SELECT COUNT(*) FROM `TABLE 2` WHERE DATEPART(MONTH, `Period`) = 01;
But it is throwing an error and it also does not have a group by function
Can anyone please tell how it can be done
You're using count() function instead of sum() function.
As per your requirement, the query should something similar to
SELECT COMPANY,SUM(VALUES) FROM TABLE2
GROUP BY DATEPART(MONTH, Period)
If you want the query without group by, use direct where clause.
SELECT SUM(VALUES) FROM TABLE2 WHERE DATEPART(MONTH, Period) = 01;
Try this Only for MySQL, DATEPART is not available in MySQL
SELECT MONTH(Period) as gmonth, Period, Company, SUM(Values) FROM `TABLE 2`
GROUP BY MONTH(Period);
As you tagged the question with the Oracle tag as well, here you go: TO_CHAR function with the 'mm' format mask fetches month from the date (01 for January, 02 for February, etc.). As there's an aggregate function (count) involved, you have to GROUP BY column that isn't aggregated. (As far as I can tell, that is valid for any other database.)
SQL> with test (period, company) as
2 (select date '2018-01-01', 'A' from dual union all
3 select date '2018-01-02', 'A' from dual union all
4 select date '2018-02-04', 'A' from dual union all
5 select date '2018-02-05', 'B' from dual union all
6 select date '2018-03-06', 'B' from dual union all
7 select date '2018-04-07', 'C' from dual union all
8 select date '2018-05-08', 'A' from dual
9 )
10 select to_char(period, 'mm') month, count(*)
11 from test
12 group by to_char(period, 'mm')
13 order by 1;
MO COUNT(*)
-- ----------
01 2
02 2
03 1
04 1
05 1
SQL>

Return Month wise count if no data for month return 0 as count in oracle sql

I have a table having data for January to March (till current month) and I am able to take the month wise count.But user required is to display zero for rest of the month.Kindly suggest.
For example:
select count(a.emp_id) as cnt ,to_char(a.due_date,'MONTH') as Process_Month from EMP_Request a
where a.due_date is not null
group by to_char(a.due_date,'MONTH')
Output:
cnt Process_month
20 JANUARY
35 FEBUARY
26 March
Desired output:
cnt Process_month
20 JANUARY
35 FEBUARY
26 March
0 APRIL
0 MAY
…….
….
….
0 DECEMBER
Please assist.
use WWV_FLOW_MONTHS_MONTH to get all the month and left join with your query to get the month name from the date column and join with it
with cte
(
SELECT month_display as month FROM WWV_FLOW_MONTHS_MONTH
) , cnt as
(
select count(a.emp_id) as cnt ,
to_char(a.due_date,'MONTH') as Process_Month from EMP_Request a
where a.due_date is not null
group by to_char(a.due_date,'MONTH')
) select coalesce(Process_Month,month), cnt from cte left join cnt on cte.month=cnt.to_char(to_date(Process_Month, 'DD-MM-YYYY'), 'Month')
Right join months generator with your query:
select to_char(to_date(mth_num, 'MM'), 'MONTH') month, nvl(cnt, 0) cnt
from (
select count(emp_id) as cnt, to_char(due_date, 'mm') mth_num
from emp_request where due_date is not null
group by to_char(due_date, 'mm')) e
right join (
select to_char(level, 'fm00') mth_num
from dual connect by level <= 12) m using (mth_num)
order by mth_num
dbfiddle demo
Months generator is a simple hierarchical query which gives us 12 values 01, 02... 12:
select to_char(level, 'fm00') mth_num from dual connect by level <= 12
You can also use system views to get these numbers:
select to_char(rownum, 'fm00') mth_num from all_objects where rownum <= 12
or this syntax:
select to_char(column_value, 'fm00') mth_num
from table(sys.odcivarchar2list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
It's better to work on numbers which you can sort properly and convert to month names in the last step. This way you have natural months order.
If you want to be sure that month names are always in english, not dependent from local settings then use to_date with third parameter, like here:
select to_char(sysdate, 'month', 'nls_date_language=english') from dual
This is a general problem which is not really a sql problem. SQL doesn't really know about what months you are interested in. So the solution is to tell it in a sub query.
Here is a solution that doesn't use external tables. You simply select all months of the year and outer join your data.
select TO_CHAR(TO_DATE(available_months.m,'MM'),'MONTH') , NVL(sum(data.cnt),0) from
(select to_number(to_char(sysdate,'MM')) m, 7 cnt from dual) data,
(select 1 m from dual union select 2 from dual union select 3 from dual union select 4 from dual
union select 5 from dual union select 6 from dual union select 7 from dual
union select 8 from dual union select 9 from dual union select 10 from dual
union select 11 from dual union select 12 from dual) available_months
where
data.m (+) = available_months.m
group by available_months.m
order by available_months.m;
Or with your data query included is should look like (not tested):
select TO_CHAR(TO_DATE(available_months.m,'MM'),'MONTH') , NVL(sum(data.cnt),0) from
(select count(a.emp_id) as cnt ,to_char(a.due_date,'MONTH') as Process_Month from EMP_Request a where a.due_date is not null) data
(select 1 m from dual union select 2 from dual union select 3 from dual union select 4 from dual
union select 5 from dual union select 6 from dual union select 7 from dual
union select 8 from dual union select 9 from dual union select 10 from dual
union select 11 from dual union select 12 from dual) available_months
where
data.due_date (+) = available_months.m
group by available_months.m
order by available_months.m;

Oracle : Get average count for last 30 business days

Oracle version 11g.
My table has records similar to these.
calendar_date ID record_count
25-OCT-2017 1 20
25-OCT-2017 2 40
25-OCT-2017 3 60
24-OCT-2017 1 70
24-OCT-2017 2 50
24-OCT-2017 3 10
20-OCT-2017 1 35
20-OCT-2017 2 60
20-OCT-2017 3 90
18-OCT-2017 1 80
18-OCT-2017 2 50
18-OCT-2017 3 45
i.e for each ID, there is one record count for a given calendar day. The days are NOT continuous, i.e there may be missing records for weekends/holidays etc. On such days, there will not be records available for any ID. However on working days there are entries available for each ID .
I need to get the average record count for last 30 business days for each id
I want an output like this. ( Don't go by the values. It is just a sample )
ID avg_count_last_30
1 150
2 130
3 110
I am trying to figure out the most efficient way to do this. I thought of using RANGE BETWEEN , ROWS BETWEEN etc , but unsure it would work.
Off course a query like this won't help as there are holidays in between.
select id, AVG(record_count) FROM mytable
where calendar_date between SYSDATE - 30 and SYSDATE - 1
group by id;
what I need is something like
select id , AVG(record_count) FROM mytable
where calendar_date between last_30th_business_day and last_business_day
group by id;
last_30th_business_day will be count(DISTINCT business_days ) starting from most recent business day going backwards till I count 30.
last_business_day will be most recent business day
Would like to know experts opinion on this and best approach.
Based on your comment try this one:
WITH mytable (calendar_date, ID, record_count) AS (
SELECT TO_DATE('25-10-2017', 'DD-MM-YYYY'), 1, 20 FROM dual UNION ALL
SELECT TO_DATE('25-10-2017', 'DD-MM-YYYY'), 2, 40 FROM dual UNION ALL
SELECT TO_DATE('25-10-2017', 'DD-MM-YYYY'), 3, 60 FROM dual UNION ALL
SELECT TO_DATE('24-10-2017', 'DD-MM-YYYY'), 1, 70 FROM dual UNION ALL
SELECT TO_DATE('24-10-2017', 'DD-MM-YYYY'), 2, 50 FROM dual UNION ALL
SELECT TO_DATE('24-10-2017', 'DD-MM-YYYY'), 3, 10 FROM dual UNION ALL
SELECT TO_DATE('20-10-2017', 'DD-MM-YYYY'), 1, 35 FROM dual UNION ALL
SELECT TO_DATE('20-10-2017', 'DD-MM-YYYY'), 2, 60 FROM dual UNION ALL
SELECT TO_DATE('20-10-2017', 'DD-MM-YYYY'), 3, 90 FROM dual UNION ALL
SELECT TO_DATE('18-10-2017', 'DD-MM-YYYY'), 1, 80 FROM dual UNION ALL
SELECT TO_DATE('18-10-2017', 'DD-MM-YYYY'), 2, 50 FROM dual UNION ALL
SELECT TO_DATE('18-10-2017', 'DD-MM-YYYY'), 3, 45 FROM dual),
t AS (
SELECT calendar_date, ID, record_count,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY calendar_date desc) AS RN
FROM mytable)
SELECT ID, AVG(RECORD_COUNT)
FROM t
WHERE rn <= 30
group by ID;

Count running total in Oracle

I want to make a query, which shows the progress of the number of users on my webpage by week.
I use following query to run the users database and get the number, grouped by a week:
SELECT TRUNC(FAB.LICENSE_DATE, 'IW'),
COUNT(DISTINCT FAB.STATEMENT_NUMBER) AS "Number of account statements"
FROM USERS FAB
GROUP BY TRUNC(FAB.LAST_UPDATED_TIME, 'IW');
This gives following output:
Date | Users
----------------------
2015/09/07 | 5
2015/09/14 | 4
2015/09/21 | 6
But this is actually not what I want to achieve. I want to have the following output:
Date | Users
----------------------
2015/09/07 | 5
2015/09/14 | 9 (5 + 4)
2015/09/21 | 15 (5 + 4 + 6)
How to modify the query so I get all the results?
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE USERS (
LICENSE_DATE,
LAST_UPDATED_TIME,
STATEMENT_NUMBER
) AS
SELECT DATE '2015-09-07', DATE '2015-09-07', 1 FROM DUAL
UNION ALL SELECT DATE '2015-09-08', DATE '2015-09-08', 2 FROM DUAL
UNION ALL SELECT DATE '2015-09-08', DATE '2015-09-08', 3 FROM DUAL
UNION ALL SELECT DATE '2015-09-09', DATE '2015-09-09', 4 FROM DUAL
UNION ALL SELECT DATE '2015-09-12', DATE '2015-09-12', 5 FROM DUAL
UNION ALL SELECT DATE '2015-09-14', DATE '2015-09-15', 6 FROM DUAL
UNION ALL SELECT DATE '2015-09-15', DATE '2015-09-16', 7 FROM DUAL
UNION ALL SELECT DATE '2015-09-16', DATE '2015-09-16', 8 FROM DUAL
UNION ALL SELECT DATE '2015-09-17', DATE '2015-09-18', 9 FROM DUAL
UNION ALL SELECT DATE '2015-09-21', DATE '2015-09-21', 10 FROM DUAL
UNION ALL SELECT DATE '2015-09-21', DATE '2015-09-26', 11 FROM DUAL
UNION ALL SELECT DATE '2015-09-22', DATE '2015-09-22', 12 FROM DUAL
UNION ALL SELECT DATE '2015-09-23', DATE '2015-09-25', 13 FROM DUAL
UNION ALL SELECT DATE '2015-09-24', DATE '2015-09-24', 14 FROM DUAL
UNION ALL SELECT DATE '2015-09-27', DATE '2015-09-27', 15 FROM DUAL;
Query 1:
SELECT LAST_UPDATED_WEEK,
SUM( NUM_STATEMENTS ) OVER ( ORDER BY LAST_UPDATED_WEEK ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS "Number of account statements"
FROM (
SELECT TRUNC(LAST_UPDATED_TIME, 'IW') AS LAST_UPDATED_WEEK,
COUNT(DISTINCT STATEMENT_NUMBER) AS NUM_STATEMENTS
FROM USERS
GROUP BY
TRUNC( LAST_UPDATED_TIME, 'IW')
)
Results:
| LAST_UPDATED_WEEK | Number of account statements |
|-----------------------------|------------------------------|
| September, 07 2015 00:00:00 | 5 |
| September, 14 2015 00:00:00 | 9 |
| September, 21 2015 00:00:00 | 15 |
SELECT TRUNC(FAB.LICENSE_DATE, 'IW'),
SUM(COUNT(DISTINCT FAB.STATEMENT_NUMBER)) OVER (ORDER BY TRUNC(FAB.LAST_UPDATED_TIME, 'IW')) as "Number of account statements"
FROM USERS FAB
GROUP BY TRUNC(FAB.LAST_UPDATED_TIME, 'IW');
You can use this code block for your problem :
select u.date
,(select sum(u1.users)
from users u1
where u1.ddate <= u.date) as users
from users u;
It gives this output :
07.09.2015 5
14.09.2015 9
21.09.2015 15
Good luck
Hello you can try this code too.
WITH t1 AS
( SELECT to_date('01/01/2015','mm/dd/yyyy') rn, 5 usrs FROM dual
UNION ALL
SELECT to_date('02/01/2015','mm/dd/yyyy') rn, 4 usrs FROM dual
UNION ALL
SELECT to_date('03/01/2015','mm/dd/yyyy') rn, 8 usrs FROM dual
UNION ALL
SELECT to_date('04/01/2015','mm/dd/yyyy') rn, 2 usrs FROM dual
)
SELECT rn,
usrs,
sum(usrs) over (order by rn ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) cumm_usrs
FROM t1
GROUP BY rn,
usrs;