CONSECUTIVE DAYS QUERY [closed] - sql

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 11 months ago.
Improve this question
I have an Oracle DB Connection that has data (SELECT * FROM SALES) as in the picture, i want a query that gives me which 3 consecutive days are those who have the sum of PREMIUM_TOTAL > 100.
I have tried with the method lead, lag , DATADIFF but failed. Also i'm new at this, if you can give me hints please.

If you want 3 rows from successive days then you can use a recursive query:
WITH successive_days (day, products, total, depth) AS (
SELECT entry_date,
TO_CHAR(product_id),
premium_total,
1
FROM table_name
UNION ALL
SELECT s.day + 1,
s.products || ',' || t.product_id,
s.total + t.premium_total,
s.depth + 1
FROM successive_days s
INNER JOIN table_name t
ON (s.day + 1 = t.entry_date)
WHERE s.depth < 3
)
SELECT day AS final_day, products, total
FROM successive_days
WHERE depth = 3
AND total >= 100;
Which, for the sample data:
CREATE TABLE table_name (product_id, entry_date, premium_total) AS
SELECT 1, DATE '2022-03-01', 1 FROM DUAL UNION ALL
SELECT 2, DATE '2022-03-01', 20 FROM DUAL UNION ALL
SELECT 4, DATE '2022-03-02', 30 FROM DUAL UNION ALL
SELECT 5, DATE '2022-03-03', 30 FROM DUAL UNION ALL
SELECT 10, DATE '2022-03-21', 12 FROM DUAL UNION ALL
SELECT 11, DATE '2022-03-31', 40.5 FROM DUAL UNION ALL
SELECT 13, DATE '2022-03-05', 70 FROM DUAL UNION ALL
SELECT 12, DATE '2022-03-05', 80 FROM DUAL UNION ALL
SELECT 14, DATE '2022-03-05', 10 FROM DUAL UNION ALL
SELECT 20, DATE '2022-03-06', 20 FROM DUAL UNION ALL
SELECT 21, DATE '2022-03-07', 30 FROM DUAL UNION ALL
SELECT 22, DATE '2022-03-07', 40 FROM DUAL UNION ALL
SELECT 30, DATE '2022-03-08', 20 FROM DUAL UNION ALL
SELECT 31, DATE '2022-03-09', 50 FROM DUAL UNION ALL
SELECT 40, DATE '2022-03-10', 2 FROM DUAL;
Outputs:
FINAL_DAY
PRODUCTS
TOTAL
2022-03-07 00:00:00
13,20,21
120
2022-03-07 00:00:00
13,20,22
130
2022-03-07 00:00:00
12,20,21
130
2022-03-07 00:00:00
12,20,22
140
2022-03-09 00:00:00
21,30,31
100
2022-03-09 00:00:00
22,30,31
110
If you want all the rows (at least 3) that are all within 3 successive days then you can use MATCH_RECOGNIZE:
SELECT MIN(entry_date) AS start_day,
MAX(entry_date) AS final_day,
LISTAGG(product_id, ',') WITHIN GROUP (ORDER BY entry_date) AS products,
SUM(premium_total) AS total
FROM table_name
MATCH_RECOGNIZE(
ORDER BY entry_date
MEASURES
MATCH_NUMBER() AS mno
ALL ROWS PER MATCH
AFTER MATCH SKIP TO NEXT ROW
PATTERN (first_day+ second_day+ third_day* final_day)
DEFINE
first_day AS FIRST(entry_date) = entry_date,
second_day AS FIRST(entry_date) + 1 = entry_date,
third_day AS FIRST(entry_date) + 2 = entry_date,
final_day AS FIRST(entry_date) + 2 = entry_date
AND SUM(premium_total) >= 100
)
GROUP BY mno;
Which, for the sample data, outputs:
START_DAY
FINAL_DAY
PRODUCTS
TOTAL
2022-03-05 00:00:00
2022-03-07 00:00:00
12,13,14,20,21,22
250
2022-03-05 00:00:00
2022-03-07 00:00:00
13,14,20,21,22
170
2022-03-05 00:00:00
2022-03-07 00:00:00
13,20,21,22
160
2022-03-06 00:00:00
2022-03-08 00:00:00
20,21,22,30
110
2022-03-07 00:00:00
2022-03-09 00:00:00
21,22,30,31
140
2022-03-07 00:00:00
2022-03-09 00:00:00
22,30,31
110
db<>fiddle here

Related

SQL Query to fetch the latest payment method and invoice date

