I have list of invoices. I wonna group data into sales_departments but only this clients who have first invoice in 2018 Year.
Below my "data". Sory for mystake but i can't paste data better.
Client_Number sales_depart Netto Number_Invoice Invoice_Date
1022562 0140 113 51545121188 04.11.18
1022562 0140 139 5586258568 04.01.18
1022564 0140 171 5586713889 03.22.18
1022565 0140 211 5587169210 03.22.17
1022566 0140 259 5587624531 03.22.16
1022567 0140 319 5588079852 03.23.15
1022568 0140 392 5588535173 03.23.14
1022569 0140 483 5588990494 03.23.13
1022570 0140 594 5589445815 03.23.12
1022571 0140 730 5589901136 03.24.11
1008144 0530 898 5590356457 01.31.18
1008145 0530 104 5590811778 02.20.18
1008146 0530 358 5591267099 02.20.17
1008147 0530 671 5591722420 02.21.16
1008148 0530 055 5592177741 02.21.15
1008149 0530 528 5592633062 02.21.14
1008150 0530 109 5593088383 02.21.13
1016058 0130 825 5593543704 01.18.18
1051643 0290 704 5593999025 01.30.18
1051643 0290 175 5595199025 01.30.17
1049433 0180 786 5594454346 02.20.18
1010219 0180 117 5594909667 02.28.18
1033233 0180 754 5595364988 02.28.18
1004914 0160 767 5595820309 02.14.18
1011699 0140 244 5596275630 02.20.18
1007323 0160 290 5596730951 04.19.18
1004914 0160 036 5597186272 02.07.18
1005837 0530 645 5597641593 04.19.18
The data I would like to receive
Sales Dept Count_Clintr
0130 1
0160 2
0180 3
You can do it with a single table scan using the HAVING clause:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( Client_Number, sales_depart, Netto, Number_Invoice, Invoice_Date ) AS
SELECT 1022562, '0140', 113, 51545121188, DATE '2018-04-11' FROM DUAL UNION ALL
SELECT 1022562, '0140', 139, 5586258568, DATE '2018-04-01' FROM DUAL UNION ALL
SELECT 1022564, '0140', 171, 5586713889, DATE '2018-03-22' FROM DUAL UNION ALL
SELECT 1022565, '0140', 211, 5587169210, DATE '2017-03-22' FROM DUAL UNION ALL
SELECT 1022566, '0140', 259, 5587624531, DATE '2016-03-22' FROM DUAL UNION ALL
SELECT 1022567, '0140', 319, 5588079852, DATE '2015-03-23' FROM DUAL UNION ALL
SELECT 1022568, '0140', 392, 5588535173, DATE '2014-03-23' FROM DUAL UNION ALL
SELECT 1022569, '0140', 483, 5588990494, DATE '2013-03-23' FROM DUAL UNION ALL
SELECT 1022570, '0140', 594, 5589445815, DATE '2012-03-23' FROM DUAL UNION ALL
SELECT 1022571, '0140', 730, 5589901136, DATE '2011-03-24' FROM DUAL UNION ALL
SELECT 1008144, '0530', 898, 5590356457, DATE '2018-01-31' FROM DUAL UNION ALL
SELECT 1008145, '0530', 104, 5590811778, DATE '2018-02-20' FROM DUAL UNION ALL
SELECT 1008146, '0530', 358, 5591267099, DATE '2017-02-20' FROM DUAL UNION ALL
SELECT 1008147, '0530', 671, 5591722420, DATE '2016-02-21' FROM DUAL UNION ALL
SELECT 1008148, '0530', 055, 5592177741, DATE '2015-02-21' FROM DUAL UNION ALL
SELECT 1008149, '0530', 528, 5592633062, DATE '2014-02-21' FROM DUAL UNION ALL
SELECT 1008150, '0530', 109, 5593088383, DATE '2013-02-21' FROM DUAL UNION ALL
SELECT 1016058, '0130', 825, 5593543704, DATE '2018-01-18' FROM DUAL UNION ALL
SELECT 1051643, '0290', 704, 5593999025, DATE '2018-01-30' FROM DUAL UNION ALL
SELECT 1051643, '0290', 175, 5595199025, DATE '2017-01-30' FROM DUAL UNION ALL
SELECT 1049433, '0180', 786, 5594454346, DATE '2018-02-20' FROM DUAL UNION ALL
SELECT 1010219, '0180', 117, 5594909667, DATE '2018-02-28' FROM DUAL UNION ALL
SELECT 1033233, '0180', 754, 5595364988, DATE '2018-02-28' FROM DUAL UNION ALL
SELECT 1004914, '0160', 767, 5595820309, DATE '2018-02-14' FROM DUAL UNION ALL
SELECT 1011699, '0140', 244, 5596275630, DATE '2018-02-20' FROM DUAL UNION ALL
SELECT 1007323, '0160', 290, 5596730951, DATE '2018-04-19' FROM DUAL UNION ALL
SELECT 1004914, '0160', 036, 5597186272, DATE '2018-02-07' FROM DUAL UNION ALL
SELECT 1005837, '0530', 645, 5597641593, DATE '2018-04-19' FROM DUAL;
Query 1:
SELECT sales_depart,
COUNT( client_number )
FROM (
SELECT sales_depart,
client_number
FROM table_name
GROUP BY
sales_depart,
client_number
HAVING MIN( Invoice_date ) >= DATE '2018-01-01'
)
GROUP BY sales_depart
Results:
| SALES_DEPART | COUNT(CLIENT_NUMBER) |
|--------------|----------------------|
| 0140 | 3 |
| 0530 | 3 |
| 0180 | 3 |
| 0130 | 1 |
| 0160 | 2 |
One method uses not exists for filtering:
select sales_department, sum(netto)
from t
where not exists (select 1
from t t2
where t2.client = t.client and
t2.invoice_date < date '2018-01-01'
)
group by sales_department;
For performance, you want an index on (client, invoice_date). Also not the use of the date keyword to define the constant using ISO/ANSI standard formats.
Here is the solution to your problem:
SELECT sales_depart, COUNT(Distinct client_Number) AS Distinct_Clients
FROM Table1
GROUP BY sales_depart
HAVING MIN(TO_DATE(invoice_date, 'MM.DD.YY')) >= TO_DATE('01-01-2018', 'DD-MM-YYYY')
ORDER BY sales_depart
OUTPUT:
SALES_DEPART DISTINCT_CLIENTS
0130 1
0160 2
0180 3
Note: Since, Client_Number = 1004914 repeats 2 times in Sales_depart = 0160 so it is counted 1 time only.
Link to the demo:
http://sqlfiddle.com/#!4/28c71/1
SELECT sd.sales_depart,COUNT(sd.Client_Number) AS Client_Number
FROM sales_departments sd
INNER JOIN
(
SELECT sales_depart,MIN(Invoice_Date) AS Invoice_Date
FROM sales_departments
WHERE Invoice_date >= DATE '2018-01-01'
GROUP BY sales_depart
) T
ON T.sales_depart=sd.sales_depart
GROUP BY sd.sales_depart
Related
I am working in oracle sql. I have two table which is linked to each other by one column - company_id (see on the picture); I want to merge table 1 to table 2 and calculate 6 month average (6 month before period from table 2) of income for each company_id and each date of table2. I appreciate any code/idea how to solve this task.
You can use an analytic range window to calculate the averages for table1 and then JOIN the result to table2:
SELECT t2.*,
t1.avg_income_6,
t1.avg_income_12
FROM table2 t2
LEFT OUTER JOIN (
SELECT company_id,
dt,
ROUND(AVG(income) OVER (
PARTITION BY company_id
ORDER BY dt
RANGE BETWEEN INTERVAL '5' MONTH PRECEDING
AND INTERVAL '0' MONTH FOLLOWING
), 2) AS avg_income_6,
ROUND(AVG(income) OVER (
PARTITION BY company_id
ORDER BY dt
RANGE BETWEEN INTERVAL '11' MONTH PRECEDING
AND INTERVAL '0' MONTH FOLLOWING
), 2) AS avg_income_12
FROM table1
) t1
ON (t2.company_id = t1.company_id AND t2.dt = t1.dt);
Which, for the sample data:
CREATE TABLE table1 (company_id, dt, income) AS
SELECT 1, date '2019-01-01', 65 FROM DUAL UNION ALL
SELECT 1, date '2019-02-01', 58 FROM DUAL UNION ALL
SELECT 1, date '2019-03-01', 12 FROM DUAL UNION ALL
SELECT 1, date '2019-04-01', 81 FROM DUAL UNION ALL
SELECT 1, date '2019-05-01', 38 FROM DUAL UNION ALL
SELECT 1, date '2019-06-01', 81 FROM DUAL UNION ALL
SELECT 1, date '2019-07-01', 38 FROM DUAL UNION ALL
SELECT 1, date '2019-08-01', 69 FROM DUAL UNION ALL
SELECT 1, date '2019-09-01', 54 FROM DUAL UNION ALL
SELECT 1, date '2019-10-01', 90 FROM DUAL UNION ALL
SELECT 1, date '2019-11-01', 10 FROM DUAL UNION ALL
SELECT 1, date '2019-12-01', 12 FROM DUAL UNION ALL
SELECT 1, date '2020-01-01', 11 FROM DUAL UNION ALL
SELECT 1, date '2020-02-01', 83 FROM DUAL UNION ALL
SELECT 1, date '2020-03-01', 18 FROM DUAL UNION ALL
SELECT 1, date '2020-04-01', 28 FROM DUAL UNION ALL
SELECT 1, date '2020-05-01', 52 FROM DUAL UNION ALL
SELECT 1, date '2020-06-01', 21 FROM DUAL UNION ALL
SELECT 1, date '2020-07-01', 54 FROM DUAL UNION ALL
SELECT 1, date '2020-08-01', 30 FROM DUAL UNION ALL
SELECT 1, date '2020-09-01', 12 FROM DUAL UNION ALL
SELECT 1, date '2020-10-01', 25 FROM DUAL UNION ALL
SELECT 1, date '2020-11-01', 86 FROM DUAL UNION ALL
SELECT 1, date '2020-12-01', 4 FROM DUAL UNION ALL
SELECT 1, date '2021-01-01', 10 FROM DUAL UNION ALL
SELECT 1, date '2021-02-01', 72 FROM DUAL UNION ALL
SELECT 1, date '2021-03-01', 65 FROM DUAL UNION ALL
SELECT 1, date '2021-04-01', 25 FROM DUAL;
CREATE TABLE table2 (company_id, dt) AS
SELECT 1, date '2019-06-01' FROM DUAL UNION ALL
SELECT 1, date '2019-09-01' FROM DUAL UNION ALL
SELECT 1, date '2019-12-01' FROM DUAL UNION ALL
SELECT 1, date '2020-01-01' FROM DUAL UNION ALL
SELECT 1, date '2020-07-01' FROM DUAL UNION ALL
SELECT 1, date '2020-08-01' FROM DUAL UNION ALL
SELECT 1, date '2021-03-01' FROM DUAL UNION ALL
SELECT 1, date '2021-04-01' FROM DUAL;
Outputs:
COMPANY_ID
DT
AVG_INCOME_6
AVG_INCOME_12
1
2019-06-01 00:00:00
55.83
55.83
1
2019-09-01 00:00:00
60.17
55.11
1
2019-12-01 00:00:00
45.5
50.67
1
2020-01-01 00:00:00
41
46.17
1
2020-07-01 00:00:00
42.67
41.83
1
2020-08-01 00:00:00
33.83
38.58
1
2021-03-01 00:00:00
43.67
38.25
1
2021-04-01 00:00:00
43.67
38
db<>fiddle here
I don't think you need any window function here (if you were thinking of analytic functions); ordinary avg with appropriate join conditions should do the job.
Sample data:
SQL> with
2 table1 (company_id, datum, income) as
3 (select 1, date '2019-01-01', 65 from dual union all
4 select 1, date '2019-02-01', 58 from dual union all
5 select 1, date '2019-03-01', 12 from dual union all
6 select 1, date '2019-04-01', 81 from dual union all
7 select 1, date '2019-05-01', 38 from dual union all
8 select 1, date '2019-06-01', 81 from dual union all
9 select 1, date '2019-07-01', 38 from dual union all
10 select 1, date '2019-08-01', 69 from dual union all
11 select 1, date '2019-09-01', 54 from dual union all
12 select 1, date '2019-10-01', 90 from dual union all
13 select 1, date '2019-11-01', 10 from dual union all
14 select 1, date '2019-12-01', 12 from dual
15 ),
16 table2 (company_id, datum) as
17 (select 1, date '2019-06-01' from dual union all
18 select 1, date '2019-09-01' from dual union all
19 select 1, date '2019-12-01' from dual union all
20 select 1, date '2020-01-01' from dual union all
21 select 1, date '2020-07-01' from dual
22 )
Query begins here:
23 select b.company_id,
24 b.datum ,
25 round(avg(a.income), 2) result
26 from table1 a join table2 b on a.company_id = b.company_id
27 and a.datum > add_months(b.datum, -6)
28 and a.datum <= b.datum
29 group by b.company_id, b.datum;
COMPANY_ID DATUM RESULT
---------- -------- ----------
1 01.06.19 55,83
1 01.09.19 60,17
1 01.12.19 45,5
1 01.01.20 47
SQL>
I'm trying to write a query in Oracle SQL that aggregates values by some ids, where I have the following table as an input:
ID
SOME_DATE
RANK_POSITION
301
20211201
1
301
20211202
2
301
20211203
3
649
20211201
1
649
20211202
2
649
20211206
3
649
20211208
4
649
20211211
5
758
20211212
1
758
20211222
2
And y want to obtain something like this:
ID
FIRST_IN_RANK_DATE
SECOND_IN_RANK_DATE
301
01/12/2021
02/12/2021
649
01/12/2021
02/12/2021
758
12/12/2021
22/12/2021
Where FIRST_IN_RANK_DATE, is the date that is the first in the RANK_POSITION for the ID, and SECOND_IN_RANK_DATE is the date that is second in RANK_POSITION for the specific ID.
You can use conditional aggregation:
SELECT id,
MAX(CASE rank_position WHEN 1 THEN some_date END) AS first_in_rank_date,
MAX(CASE rank_position WHEN 2 THEN some_date END) AS second_in_rank_date
FROM table_name
GROUP BY id
Or PIVOT:
SELECT *
FROM table_name
PIVOT (
MAX(some_date)
FOR rank_position IN (
1 AS first_in_rank_date,
2 AS second_in_rank_date
)
)
Or, from Oracle 12, MATCH_RECOGNIZE:
SELECT *
FROM table_name
MATCH_RECOGNIZE (
PARTITION BY id
ORDER BY rank_position
MEASURES
rank1.some_date AS first_in_rank_date,
rank2.some_date AS second_in_rank_date
PATTERN ( ^ rank1 rank2 )
DEFINE
rank1 AS rank_position = 1,
rank2 AS rank_position = 2
)
Which, for the sample data:
CREATE TABLE table_name (ID, SOME_DATE, RANK_POSITION) AS
SELECT 301, DATE '2021-12-01', 1 FROM DUAL UNION ALL
SELECT 301, DATE '2021-12-02', 2 FROM DUAL UNION ALL
SELECT 301, DATE '2021-12-03', 3 FROM DUAL UNION ALL
SELECT 649, DATE '2021-12-01', 1 FROM DUAL UNION ALL
SELECT 649, DATE '2021-12-02', 2 FROM DUAL UNION ALL
SELECT 649, DATE '2021-12-06', 3 FROM DUAL UNION ALL
SELECT 649, DATE '2021-12-08', 4 FROM DUAL UNION ALL
SELECT 649, DATE '2021-12-11', 5 FROM DUAL UNION ALL
SELECT 758, DATE '2021-12-12', 1 FROM DUAL UNION ALL
SELECT 758, DATE '2021-12-22', 2 FROM DUAL;
All output:
ID
FIRST_IN_RANK_DATE
SECOND_IN_RANK_DATE
301
2021-12-01 00:00:00
2021-12-02 00:00:00
649
2021-12-01 00:00:00
2021-12-02 00:00:00
758
2021-12-12 00:00:00
2021-12-22 00:00:00
db<>fiddle here
How can I get comma separated values from a table in a single cell in Oracle SQL? How do I do it?
For example, if the input table I have is the following::
id
value
datetime
9245
44
2021-10-15 00:00:00
9245
42
2021-09-14 00:00:00
9245
41
2021-08-13 00:00:00
9245
62
2021-05-14 00:00:00
9245
100
2021-04-15 00:00:00
9245
131
2021-03-16 00:00:00
9245
125
2021-02-12 00:00:00
9245
137
2021-01-18 00:00:00
8873
358
2021-10-15 00:00:00
8873
373
2021-09-14 00:00:00
8873
373
2021-08-13 00:00:00
8873
411
2021-07-14 00:00:00
8873
381
2021-06-14 00:00:00
8873
275
2021-05-14 00:00:00
8873
216
2021-04-15 00:00:00
8873
189
2021-03-16 00:00:00
8873
157
2021-02-12 00:00:00
8873
191
2021-01-18 00:00:00
My idea would be to achieve a grouping like the one below:
id
grouped_values
8873
191,157,Null,Null,Null,381,411,373,373,358
9245
137,125,131,100,62,Null,Null,41,42,44
As you can see in this case I have 2 different ids, when I group by id I would like the missing dates to have a null value and for the first value to correspond to the first date for that id. Also, when there are no values on that date, add a null value.
How can I put those null values in the correct place? How do I detect the absence of these values and set them as null? How to make the positions of the values correlate with the dates?
I've been trying to use the listgg or xmlagg function to group, but at the moment I don't know how to cover the missing places.
Another option; read comments within code. Sample data in lines #1 - 9; query begins at line #10.
SQL> with test(id, value, datum) as
2 (select 1, 5, date '2021-01-10' from dual union all --> missing February and March
3 select 1, 8, date '2021-04-13' from dual union all
4 select 1, 3, date '2021-05-22' from dual union all
5 --
6 select 2, 1, date '2021-03-21' from dual union all
7 select 2, 7, date '2021-04-22' from dual union all --> missing May and June
8 select 2, 9, date '2021-07-10' from dual
9 ),
10 -- calendar per ID
11 minimax as
12 (select id, trunc(min(datum), 'mm') mindat, trunc(max(datum), 'mm') maxdat
13 from test
14 group by id
15 ),
16 calendar as
17 (select m.id,
18 'null' value,
19 add_months(m.mindat, column_value - 1) datum
20 from minimax m
21 cross join table(cast(multiset(select level from dual
22 connect by level <= ceil(months_between(maxdat, mindat)) + 1
23 ) as sys.odcinumberlist))
24 )
25 select c.id,
26 listagg(nvl(to_char(t.value), c.value), ', ') within group (order by c.datum) result
27 from calendar c left join test t on t.id = c.id and trunc(t.datum, 'mm') = c.datum
28 group by c.id;
ID RESULT
---------- ----------------------------------------
1 5, null, null, 8, 3
2 1, 7, null, null, 9
SQL>
Use a PARTITIONed OUTER JOIN:
WITH calendar (day) AS (
SELECT DATE '2021-01-18' FROM DUAL UNION ALL
SELECT DATE '2021-02-12' FROM DUAL UNION ALL
SELECT DATE '2021-03-16' FROM DUAL UNION ALL
SELECT DATE '2021-04-15' FROM DUAL UNION ALL
SELECT DATE '2021-05-14' FROM DUAL UNION ALL
SELECT DATE '2021-06-14' FROM DUAL UNION ALL
SELECT DATE '2021-07-14' FROM DUAL UNION ALL
SELECT DATE '2021-08-13' FROM DUAL UNION ALL
SELECT DATE '2021-09-14' FROM DUAL UNION ALL
SELECT DATE '2021-10-15' FROM DUAL
-- Or
-- SELECT DISTINCT datetime FROM table_name
)
SELECT t.id,
LISTAGG(COALESCE(TO_CHAR(t.value), 'null'), ',')
WITHIN GROUP (ORDER BY c.day)
AS grouped_values
FROM calendar c
LEFT OUTER JOIN table_name t
PARTITION BY (t.id)
ON (c.day = t.datetime)
GROUP BY t.id
Or:
WITH calendar (day) AS (
SELECT ADD_MONTHS(DATE '2021-01-01', LEVEL - 1)
FROM DUAL
CONNECT BY LEVEL <= 10
-- or
-- SELECT ADD_MONTHS(min_dt, LEVEL - 1)
-- FROM (
-- SELECT MIN(TRUNC(datetime, 'MM')) AS min_dt,
-- MAX(TRUNC(datetime, 'MM')) AS max_dt
-- FROM table_name
-- )
-- CONNECT BY ADD_MONTHS(min_dt, LEVEL - 1) <= max_dt
)
SELECT t.id,
LISTAGG(COALESCE(TO_CHAR(t.value), 'null'), ',') WITHIN GROUP (ORDER BY c.day)
AS grouped_values
FROM calendar c
LEFT OUTER JOIN table_name t
PARTITION BY (t.id)
ON (c.day = TRUNC(t.datetime, 'MM'))
GROUP BY t.id
Which, for the sample data:
CREATE TABLE table_name (id, value, datetime) AS
SELECT 9245, 137, DATE '2021-01-18' FROM DUAL UNION ALL
SELECT 9245, 125, DATE '2021-02-12' FROM DUAL UNION ALL
SELECT 9245, 131, DATE '2021-03-16' FROM DUAL UNION ALL
SELECT 9245, 100, DATE '2021-04-15' FROM DUAL UNION ALL
SELECT 9245, 62, DATE '2021-05-14' FROM DUAL UNION ALL
SELECT 9245, 41, DATE '2021-08-13' FROM DUAL UNION ALL
SELECT 9245, 42, DATE '2021-09-14' FROM DUAL UNION ALL
SELECT 9245, 44, DATE '2021-10-15' FROM DUAL UNION ALL
SELECT 8873, 191, DATE '2021-01-18' FROM DUAL UNION ALL
SELECT 8873, 157, DATE '2021-02-12' FROM DUAL UNION ALL
SELECT 8873, 189, DATE '2021-03-16' FROM DUAL UNION ALL
SELECT 8873, 216, DATE '2021-04-15' FROM DUAL UNION ALL
SELECT 8873, 275, DATE '2021-05-14' FROM DUAL UNION ALL
SELECT 8873, 381, DATE '2021-06-14' FROM DUAL UNION ALL
SELECT 8873, 411, DATE '2021-07-14' FROM DUAL UNION ALL
SELECT 8873, 373, DATE '2021-08-13' FROM DUAL UNION ALL
SELECT 8873, 373, DATE '2021-09-14' FROM DUAL UNION ALL
SELECT 8873, 358, DATE '2021-10-15' FROM DUAL;
Both output:
ID
GROUPED_VALUES
8873
191,157,189,216,275,381,411,373,373,358
9245
137,125,131,100,62,null,null,41,42,44
db<>fiddle here
You can run this query directly without creating any tables. Here is a version with start date and end date with parameters:
SELECT
FE.id
,LISTAGG(NVL(TO_CHAR(TRUNC(CON.value)), 'null'), ',') WITHIN GROUP (ORDER BY FE.the_date ASC) GROUPED_VALUES
FROM
(--begin from1
SELECT id
,EXTRACT (YEAR FROM the_date) the_year
,EXTRACT (MONTH FROM the_date) the_month
,the_date
FROM
(
SELECT distinct id
FROM
(
SELECT 9245 id, 137 value, DATE '2021-01-18' datetime FROM DUAL UNION ALL
SELECT 9245, 125, DATE '2021-02-12' FROM DUAL UNION ALL
SELECT 9245, 131, DATE '2021-03-16' FROM DUAL UNION ALL
SELECT 9245, 100, DATE '2021-04-15' FROM DUAL UNION ALL
SELECT 9245, 62, DATE '2021-05-14' FROM DUAL UNION ALL
SELECT 9245, 41, DATE '2021-08-13' FROM DUAL UNION ALL
SELECT 9245, 42, DATE '2021-09-14' FROM DUAL UNION ALL
SELECT 9245, 44, DATE '2021-10-15' FROM DUAL UNION ALL
SELECT 8873, 191, DATE '2021-01-18' FROM DUAL UNION ALL
SELECT 8873, 157, DATE '2021-02-12' FROM DUAL UNION ALL
SELECT 8873, 189, DATE '2021-03-16' FROM DUAL UNION ALL
SELECT 8873, 216, DATE '2021-04-15' FROM DUAL UNION ALL
SELECT 8873, 275, DATE '2021-05-14' FROM DUAL UNION ALL
SELECT 8873, 381, DATE '2021-06-14' FROM DUAL UNION ALL
SELECT 8873, 411, DATE '2021-07-14' FROM DUAL UNION ALL
SELECT 8873, 373, DATE '2021-08-13' FROM DUAL UNION ALL
SELECT 8873, 373, DATE '2021-09-14' FROM DUAL UNION ALL
SELECT 8873, 358, DATE '2021-10-15' FROM DUAL
) table_name
) PS CROSS JOIN
( -- in this sub query you can change the **start date** and **end date** to change the ranges
SELECT
MIN(TO_DATE('2021-01-01' /*start date*/, 'YYYY-MM-DD') + LEVEL - 1) the_date
FROM DUAL
CONNECT BY
TO_DATE('2021-01-01' /*start date*/, 'YYYY-MM-DD') + LEVEL - 1 <= TO_DATE('2021-10-01' /*end date*/, 'YYYY-MM-DD')
GROUP BY EXTRACT (YEAR FROM TO_DATE('2021-01-01' /*start date*/, 'YYYY-MM-DD') + LEVEL - 1)
,EXTRACT (MONTH FROM TO_DATE('2021-01-01' /*start date*/, 'YYYY-MM-DD') + LEVEL - 1)
) the_dates
) FE LEFT OUTER JOIN --end from1
(
SELECT
table_name.id id
, EXTRACT(MONTH FROM table_name.datetime) the_month
, EXTRACT(YEAR FROM table_name.datetime) the_year
,MAX(table_name.datetime) datetime
,SUM(table_name.value) value
FROM
(
SELECT 9245 id, 137 value, DATE '2021-01-18' datetime FROM DUAL UNION ALL
SELECT 9245, 125, DATE '2021-02-12' FROM DUAL UNION ALL
SELECT 9245, 131, DATE '2021-03-16' FROM DUAL UNION ALL
SELECT 9245, 100, DATE '2021-04-15' FROM DUAL UNION ALL
SELECT 9245, 62, DATE '2021-05-14' FROM DUAL UNION ALL
SELECT 9245, 41, DATE '2021-08-13' FROM DUAL UNION ALL
SELECT 9245, 42, DATE '2021-09-14' FROM DUAL UNION ALL
SELECT 9245, 44, DATE '2021-10-15' FROM DUAL UNION ALL
SELECT 8873, 191, DATE '2021-01-18' FROM DUAL UNION ALL
SELECT 8873, 157, DATE '2021-02-12' FROM DUAL UNION ALL
SELECT 8873, 189, DATE '2021-03-16' FROM DUAL UNION ALL
SELECT 8873, 216, DATE '2021-04-15' FROM DUAL UNION ALL
SELECT 8873, 275, DATE '2021-05-14' FROM DUAL UNION ALL
SELECT 8873, 381, DATE '2021-06-14' FROM DUAL UNION ALL
SELECT 8873, 411, DATE '2021-07-14' FROM DUAL UNION ALL
SELECT 8873, 373, DATE '2021-08-13' FROM DUAL UNION ALL
SELECT 8873, 373, DATE '2021-09-14' FROM DUAL UNION ALL
SELECT 8873, 358, DATE '2021-10-15' FROM DUAL
) table_name
GROUP BY table_name.id, EXTRACT(YEAR FROM table_name.datetime), EXTRACT(MONTH FROM table_name.datetime)
) Con ON FE.id = Con.id AND FE.the_year = CON.the_year AND FE.the_month = CON.the_month
GROUP BY FE.id
Note: this query also recognizes the missing dates automatically
I am looking to display the group which is grouped on id column where trans_cd='Audit' only on max trans_proc_dt of that group. There should not be any other trans_cd='Audit' execpt for max trans_proc_dt.
ID TRANS_PROC_DT TRANS_CD TRANS_AMT
165 5/13/2020 Renewal 553
165 10/22/2020 Cancellation -376
165 11/24/2020 Audit 3
165 6/2/2021 Change 0
165 6/2/2021 Audit -7
165 6/3/2021 Audit 0
497 5/1/2020 Renewal 1394
497 1/11/2021 Cancellation -578
497 2/10/2021 Audit -3
497 4/28/2021 Audit 76
497 5/12/2021 Audit -73
497 6/2/2021 Change 0
511 4/27/2020 Renewal 4409
511 7/30/2020 Change 0
511 10/5/2020 Cancellation -2558
511 2/18/2021 Audit 2806
577 5/15/2020 Renewal 829
577 2/12/2021 Audit -123
577 4/28/2021 Audit 118
577 5/12/2021 Audit 5
577 6/2/2021 Change 0
577 6/2/2021 Audit -5
577 6/3/2021 Audit 0
577 12/4/2020 Renewal 1996
577 6/2/2021 Change 0
751 5/13/2020 Renewal 1307
751 1/28/2021 Cancellation -523
751 3/3/2021 Audit 481
751 4/28/2021 Audit 120
751 5/12/2021 Audit -601
751 6/2/2021 Change 0
751 6/2/2021 Audit 601
751 6/3/2021 Audit 0
984 5/13/2020 Renewal 1081
984 11/2/2020 Change 0
984 6/3/2021 Audit 0
My output should be
ID TRANS_PROC_DT TRANS_CD TRANS_AMT
511 4/27/2020 Renewal 4409
511 7/30/2020 Change 0
511 10/5/2020 Cancellation -2558
511 1/27/2021 Renewal 4409
511 2/18/2021 Audit 2806
984 5/13/2020 Renewal 1081
984 11/2/2020 Change 0
984 6/3/2021 Audit 0
I can't think how to go about getting my result set.
You can use analytic functions to calculate absolute maximum date and minimum date of Audit code per group, then compare them: if no more such transactions occurred on other dates, they should be equal.
with a(ID, TRANS_PROC_DT, TRANS_CD, TRANS_AMT) as (
select 165, '5/13/2020', 'Renewal', 553 from dual union all
select 165, '10/22/2020', 'Cancellation', -376 from dual union all
select 165, '11/24/2020', 'Audit', 3 from dual union all
select 165, '6/2/2021', 'Change', 0 from dual union all
select 165, '6/2/2021', 'Audit', -7 from dual union all
select 165, '6/3/2021', 'Audit', 0 from dual union all
select 497, '5/1/2020', 'Renewal', 1394 from dual union all
select 497, '1/11/2021', 'Cancellation', -578 from dual union all
select 497, '2/10/2021', 'Audit', -3 from dual union all
select 497, '4/28/2021', 'Audit', 76 from dual union all
select 497, '5/12/2021', 'Audit', -73 from dual union all
select 497, '6/2/2021', 'Change', 0 from dual union all
select 511, '4/27/2020', 'Renewal', 4409 from dual union all
select 511, '7/30/2020', 'Change', 0 from dual union all
select 511, '10/5/2020', 'Cancellation', -2558 from dual union all
select 511, '2/18/2021', 'Audit', 2806 from dual union all
select 577, '5/15/2020', 'Renewal', 829 from dual union all
select 577, '2/12/2021', 'Audit', -123 from dual union all
select 577, '4/28/2021', 'Audit', 118 from dual union all
select 577, '5/12/2021', 'Audit', 5 from dual union all
select 577, '6/2/2021', 'Change', 0 from dual union all
select 577, '6/2/2021', 'Audit', -5 from dual union all
select 577, '6/3/2021', 'Audit', 0 from dual union all
select 577, '12/4/2020', 'Renewal', 1996 from dual union all
select 577, '6/2/2021', 'Change', 0 from dual union all
select 751, '5/13/2020', 'Renewal', 1307 from dual union all
select 751, '1/28/2021', 'Cancellation', -523 from dual union all
select 751, '3/3/2021', 'Audit', 481 from dual union all
select 751, '4/28/2021', 'Audit', 120 from dual union all
select 751, '5/12/2021', 'Audit', -601 from dual union all
select 751, '6/2/2021', 'Change', 0 from dual union all
select 751, '6/2/2021', 'Audit', 601 from dual union all
select 751, '6/3/2021', 'Audit', 0 from dual union all
select 984, '5/13/2020', 'Renewal', 1081 from dual union all
select 984, '11/2/2020', 'Change', 0 from dual union all
select 984, '6/3/2021', 'Audit', 0 from dual
)
, last_dt as (
select
a.*
, min(
case trans_cd
when 'Audit'
then to_date(TRANS_PROC_DT, 'mm/dd/yyyy')
end
) over(partition by id)
as audit_dt
, max(to_date(TRANS_PROC_DT, 'mm/dd/yyyy'))
over(partition by id)
as max_dt
from a
)
select
id
, trans_proc_dt
, trans_cd
, trans_amt
from last_dt
where max_dt = audit_dt
order by id, to_date(TRANS_PROC_DT, 'mm/dd/yyyy')
ID | TRANS_PROC_DT | TRANS_CD | TRANS_AMT
--: | :------------ | :----------- | --------:
511 | 4/27/2020 | Renewal | 4409
511 | 7/30/2020 | Change | 0
511 | 10/5/2020 | Cancellation | -2558
511 | 2/18/2021 | Audit | 2806
984 | 5/13/2020 | Renewal | 1081
984 | 11/2/2020 | Change | 0
984 | 6/3/2021 | Audit | 0
db<>fiddle here
You may try below -
Select
*
from
tableName tn
where
ID not in
(
Select
ID
from
tableName t
where
trans_cd = 'Audit'
and TRANS_PROC_DT <> (
Select
max(TRANS_PROC_DT)
from
tableName t1
where
t1.ID = t.ID)
)
The goal is to select the count of distinct customer_id's who have not made a purchase in the rolling 30 day period prior to every day in the calendar year 2016. I have created a calendar table in my database to join to.
Here is an example table for reference, let's say you have customers orders normalized as follows:
+-------------+------------+----------+
| customer_id | date | order_id |
+-------------+------------+----------+
| 123 | 01/25/2016 | 1000 |
+-------------+------------+----------+
| 123 | 04/27/2016 | 1025 |
+-------------+------------+----------+
| 444 | 02/02/2016 | 1010 |
+-------------+------------+----------+
| 521 | 01/23/2016 | 998 |
+-------------+------------+----------+
| 521 | 01/24/2016 | 999 |
+-------------+------------+----------+
The goal output is effectively a calendar with 1 row for every single day of 2016 with a count on each day of how many customers "lapsed" on that day, meaning their last purchase was 30 days or more prior from that day of the year. The final output will look like this:
+------------+--------------+
| date | lapsed_count |
+------------+--------------+
| 01/01/2016 | 0 |
+------------+--------------+
| 01/02/2016 | 0 |
+------------+--------------+
| ... | ... |
+------------+--------------+
| 03/01/2016 | 12 |
+------------+--------------+
| 03/02/2016 | 9 |
+------------+--------------+
| 03/03/2016 | 7 |
+------------+--------------+
This data does not exist in 2015, therefore it's not possible for Jan-01-2016 to have a count of lapsed customers because that is the first possible day to ever make a purchase.
So for customer_id #123, they purchased on 01/25/2016 and 04/27/2016. They should have 2 lapse counts because their purchases are more than 30 days apart. One lapse occurring on 2/24/2016 and another lapse on 05/27/2016.
Customer_id#444 only purchased once, so they should have one lapse count for 30 days after 02/02/2016 on 03/02/2016.
Customer_id#521 is tricky, since they purchased with a frequency of 1 day we will not count the first purchase on 03/02/2016, so there is only one lapse starting from their last purchase of 03/03/2016. The count for the lapse will occur on 04/02/2016 (+30 days).
If you have a table of dates, here is one expensive method:
select date,
sum(case when prev_date < date - 30 then 1 else 0 end) as lapsed
from (select c.date, o.customer_id, max(o.date) as prev_date
from calendar c cross join
(select distinct customer_id from orders) c left join
orders o
on o.date <= c.date and o.customer_id = c.customer_id
group by c.date, o.customer_id
) oc
group by date;
For each date/customer pair, it determines the latest purchase the customer made before the date. It then uses this information to count the lapsed.
To be honest, this will probably work well on a handful of dates, but not for a full year's worth.
Apologies, I didn't read your question properly the first time around. This query will give you all the lapses you have. It takes each order and uses an analytic function to work out the next order date - if the gap is greater than 30 days then a lapse is recorded
WITH
cust_orders (customer_id , order_date , order_id )
AS
(SELECT 1, TO_DATE('01/01/2016','DD/MM/YYYY'), 1001 FROM dual UNION ALL
SELECT 1, TO_DATE('29/01/2016','DD/MM/YYYY'), 1002 FROM dual UNION ALL
SELECT 1, TO_DATE('01/03/2016','DD/MM/YYYY'), 1003 FROM dual UNION ALL
SELECT 2, TO_DATE('01/01/2016','DD/MM/YYYY'), 1004 FROM dual UNION ALL
SELECT 2, TO_DATE('29/01/2016','DD/MM/YYYY'), 1005 FROM dual UNION ALL
SELECT 2, TO_DATE('01/04/2016','DD/MM/YYYY'), 1006 FROM dual UNION ALL
SELECT 2, TO_DATE('01/06/2016','DD/MM/YYYY'), 1007 FROM dual UNION ALL
SELECT 2, TO_DATE('01/08/2016','DD/MM/YYYY'), 1008 FROM dual UNION ALL
SELECT 3, TO_DATE('01/09/2016','DD/MM/YYYY'), 1009 FROM dual UNION ALL
SELECT 3, TO_DATE('01/12/2016','DD/MM/YYYY'), 1010 FROM dual UNION ALL
SELECT 3, TO_DATE('02/12/2016','DD/MM/YYYY'), 1011 FROM dual UNION ALL
SELECT 3, TO_DATE('03/12/2016','DD/MM/YYYY'), 1012 FROM dual UNION ALL
SELECT 3, TO_DATE('04/12/2016','DD/MM/YYYY'), 1013 FROM dual UNION ALL
SELECT 3, TO_DATE('05/12/2016','DD/MM/YYYY'), 1014 FROM dual UNION ALL
SELECT 3, TO_DATE('06/12/2016','DD/MM/YYYY'), 1015 FROM dual UNION ALL
SELECT 3, TO_DATE('07/12/2016','DD/MM/YYYY'), 1016 FROM dual
)
SELECT
customer_id
,order_date
,order_id
,next_order_date
,order_date + 30 lapse_date
FROM
(SELECT
customer_id
,order_date
,order_id
,LEAD(order_date) OVER (PARTITION BY customer_id ORDER BY order_date) next_order_date
FROM
cust_orders
)
WHERE NVL(next_order_date,sysdate) - order_date > 30
;
Now join that to a set of dates and run a COUNT function (enter the year parameter as YYYY) :
WITH
cust_orders (customer_id , order_date , order_id )
AS
(SELECT 1, TO_DATE('01/01/2016','DD/MM/YYYY'), 1001 FROM dual UNION ALL
SELECT 1, TO_DATE('29/01/2016','DD/MM/YYYY'), 1002 FROM dual UNION ALL
SELECT 1, TO_DATE('01/03/2016','DD/MM/YYYY'), 1003 FROM dual UNION ALL
SELECT 2, TO_DATE('01/01/2016','DD/MM/YYYY'), 1004 FROM dual UNION ALL
SELECT 2, TO_DATE('29/01/2016','DD/MM/YYYY'), 1005 FROM dual UNION ALL
SELECT 2, TO_DATE('01/04/2016','DD/MM/YYYY'), 1006 FROM dual UNION ALL
SELECT 2, TO_DATE('01/06/2016','DD/MM/YYYY'), 1007 FROM dual UNION ALL
SELECT 2, TO_DATE('01/08/2016','DD/MM/YYYY'), 1008 FROM dual UNION ALL
SELECT 3, TO_DATE('01/09/2016','DD/MM/YYYY'), 1009 FROM dual UNION ALL
SELECT 3, TO_DATE('01/12/2016','DD/MM/YYYY'), 1010 FROM dual UNION ALL
SELECT 3, TO_DATE('02/12/2016','DD/MM/YYYY'), 1011 FROM dual UNION ALL
SELECT 3, TO_DATE('03/12/2016','DD/MM/YYYY'), 1012 FROM dual UNION ALL
SELECT 3, TO_DATE('04/12/2016','DD/MM/YYYY'), 1013 FROM dual UNION ALL
SELECT 3, TO_DATE('05/12/2016','DD/MM/YYYY'), 1014 FROM dual UNION ALL
SELECT 3, TO_DATE('06/12/2016','DD/MM/YYYY'), 1015 FROM dual UNION ALL
SELECT 3, TO_DATE('07/12/2016','DD/MM/YYYY'), 1016 FROM dual
)
,calendar (date_value)
AS
(SELECT TO_DATE('01/01/'||:P_year,'DD/MM/YYYY') + (rownum -1)
FROM all_tables
WHERE rownum < (TO_DATE('31/12/'||:P_year,'DD/MM/YYYY') - TO_DATE('01/01/'||:P_year,'DD/MM/YYYY')) + 2
)
SELECT
calendar.date_value
,COUNT(*)
FROM
(
SELECT
customer_id
,order_date
,order_id
,next_order_date
,order_date + 30 lapse_date
FROM
(SELECT
customer_id
,order_date
,order_id
,LEAD(order_date) OVER (PARTITION BY customer_id ORDER BY order_date) next_order_date
FROM
cust_orders
)
WHERE NVL(next_order_date,sysdate) - order_date > 30
) lapses
,calendar
WHERE 1=1
AND calendar.date_value = TRUNC(lapses.lapse_date)
GROUP BY
calendar.date_value
;
Or if you really want every date printed out then use this :
WITH
cust_orders (customer_id , order_date , order_id )
AS
(SELECT 1, TO_DATE('01/01/2016','DD/MM/YYYY'), 1001 FROM dual UNION ALL
SELECT 1, TO_DATE('29/01/2016','DD/MM/YYYY'), 1002 FROM dual UNION ALL
SELECT 1, TO_DATE('01/03/2016','DD/MM/YYYY'), 1003 FROM dual UNION ALL
SELECT 2, TO_DATE('01/01/2016','DD/MM/YYYY'), 1004 FROM dual UNION ALL
SELECT 2, TO_DATE('29/01/2016','DD/MM/YYYY'), 1005 FROM dual UNION ALL
SELECT 2, TO_DATE('01/04/2016','DD/MM/YYYY'), 1006 FROM dual UNION ALL
SELECT 2, TO_DATE('01/06/2016','DD/MM/YYYY'), 1007 FROM dual UNION ALL
SELECT 2, TO_DATE('01/08/2016','DD/MM/YYYY'), 1008 FROM dual UNION ALL
SELECT 3, TO_DATE('01/09/2016','DD/MM/YYYY'), 1009 FROM dual UNION ALL
SELECT 3, TO_DATE('01/12/2016','DD/MM/YYYY'), 1010 FROM dual UNION ALL
SELECT 3, TO_DATE('02/12/2016','DD/MM/YYYY'), 1011 FROM dual UNION ALL
SELECT 3, TO_DATE('03/12/2016','DD/MM/YYYY'), 1012 FROM dual UNION ALL
SELECT 3, TO_DATE('04/12/2016','DD/MM/YYYY'), 1013 FROM dual UNION ALL
SELECT 3, TO_DATE('05/12/2016','DD/MM/YYYY'), 1014 FROM dual UNION ALL
SELECT 3, TO_DATE('06/12/2016','DD/MM/YYYY'), 1015 FROM dual UNION ALL
SELECT 3, TO_DATE('07/12/2016','DD/MM/YYYY'), 1016 FROM dual
)
,lapses
AS
(SELECT
customer_id
,order_date
,order_id
,next_order_date
,order_date + 30 lapse_date
FROM
(SELECT
customer_id
,order_date
,order_id
,LEAD(order_date) OVER (PARTITION BY customer_id ORDER BY order_date) next_order_date
FROM
cust_orders
)
WHERE NVL(next_order_date,sysdate) - order_date > 30
)
,calendar (date_value)
AS
(SELECT TO_DATE('01/01/'||:P_year,'DD/MM/YYYY') + (rownum -1)
FROM all_tables
WHERE rownum < (TO_DATE('31/12/'||:P_year,'DD/MM/YYYY') - TO_DATE('01/01/'||:P_year,'DD/MM/YYYY')) + 2
)
SELECT
calendar.date_value
,(SELECT COUNT(*)
FROM lapses
WHERE calendar.date_value = lapses.lapse_date
)
FROM
calendar
WHERE 1=1
ORDER BY
calendar.date_value
;
Here's how I'd do it:
WITH your_table AS (SELECT 123 customer_id, to_date('24/01/2016', 'dd/mm/yyyy') order_date, 12345 order_id FROM dual UNION ALL
SELECT 123 customer_id, to_date('24/01/2016', 'dd/mm/yyyy') order_date, 12346 order_id FROM dual UNION ALL
SELECT 123 customer_id, to_date('25/01/2016', 'dd/mm/yyyy') order_date, 12347 order_id FROM dual UNION ALL
SELECT 123 customer_id, to_date('24/02/2016', 'dd/mm/yyyy') order_date, 12347 order_id FROM dual UNION ALL
SELECT 123 customer_id, to_date('16/03/2016', 'dd/mm/yyyy') order_date, 12348 order_id FROM dual UNION ALL
SELECT 123 customer_id, to_date('18/04/2016', 'dd/mm/yyyy') order_date, 12349 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('20/02/2016', 'dd/mm/yyyy') order_date, 12350 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('01/03/2016', 'dd/mm/yyyy') order_date, 12351 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('03/03/2016', 'dd/mm/yyyy') order_date, 12352 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('18/04/2016', 'dd/mm/yyyy') order_date, 12353 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('20/05/2016', 'dd/mm/yyyy') order_date, 12354 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('23/06/2016', 'dd/mm/yyyy') order_date, 12355 order_id FROM dual UNION ALL
SELECT 456 customer_id, to_date('19/01/2017', 'dd/mm/yyyy') order_date, 12356 order_id FROM dual),
-- end of mimicking your_table with data in it
lapsed_info AS (SELECT customer_id,
order_date,
CASE WHEN TRUNC(SYSDATE) - order_date <= 30 THEN NULL
WHEN COUNT(*) OVER (PARTITION BY customer_id ORDER BY order_date RANGE BETWEEN 1 FOLLOWING AND 30 FOLLOWING) = 0 THEN order_date+30
ELSE NULL
END lapsed_date
FROM your_table),
dates AS (SELECT to_date('01/01/2016', 'dd/mm/yyyy') + LEVEL -1 dt
FROM dual
CONNECT BY to_date('01/01/2016', 'dd/mm/yyyy') + LEVEL -1 <= TRUNC(SYSDATE))
SELECT dates.dt,
COUNT(li.lapsed_date) lapsed_count
FROM dates
LEFT OUTER JOIN lapsed_info li ON dates.dt = li.lapsed_date
GROUP BY dates.dt
ORDER BY dates.dt;
Results:
DT LAPSED_COUNT
---------- ------------
01/01/2016 0
<snip>
23/01/2016 0
24/01/2016 0
25/01/2016 0
26/01/2016 0
<snip>
19/02/2016 0
20/02/2016 0
21/02/2016 0
22/02/2016 0
23/02/2016 0
24/02/2016 1
25/02/2016 0
<snip>
29/02/2016 0
01/03/2016 0
02/03/2016 0
03/03/2016 0
04/03/2016 0
<snip>
15/03/2016 0
16/03/2016 0
17/03/2016 0
<snip>
20/03/2016 0
21/03/2016 0
22/03/2016 0
<snip>
30/03/2016 0
31/03/2016 0
01/04/2016 0
02/04/2016 1
03/04/2016 0
<snip>
14/04/2016 0
15/04/2016 1
16/04/2016 0
17/04/2016 0
18/04/2016 0
19/04/2016 0
<snip>
17/05/2016 0
18/05/2016 2
19/05/2016 0
20/05/2016 0
21/05/2016 0
<snip>
18/06/2016 0
19/06/2016 1
20/06/2016 0
21/06/2016 0
22/06/2016 0
23/06/2016 0
24/06/2016 0
<snip>
22/07/2016 0
23/07/2016 1
24/07/2016 0
<snip>
18/01/2017 0
19/01/2017 0
20/01/2017 0
<snip>
08/02/2017 0
This takes your data, and uses an the analytic count function to work out the number of rows that have a value within 30 days of (but excluding) the current row's date.
Then we apply a case expression to determine that if the row has a date within 30 days of today's date, we'll count those as not lapsed. If a count of 0 was returned, then the row is considered lapsed and we'll output the lapsed date as the order_date plus 30 days. Any other count result means the row has not lapsed.
The above is all worked out in the lapsed_info subquery.
Then all we need to do is list the dates (see the dates subquery) and outer join the lapsed_info subquery to it based on the lapsed_date and then do a count of the lapsed dates for each day.