There are four tables
First - Customer
Second - Invoice
Third - Supplier
Fourth - Supplier_Remit
Tables details are mentioned below
Customer_id
Customer_Account_number
Customer_Status
Supplier_id
Supplier_Remit_id
1
1501
Active
11
111
2
1502
Inactive
12
112
3
1503
Active
13
113
4
1504
Active
14
114
5
1505
Inactive
15
115
Invoice_Date
Invoice_Amount
Invoice_Number
Payment Method
Customer_id
01/01/2023
100
1000001
Cash
1
12/01/2022
150
1000002
Credit Card
1
11/09/2022
200
1000003
Credit Card
1
12/09/2022
300
1000004
Cash
2
04/15/2022
1000
1000005
Cash
2
04/15/2022
1000
1000006
Credit Card
3
10/31/2022
250
1000007
Cash
4
10/25/2022
250
1000008
Cash
4
09/20/2022
130
1000009
Credit Card
5
05/20/2022
120
10000010
Credit Card
5
Supplier_Name
Supplier_id
ABC
11
ACCC
12
ADEF
13
AJKL
14
AFLR
15
City
Country
Supplier_Remit_id
Supplier_id
Boston
US
111
11
Oak
US
112
12
Albany
US
113
13
Madison
US
114
14
Los Ang
US
115
15
I need help in finding the most recent payment method, most recent invoice amount, no of count of invoices missing for current year (2023) and no of count of invoices missing for previous year(2022)
I have written query to find first few columns but unable to write further to get the above mentioned details
select c.customer_id,c.customer_account_number,c.customer_status,sr.country,max(i.invoice_date) as Latest receieved_Invoice_date
from
customer c,
invoice i,
supplier s,
supplier_Remit sr
where
c.customer_status='Active' and
sr.supplier_id=s.supplier_id and
c.supplier_remit_id=sr.supplier_remit_id and
c.customer_id=i.customer_id
group by
c.customer_id,c.customer_account_number,c.customer_status,sr.country;
My expected output would be as below
Customer_id
Cust_Acct_Num
Cust_Status
Country
Last_Inv_Rec_Date
1
1501
Active
US
01/01/2023
3
1503
Active
US
04/15/2022
4
1504
Active
US
10/31/2022
Latest_Paym_Method
Lastest_Inv_Amt
Count of Missing Inv for Curr Yr
Cash
100
0
Credit card
1000
1
Cash
250
1
Count of Missing Invoices for Prev Year
10
11
11
You can use MAX(...) KEEP (DENSE_RANK LAST ORDER BY invoice_date) to get values for the latest invoice and conditional aggregation to count the number of months where there are invoices and then subtract from the total number of months to find the missing invoices:
SELECT c.Customer_id,
c.Customer_Account_number,
c.Customer_Status,
r.country,
i.last_invoice_date,
i.latest_payment_method,
i.latest_invoice_amount,
EXTRACT(MONTH FROM SYSDATE) - COALESCE(i.missing_invoices_this_year, 0)
AS missing_invoices_this_year,
12 - COALESCE(i.missing_invoices_last_year, 0)
AS missing_invoices_last_year
FROM customer c
INNER JOIN supplier_remit r
ON (c.supplier_id = r.supplier_id)
LEFT OUTER JOIN (
SELECT customer_id,
MAX(invoice_date) AS last_invoice_date,
MAX(payment_method) KEEP (DENSE_RANK LAST ORDER BY invoice_date)
AS latest_payment_method,
MAX(invoice_amount) KEEP (DENSE_RANK LAST ORDER BY invoice_date)
AS latest_invoice_amount,
COUNT(
DISTINCT
CASE
WHEN invoice_date < SYSDATE
AND invoice_date >= TRUNC(SYSDATE, 'YY')
THEN TRUNC(invoice_date, 'MM')
END
) AS missing_invoices_this_year,
COUNT(
DISTINCT
CASE
WHEN invoice_date < TRUNC(SYSDATE, 'YY')
AND invoice_date >= ADD_MONTHS(TRUNC(SYSDATE, 'YY'), -12)
THEN TRUNC(invoice_date, 'MM')
END
) AS missing_invoices_last_year
FROM invoice
GROUP BY customer_id
) i
ON (c.customer_id = i.customer_id)
WHERE c.customer_status = 'Active';
Which, for the sample data:
CREATE TABLE customer (Customer_id, Customer_Account_number, Customer_Status, Supplier_id, Supplier_Remit_id) AS
SELECT 1, 1501, 'Active', 11, 111 FROM DUAL UNION ALL
SELECT 2, 1502, 'Inactive', 12, 112 FROM DUAL UNION ALL
SELECT 3, 1503, 'Active', 13, 113 FROM DUAL UNION ALL
SELECT 4, 1504, 'Active', 14, 114 FROM DUAL UNION ALL
SELECT 5, 1505, 'Inactive', 15, 115 FROM DUAL;
CREATE TABLE invoice (Invoice_Date, Invoice_Amount, Invoice_Number, Payment_Method, Customer_id) AS
SELECT DATE '2023-01-01', 100, 1000001, 'Cash', 1 FROM DUAL UNION ALL
SELECT DATE '2022-12-01', 150, 1000002, 'Credit Card', 1 FROM DUAL UNION ALL
SELECT DATE '2022-11-09', 200, 1000003, 'Credit Card', 1 FROM DUAL UNION ALL
SELECT DATE '2022-12-09', 300, 1000004, 'Cash', 2 FROM DUAL UNION ALL
SELECT DATE '2022-04-15', 1000, 1000005, 'Cash', 2 FROM DUAL UNION ALL
SELECT DATE '2022-04-15', 1000, 1000006, 'Credit Card', 3 FROM DUAL UNION ALL
SELECT DATE '2022-10-31', 250, 1000007, 'Cash', 4 FROM DUAL UNION ALL
SELECT DATE '2022-10-25', 250, 1000008, 'Cash', 4 FROM DUAL UNION ALL
SELECT DATE '2022-09-20', 130, 1000009, 'Credit Card', 5 FROM DUAL UNION ALL
SELECT DATE '2022-05-20', 120, 10000010, 'Credit Card', 5 FROM DUAL;
CREATE TABLE supplier (Supplier_Name, Supplier_id) AS
SELECT 'ABC', 11 FROM DUAL UNION ALL
SELECT 'ACCC', 12 FROM DUAL UNION ALL
SELECT 'ADEF', 13 FROM DUAL UNION ALL
SELECT 'AJKL', 14 FROM DUAL UNION ALL
SELECT 'AFLR', 15 FROM DUAL;
CREATE TABLE supplier_remit (City, Country, Supplier_Remit_id, Supplier_id) AS
SELECT 'Boston', 'US', 111, 11 FROM DUAL UNION ALL
SELECT 'Oak', 'US', 112, 12 FROM DUAL UNION ALL
SELECT 'Albany', 'US', 113, 13 FROM DUAL UNION ALL
SELECT 'Madison', 'US', 114, 14 FROM DUAL UNION ALL
SELECT 'Los Ang', 'US', 115, 15 FROM DUAL;
Outputs:
CUSTOMER_ID
CUSTOMER_ACCOUNT_NUMBER
CUSTOMER_STATUS
COUNTRY
LAST_INVOICE_DATE
LATEST_PAYMENT_METHOD
LATEST_INVOICE_AMOUNT
MISSING_INVOICES_THIS_YEAR
MISSING_INVOICES_LAST_YEAR
1
1501
Active
US
2023-01-01 00:00:00
Cash
100
0
10
3
1503
Active
US
2022-04-15 00:00:00
Credit Card
1000
1
11
4
1504
Active
US
2022-10-31 00:00:00
Cash
250
1
11
fiddle
In order to find what's missing, you have to first define what should be there, so you need to create a calendar of every month. Then you can use outer joins to the invoice table to find where there aren't any records for that month for that customer. There are lots of ways to write SQL to do this. Here's one:
WITH months AS(SELECT /*+ MATERIALIZE */ *
FROM (SELECT 'Current' year,
ADD_MONTHS(TRUNC(SYSDATE,'YYYY'),ROWNUM-1) month_start
FROM [any table with at least 12 rows]
WHERE ROWNUM <= 12)
WHERE month_start < SYSDATE
UNION ALL
SELECT 'Previous' year,
ADD_MONTHS(TRUNC(ADD_MONTHS(SYSDATE,-12),'YYYY'),ROWNUM-1)
FROM [any table with at least 12 rows]
WHERE ROWNUM <= 12)
SELECT customer.*,
inv.invoice_amount most_recent_invoice_amount,
inv.payment_method most_recent_payment_method,
(SELECT COUNT(*)
FROM months,
invoice
WHERE months.year = 'Current'
AND months.month_start = TRUNC(invoice_date(+),'MM')
AND invoice.customer_id(+) = customer.customer_id
AND invoice.customer_id IS NULL) missed_current_year_months,
(SELECT COUNT(*)
FROM months,
invoice
WHERE months.year = 'Previous'
AND months.month_start = TRUNC(invoice_date(+),'MM')
AND invoice.customer_id(+) = customer.customer_id
AND invoice.customer_id IS NULL) missed_previous_year_months
FROM customer
OUTER APPLY (SELECT invoice_amount,
payment_method
FROM (SELECT invoice_amount,
payment_method,
ROW_NUMBER() OVER (ORDER BY invoice_date DESC) seq
FROM invoice
WHERE invoice.customer_id = customer.customer_id)
WHERE seq = 1) inv

SQL statement to return the Min and Max amount of stock per article for a given Month

I have a table from which I am trying to return the quantity per day that the article was in the system.
Example is in table Bestand the are multiple palletes of a different articles that each have a Booking In and Out date; I am try to find out the Min and Max amount of stock that was in the system per article and month.
My thinking is that if I can return the stock quantity for each day and then read out the Min and Max values.
The Timespan would be set at the time of running the SQL and the articles would be fixed.
To find out the quantity for each day I have used the following SQL:
SELECT DISTINCT
a.artbez1 AS Artikelbezeichnung,
b.artikelnr AS Artikelnummer,
SUM(CASE WHEN TO_DATE('2019-11-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') BETWEEN b.neu_datum AND b.aender_datum THEN 1 * b.menge_ist ELSE 0 END) AS "01 Nov 2019"
FROM
artikel a, bestand b
WHERE
b.artikelnr IN ('273632002', .... (huge long list of numbers) ....)
AND b.artikelnr = a.artikelnr
GROUP BY
a.artbez1, b.artikelnr;
This returns for example:
ARTIKELBEZEICHNUNG
ARTIKELNUMMER
01 Nov 2019
SC-4400.CW
220450002
39
S-320.FK120
220502004
0
H-595.FK120
220800004
35
AC-548.FK209
220948032
0
AS-6800.CW
221355002
20
I would like return this for each day of the Month and then from that return the Min and Max Value for each Article
I have the following SQL to return the days of a given Month and was wondering if anyone had any ideas on how they could be combined (If at all possible):
SELECT to_date('01.11.2019','dd.mm.yyyy')+LEVEL-1
FROM dual
CONNECT BY LEVEL <= TO_CHAR(LAST_DAY(to_date('01.11.2019','dd.mm.yyyy')),'DD')
DATES
2019-11-01 00:00:00
2019-11-02 00:00:00
2019-11-03 00:00:00
2019-11-04 00:00:00
2019-11-05 00:00:00
2019-11-06 00:00:00
2019-11-07 00:00:00
The result i am try to get would be something like:
ARTIKELBEZEICHNUNG
ARTIKELNUMMER
Nov 19 Min
Nov 19 Max
SC-4400.CW
220450002
5
39
S-320.FK120
220502004
0
15
H-595.FK120
220800004
2
35
AC-548.FK209
220948032
0
0
AS-6800.CW
221355002
10
20
Is this at all possible in SQL?
Thanks for taking the time to read my post.
JeRi
You can use a partitioned outer join:
WITH calendar ( day ) AS (
SELECT DATE '2019-11-01'
FROM DUAL
UNION ALL
SELECT day + INTERVAL '1' DAY
FROM calendar
WHERE day < LAST_DAY( DATE '2019-11-01' )
),
daily_totals ( artbez1, Artikelnr, Day, total_menge_ist ) AS (
SELECT MAX( ab.artbez1 ),
ab.artikelnr,
c.day,
COALESCE( SUM( ab.menge_ist ), 0 )
FROM calendar c
LEFT OUTER JOIN
( SELECT a.artikelnr,
a.artbez1,
b.neu_datum,
b.aender_datum,
b.menge_ist
FROM artikel a
LEFT JOIN bestand b
ON ( a.artikelnr = b.artikelnr )
-- WHERE b.artikelnr IN ('273632002', .... (huge long list of numbers) ....)
) ab
PARTITION BY ( ab.artikelnr, ab.artbez1 )
ON ( c.day BETWEEN ab.neu_datum AND ab.aender_datum )
GROUP BY ab.artikelnr, c.day
)
SELECT MAX( artbez1 ) AS Artikelbezeichnung,
artikelnr AS Artikelnummer,
TRUNC( day, 'MM' ) AS month,
MIN( total_menge_ist ) AS min_total_menge_ist,
MAX( total_menge_ist ) AS max_total_menge_ist
FROM daily_totals
GROUP BY artikelnr, TRUNC( day, 'MM' );
Which, for the sample data:
CREATE TABLE artikel ( artikelnr, artbez1 ) AS
SELECT 220450002, 'SC-4400.CW' FROM DUAL UNION ALL
SELECT 220502004, 'S-320.FK120' FROM DUAL UNION ALL
SELECT 220800004, 'H-595.FK120' FROM DUAL UNION ALL
SELECT 220948032, 'AC-548.FK209' FROM DUAL UNION ALL
SELECT 221355002, 'AS-6800.CW' FROM DUAL;
CREATE TABLE bestand ( artikelnr, neu_datum, aender_datum, menge_ist ) AS
SELECT 220450002, DATE '2019-10-30', DATE '2019-11-01', 20 FROM DUAL UNION ALL
SELECT 220450002, DATE '2019-11-01', DATE '2019-11-05', 19 FROM DUAL UNION ALL
SELECT 220502004, DATE '2019-11-05', DATE '2019-11-03', 5 FROM DUAL UNION ALL
SELECT 220800004, DATE '2019-11-01', DATE '2019-11-15', 35 FROM DUAL UNION ALL
SELECT 221355002, DATE '2019-10-20', DATE '2019-11-05', 5 FROM DUAL UNION ALL
SELECT 221355002, DATE '2019-10-25', DATE '2019-11-10', 5 FROM DUAL UNION ALL
SELECT 221355002, DATE '2019-10-28', DATE '2019-11-13', 5 FROM DUAL UNION ALL
SELECT 221355002, DATE '2019-10-30', DATE '2019-11-15', 5 FROM DUAL UNION ALL
SELECT 221355002, DATE '2019-11-05', DATE '2019-11-20', 5 FROM DUAL;
Outputs:
ARTIKELBEZEICHNUNG | ARTIKELNUMMER | MONTH | MIN_TOTAL_MENGE_IST | MAX_TOTAL_MENGE_IST
:----------------- | ------------: | :------------------ | ------------------: | ------------------:
SC-4400.CW | 220450002 | 2019-11-01 00:00:00 | 0 | 39
S-320.FK120 | 220502004 | 2019-11-01 00:00:00 | 0 | 0
AC-548.FK209 | 220948032 | 2019-11-01 00:00:00 | 0 | 0
H-595.FK120 | 220800004 | 2019-11-01 00:00:00 | 0 | 35
AS-6800.CW | 221355002 | 2019-11-01 00:00:00 | 0 | 25
db<>fiddle here

Complete datetime list despite missing values for analysis

I have been a reader of StackOverflow for a long time already. Nearly always, I find my answers here. Great!
But now, I have a problem where I could not find a solution yet:
I have an Oracle table with an ID, a date and a value.
Think of it as a list of outstanding tasks (value) and project (ID). When the number of open tasks of a project changes, the list gets a new entry.
It looks like this:
ID month RemainingValue
1 01/01/2018 1000
1 01/03/2018 800
1 01/04/2018 600
1 01/07/2018 400
2 01/02/2018 700
2 01/03/2018 650
2 01/05/2018 600
3 01/02/2018 50
3 01/08/2018 40
4 01/01/2018 2000
(DateFormat DD/MM/YYYY)
Please note that not every month has a value!
I have to calculate the sum of all open tasks per month.
If there is no value for a month, that means that the number of open tasks has not decreased in that month, so the query should take the previous existing value of this project into account.
I want this result:
month result calculation remark
01/01/2018 3000 =1000 + 2000 ID 1+4
01/02/2018 3750 =1000 + 700 + 50 + 2000 ID 1[value of 01/01/2018]+2+3+4[value of 01/01/2018]
01/03/2018 3500 =800 + 650 + 50 + 2000 ID 1+2+3[value of 01/02/2018]+4[value of 01/01/2018]
What I did already:
I created a list of all months using the CONNECT BY LEVEL functionality, similar to this:
SELECT LEVEL AS NR
, ADD_MONTHS('01-JAN-2018', LEVEL) AS MONAT
FROM DUAL
CONNECT BY LEVEL <= (... SOME.SUBSELECT.TO.GET.THE.NUMBER.OF.LEVELS ...)
Then I can outer join this list of months to the table above based on the date.
The problem is, that the values of tasks of the unfilled months are NULL. But I don't want them to be NULL, I want the previous filled value in this case.
I tried with LAG functions, but without success so far.
I am hoping that there is some functionality in (Oracle) SQL which can do this where I don't know of.
Or maybe it's even simpler and I just don't get it...
The resulting query should also be performant, because the underlying table has millions of rows. So I'd like to avoid slow PL/SQL solutions...
Hope you can help!
Kind Regards,
Nadine
You could use an analytic query to get the latest value for each ID, up to and including that month (relying on the default windowing clause.
This uses your sample data in a CTE, and adds another one to provide your month generation (may not match your desired range of course):
-- first CTE to replictae your data
with my_table(ID, month, RemainingValue) as (
select 1, to_date('01/01/2018', 'DD/MM/YYYY'), 1000 from dual
union all select 1, to_date('01/03/2018', 'DD/MM/YYYY'), 800 from dual
union all select 1, to_date('01/04/2018', 'DD/MM/YYYY'), 600 from dual
union all select 1, to_date('01/07/2018', 'DD/MM/YYYY'), 400 from dual
union all select 2, to_date('01/02/2018', 'DD/MM/YYYY'), 700 from dual
union all select 2, to_date('01/03/2018', 'DD/MM/YYYY'), 650 from dual
union all select 2, to_date('01/05/2018', 'DD/MM/YYYY'), 600 from dual
union all select 3, to_date('01/02/2018', 'DD/MM/YYYY'), 50 from dual
union all select 3, to_date('01/08/2018', 'DD/MM/YYYY'), 40 from dual
union all select 4, to_date('01/01/2018', 'DD/MM/YYYY'), 2000 from dual
),
-- second CTE to generate all months, here based on full range in table
-- use whatever you currently have for this
all_months (month) as (
select add_months(min_month, + level - 1)
from (
select min(month) as min_month, max(month) as max_month from my_table
)
connect by level <= months_between(max_month, min_month) + 1
)
select am.month, mt.id,
max(mt.remainingvalue) keep (dense_rank last order by mt.month) as remainingvalue
from all_months am
left join my_table mt on mt.month <= am.month
group by am.month, mt.id
order by id, month;
which gets
MONTH ID REMAININGVALUE
---------- ---------- --------------
2018-01-01 1 1000
2018-02-01 1 1000
2018-03-01 1 800
2018-04-01 1 600
2018-05-01 1 600
2018-06-01 1 600
2018-07-01 1 400
2018-08-01 1 400
2018-02-01 2 700
2018-03-01 2 650
2018-04-01 2 650
...
And then use that as an inline view or another CTE, summing the values:
-- first CTE to replictae your data
with my_table(ID, month, RemainingValue) as (
select 1, to_date('01/01/2018', 'DD/MM/YYYY'), 1000 from dual
union all select 1, to_date('01/03/2018', 'DD/MM/YYYY'), 800 from dual
union all select 1, to_date('01/04/2018', 'DD/MM/YYYY'), 600 from dual
union all select 1, to_date('01/07/2018', 'DD/MM/YYYY'), 400 from dual
union all select 2, to_date('01/02/2018', 'DD/MM/YYYY'), 700 from dual
union all select 2, to_date('01/03/2018', 'DD/MM/YYYY'), 650 from dual
union all select 2, to_date('01/05/2018', 'DD/MM/YYYY'), 600 from dual
union all select 3, to_date('01/02/2018', 'DD/MM/YYYY'), 50 from dual
union all select 3, to_date('01/08/2018', 'DD/MM/YYYY'), 40 from dual
union all select 4, to_date('01/01/2018', 'DD/MM/YYYY'), 2000 from dual
),
-- second CTE to generate all months, here based on full range in table
-- use whatever you currently have for this
all_months (month) as (
select add_months(min_month, + level - 1)
from (
select min(month) as min_month, max(month) as max_month from my_table
)
connect by level <= months_between(max_month, min_month) + 1
),
-- third CTE to get the latest value for each ID up to that month
inter (month, id, remainingvalue) as (
select am.month, mt.id,
max(mt.remainingvalue) keep (dense_rank last order by mt.month)
from all_months am
left join my_table mt on mt.month <= am.month
group by am.month, mt.id
)
select month, sum(remainingvalue) as result,
listagg(remainingvalue, ' + ') within group (order by id) as calculation
from inter
group by month
order by month;
which gets:
MONTH RESULT CALCULATION
---------- ---------- ------------------------------
2018-01-01 3000 1000 + 2000
2018-02-01 3750 1000 + 700 + 50 + 2000
2018-03-01 3500 800 + 650 + 50 + 2000
2018-04-01 3300 600 + 650 + 50 + 2000
2018-05-01 3250 600 + 600 + 50 + 2000
2018-06-01 3250 600 + 600 + 50 + 2000
2018-07-01 3050 400 + 600 + 50 + 2000
2018-08-01 3040 400 + 600 + 40 + 2000
I assume the calculation and remark columns in your result are just for our benefit to understand the logic; if you do want them then calculation is easy to get as above, and if you want remark too then you just need to identify the month the value comes from too, and add another listagg:
...
-- third CTE to get the latest value for each ID up to that month
inter (month, id, remainingvalue, valuemonth) as (
select am.month, mt.id,
max(mt.remainingvalue) keep (dense_rank last order by mt.month),
max(mt.month)
from all_months am
left join my_table mt on mt.month <= am.month
group by am.month, mt.id
)
select month, sum(remainingvalue) as result,
'= ' || listagg(remainingvalue, ' + ') within group (order by id) as calculation,
'ID ' || listagg(id || case when month != valuemonth then '[' || valuemonth || ']' end, ' + ')
within group (order by id) as remark
from inter
group by month
order by month;
MONTH RESULT CALCULATION REMARK
---------- ---------- ------------------------ -----------------------------------------------------------------
2018-01-01 3000 = 1000 + 2000 ID 1 + 4
2018-02-01 3750 = 1000 + 700 + 50 + 2000 ID 1[2018-01-01] + 2 + 3 + 4[2018-01-01]
2018-03-01 3500 = 800 + 650 + 50 + 2000 ID 1 + 2 + 3[2018-02-01] + 4[2018-01-01]
2018-04-01 3300 = 600 + 650 + 50 + 2000 ID 1 + 2[2018-03-01] + 3[2018-02-01] + 4[2018-01-01]
2018-05-01 3250 = 600 + 600 + 50 + 2000 ID 1[2018-04-01] + 2 + 3[2018-02-01] + 4[2018-01-01]
2018-06-01 3250 = 600 + 600 + 50 + 2000 ID 1[2018-04-01] + 2[2018-05-01] + 3[2018-02-01] + 4[2018-01-01]
2018-07-01 3050 = 400 + 600 + 50 + 2000 ID 1 + 2[2018-05-01] + 3[2018-02-01] + 4[2018-01-01]
2018-08-01 3040 = 400 + 600 + 40 + 2000 ID 1[2018-07-01] + 2[2018-05-01] + 3 + 4[2018-01-01]
You seem to want to sum the most recent value for each project before a given month.
The following gets the remaining value for each id for each month:
with months as (
SELECT LEVEL AS NR, ADD_MONTHS(DATE '2018-01-01', LEVEL) AS MONTH
FROM DUAL
CONNECT BY LEVEL <= (... SOME.SUBSELECT.TO.GET.THE.NUMBER.OF.LEVELS ...)
)
select m.month, i.id,
(select max(t.remainingvalue) keep (dense_rank first order by month desc)
from t
where t.id = i.id and t.month <= m.month
) as remainingvalue
from months m cross join
(select distinct id from t) i;
Now let's just summarize this:
with months as (
SELECT LEVEL AS NR, ADD_MONTHS(DATE '2018-01-01', LEVEL) AS MONTH
FROM DUAL
CONNECT BY LEVEL <= (... SOME.SUBSELECT.TO.GET.THE.NUMBER.OF.LEVELS ...)
)
select month, sum(remainingvalue)
from (select m.month, i.id,
(select max(t.remainingvalue) keep (dense_rank first order by month desc)
from t
where t.id = i.id and t.month <= m.month
) as remaining_value
from months m cross join
(select distinct id from t) i
) mi
group by month;

How to get data for previous 7 days based on set of dates in groups in sql

I was going through this forum for my query to get data for previous 7 days,but most of them give it for current date.Below is my requirement:
I have a Table 1 as below:
These are start dates of week which is monday
from_date
2016-01-04
2016-01-11
2016-01-18
Table 2
I have all days of week here starting from monday.Ex: jan 04 - monday to jan 10 - sunday and so on for other weeks also.
get_date flag value
2016-01-04 N 4
2016-01-05 N 9
2016-01-06 Y 2
2016-01-07 Y 13
2016-01-08 Y 7
2016-01-09 Y 8
2016-01-10 Y 8
2016-01-11 Y 1
2016-01-12 Y 9
2016-01-13 N 8
2016-01-14 N 24
2016-01-15 N 8
2016-01-16 Y 4
2016-01-17 Y 5
2016-01-18 Y 9
2016-01-19 Y 2
2016-01-20 Y 8
2016-01-21 Y 4
2016-01-22 N 9
2016-01-23 N 87
2016-01-24 Y 3
Expected Result
here wk is the unique number for each start-end dates respectively
avg value is the avg of the values for the dates in that week.
last 2 days of the week are weekend days.
say 2016-01-09 and 2016-01-10 are weekends
from_date get_date Wk Total_days Total_weekdays_flag_Y Total_weekenddays_flag_Y Avg_value
2016-01-04 2016-01-10 1 7 3 2 6.714285714
2016-01-11 2016-01-17 2 7 2 2 8.428571429
2016-01-18 2016-01-24 3 7 4 1 17.42857143
Could anyone help me with this as I am not good at sql.
Thanks
select
from_date
, Wk
, count(case when day_of_week <=5 and flag = 'Y' then 1 end) as Total_weekdays_flag_Y
, count(case when day_of_week > 5 and flag = 'Y' then 1 end) as Total_weekenddays_flag_Y
, avg(value) as Avg_value
from (
select trunc(get_date,'IW') as from_date
, (trunc(get_date,'IW')- trunc(date'2016-01-04','IW'))/7 + 1 as Wk
, flag
, value
, get_date - trunc(get_date,'IW') as day_of_week
from Table_2)
group by from_date, Wk
order by from_date, Wk;
EDIT:
/*generate some test_data for table 2*/
with table_2 (get_date, flag, value) as (
select date'2016-01-03' + level,
DECODE(mod(level,3),0,'Y','N'),
round(dbms_random.value(0,10))
from dual connect by level < 101
),
/*generate some test_weeks for table 1*/
table_1 (FROM_date) as (select date'2016-01-04' + (level-1)*7 from dual connect by level < 101 )
/*main query */
select
from_date
, Wk
, count(day_of_week) as total
, count(case when day_of_week <=5 and flag = 'Y' then 1 end) as Total_weekdays_flag_Y
, count(case when day_of_week > 5 and flag = 'Y' then 1 end) as Total_weekenddays_flag_Y
, avg(value) as Avg_value
from (
select last_value(from_date ignore nulls) over (order by get_date) as from_date
,last_value(Wk ignore nulls) over (order by get_date) as Wk
, flag
, value
, get_date - trunc(get_date,'IW') as day_of_week
from Table_2 t2
full join (select row_number() over (order by from_date) as wk,from_date from table_1) t1 on t2.get_date = t1.from_date
)
group by from_date, Wk
having count(day_of_week) > 0
order by from_date, Wk
In the query below, I create the test data right within the query; in final form, you would delete the subqueries table_1 and table_2 and use the rest.
The syntax will work from Oracle 11.2 on. In Oracle 11.1, you need to move the column names in factored subqueries to the select... from dual part. Or, since you really only have one subquery (prep) and an outer query, you can write prep as an actual, in-line subquery.
Your arithmetic seems off on the average for the first week.
In your sample output you use get_date for the last day of the week. That is odd, since in table_2 that name has a different meaning. I used to_date in my output. I also do not show total_days - that is always 7, so why include it at all? (If it is not always 7, then there is something you didn't tell us; anyway, a count(...), if that is what it should be, is easy to add).
with
-- begin test data, can be removed in final solution
table_1 ( from_date ) as (
select date '2016-01-04' from dual union all
select date '2016-01-11' from dual union all
select date '2016-01-18' from dual
)
,
table_2 ( get_date, flag, value ) as (
select date '2016-01-04', 'N', 4 from dual union all
select date '2016-01-05', 'N', 9 from dual union all
select date '2016-01-06', 'Y', 2 from dual union all
select date '2016-01-07', 'Y', 13 from dual union all
select date '2016-01-08', 'Y', 7 from dual union all
select date '2016-01-09', 'Y', 8 from dual union all
select date '2016-01-10', 'Y', 8 from dual union all
select date '2016-01-11', 'Y', 1 from dual union all
select date '2016-01-12', 'Y', 9 from dual union all
select date '2016-01-13', 'N', 8 from dual union all
select date '2016-01-14', 'N', 24 from dual union all
select date '2016-01-15', 'N', 8 from dual union all
select date '2016-01-16', 'Y', 4 from dual union all
select date '2016-01-17', 'Y', 5 from dual union all
select date '2016-01-18', 'Y', 9 from dual union all
select date '2016-01-19', 'Y', 2 from dual union all
select date '2016-01-20', 'Y', 8 from dual union all
select date '2016-01-21', 'Y', 4 from dual union all
select date '2016-01-22', 'N', 9 from dual union all
select date '2016-01-23', 'N', 87 from dual union all
select date '2016-01-24', 'Y', 3 from dual
),
-- end test data, continue actual query
prep ( get_date, flag, value, from_date, wd_flag ) as (
select t2.get_date, t2.flag, t2.value, t1.from_date,
case when t2.get_date - t1.from_date <= 4 then 'wd' else 'we' end
from table_1 t1 inner join table_2 t2
on t2.get_date between t1.from_date and t1.from_date + 6
)
select from_date,
from_date + 6 as to_date,
row_number() over (order by from_date) as wk,
count(case when flag = 'Y' and wd_flag = 'wd' then 1 end)
as total_weekday_Y,
count(case when flag = 'Y' and wd_flag = 'we' then 1 end)
as total_weekend_Y,
round(avg(value), 6) as avg_value
from prep
group by from_date;
Output:
FROM_DATE TO_DATE WK TOTAL_WEEKDAY_Y TOTAL_WEEKEND_Y AVG_VALUE
---------- ---------- ---- --------------- --------------- ----------
2016-01-04 2016-01-10 1 3 2 7.285714
2016-01-11 2016-01-17 2 2 2 8.428571
2016-01-18 2016-01-24 3 4 1 17.428571

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;