Subtracting a column based on another column - oracle

This table consists of transaction data, where each row is one transaction exchange. Main data to collect: average amount of years it takes users to switch from checks to credit cards when paying.
I'm trying to subtract the year a person first used a check with the year he first used a credit card to the same receiving bank account. Example data is attached below. This query is freaking complicated and I'm wondering if I should/can even do this, but this is what I got so far
SELECT
ID,
BankAcc#,
FROM table
GROUP BY
ID,
BankAcc#,
TransYear,
Method
ORDER BY
ID,BackAcc#,TransYear ASC
Example table (sorry couldn't embed the photo since I'm new)
My idea was to group ID-Bank#-TransYear-Method into a single row, with the TransYear being the earliest year of that Method, by using ORDER BY ASC LIMIT 1. Problems I'm running into:
Oracle doesn't support LIMIT after the ORDER BY clause. I tried using OFFSET or FETCH but it didn't work for some reason after the ORDER BY clause. I have Oracle 12.9.0.71 so I theoretically should have that function, but nope. I also tried WHERE rownum=1 but it limits all my results to 1 instead of limiting the ID-Bank-Year-Method group to 1.
Even if I do manage to get rows of the correct data, I still don't know how I can subtract the year values of check against card. Issue is that the data is in the same column and I need to distinguish it with another column before subtracting the year values.
Any thoughts? Appreciate any help, especially since this is really complicated.

Two methods - one using MIN and CASE and the other using MIN and PIVOT
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( PersonId, BankAcc, TransactionNo, TransYear, method) as
select 1, 10, 1, 2011, 'check' from dual union
select 1, 10, 5, 2012, 'card' from dual union
select 2, 11, 7, 2012, 'check' from dual union
select 2, 15, 10, 2012, 'check' from dual union
select 2, 15, 11, 2014, 'card' from dual union
select 2, 15, 14, 2016, 'card' from dual union
select 2, 19, 15, 2009, 'check' from dual union
select 2, 19, 16, 2015, 'card' from dual union
select 3, 20, 25, 2017, 'check' from dual union
select 3, 21, 34, 2015, 'check' from dual union
select 3, 21, 51, 2017, 'card' from dual;
Query 1:
SELECT PersonID,
BankAcc,
MIN( CASE method WHEN 'card' THEN TransYear END )
- MIN( CASE method WHEN 'check' THEN TransYear END ) AS diff
FROM table_name
GROUP BY PersonID,
BankAcc
ORDER BY PersonID,
BankAcc
Results:
| PERSONID | BANKACC | DIFF |
|----------|---------|--------|
| 1 | 10 | 1 |
| 2 | 11 | (null) |
| 2 | 15 | 2 |
| 2 | 19 | 6 |
| 3 | 20 | (null) |
| 3 | 21 | 2 |
Query 2:
SELECT PersonId,
BankAcc,
crd - chk AS diff
FROM ( SELECT PersonId, BankAcc, TransYear, method FROM table_name )
PIVOT( MIN( transyear ) FOR method IN ( 'check' AS chk, 'card' AS crd ) )
ORDER BY PersonID, BankAcc
Results:
| PERSONID | BANKACC | DIFF |
|----------|---------|--------|
| 1 | 10 | 1 |
| 2 | 11 | (null) |
| 2 | 15 | 2 |
| 2 | 19 | 6 |
| 3 | 20 | (null) |
| 3 | 21 | 2 |

This query returns the result you posted; see if it is really OK.
SQL> with test (person_id, acc, trans, tyear, method) as
2 (select 1, 10, 1, 2011, 'check' from dual union
3 select 1, 10, 5, 2012, 'card' from dual union
4 select 2, 11, 7, 2012, 'check' from dual union
5 select 2, 15, 10, 2012, 'check' from dual union
6 select 2, 15, 11, 2014, 'card' from dual union
7 select 2, 15, 14, 2016, 'card' from dual union
8 select 2, 19, 15, 2009, 'check' from dual union
9 select 2, 19, 16, 2015, 'card' from dual union
10 select 3, 20, 25, 2017, 'check' from dual union
11 select 3, 21, 34, 2015, 'check' from dual union
12 select 3, 21, 51, 2017, 'card' from dual
13 ),
14 inter as
15 (select person_id, acc, trans, tyear, method,
16 first_value(tyear) over (partition by person_id, acc, method order by trans) fv
17 from test
18 )
19 select person_id, acc, max(fv) - min(fv) diff
20 from inter
21 group by person_id, acc
22 having count(distinct method) > 1
23 order by person_id, acc;
PERSON_ID ACC DIFF
---------- ---------- ----------
1 10 1
2 15 2
2 19 6
3 21 2
SQL>

Related

How to query Data Group by with Order by in Oracle

I have sample data like this
CREATE TABLE table_name (aktif, "START", "END", NO_BOX, QTY) AS
SELECT 1, 'A0001', 'A0020', 2016, 100 FROM DUAL UNION ALL
SELECT 1, 'A0021', 'A0040', 2016, 100 FROM DUAL UNION ALL
SELECT 1, 'A0041', 'A0060', 2016, 100 FROM DUAL UNION ALL
SELECT 0, 'A0061', 'A0080', NULL, 100 FROM DUAL UNION ALL
SELECT 0, 'A0081', 'A0100', NULL, 100 FROM DUAL UNION ALL
SELECT 1, 'A0101', 'A0120', 2016, 100 FROM DUAL UNION ALL
SELECT 1, 'A0121', 'A0140', 2016, 100 FROM DUAL UNION ALL
SELECT 1, 'A0141', 'A0160', 2016, 100 FROM DUAL UNION ALL
SELECT 0, 'A0161', 'A0180', NULL, 100 FROM DUAL UNION ALL
SELECT 0, 'A0181', 'A0200', NULL, 100 FROM DUAL;
I want to group by the columns where AKTIF and NO_BOX remain the same based on the order of the rows and then select SUM(QTY), MIN(START), MAX(END).
The output should be:
AKTIF
START
END
NO_BOX
QTY
1
A0001
A0060
2016
300
0
A0061
A0100
NULL
200
1
A0101
A0160
2016
300
0
A0161
A0200
NULL
200
At the end, that's gaps and islands problem.
Sample data:
SQL> with test (aktif, cstart, end, no_box, qty) as
2 (select 1, 'A0001', 'A0020', 2016, 100 from dual union all
3 select 1, 'A0021', 'A0040', 2016, 100 from dual union all
4 select 1, 'A0041', 'A0060', 2016, 100 from dual union all
5 --
6 select 0, 'A0061', 'A0080', null, 100 from dual union all
7 select 0, 'A0081', 'A0100', null, 100 from dual union all
8 --
9 select 1, 'A0101', 'A0120', 2016, 100 from dual union all
10 select 1, 'A0121', 'A0140', 2016, 100 from dual union all
11 select 1, 'A0141', 'A0160', 2016, 100 from dual union all
12 --
13 select 0, 'A0161', 'A0180', null, 100 from dual union all
14 select 0, 'A0181', 'A0200', null, 100 from dual
15 ),
Query begins here:
16 temp as
17 (select t.*,
18 row_number() over (order by cstart) -
19 row_Number() over (partition by aktif order by cstart) grp
20 from test t
21 )
22 select aktif,
23 min(cstart) cstart,
24 max(end) end,
25 no_box,
26 sum(qty) qty
27 from temp
28 group by aktif, no_box, grp
29 order by cstart;
AKTIF CSTAR END NO_BOX QTY
---------- ----- ----- ---------- ----------
1 A0001 A0060 2016 300
0 A0061 A0100 200
1 A0101 A0160 2016 300
0 A0161 A0200 200
SQL>

How to select the first 5 dates from each group and put them in a single column separated by comma in Oracle?

I have a table like this:
Division
Region
Date of Last Visit
1
2
11/20/2021
1
2
11/18/2021
1
7
10/18/2021
1
7
11/19/2021
2
2
11/17/2021
2
3
09/20/2021
2
3
10/20/2021
I want to write a query that groups by the division and region columns and gives me the last 5 dates for each group separated by commas in a single column. Something like this:
Division
Region
Date of Last Visit
Today
Days since last visit
1
2
11/20/2021, 11/18/2021
sysdate
sysdate - max(date of last visit)
1
7
10/18/2021, 11/19/2021
sysdate
sysdate - max(date of last visit)
2
2
11/17/2021
sysdate
sysdate - max(date of last visit)
2
3
9/20/2021, 10/20/2021
sysdate
sysdate - max(date of last visit)
The last two columns are custom calculated columns that I also need for the final output table. Any help would be greatly appreciated as I have tried a lot of things but I keep getting errors about it not being grouped properly, possibly because of the two extra columns at the end. But even without that, I am not sure how to fetch only the last 5 dates per group in oracle.
Thanks!
You want to filter the greatest-n-per-group using the ROW_NUMBER analytic function and then aggregate:
SELECT division,
region,
LISTAGG(TO_CHAR(date_of_last_visit, 'DD/MM/YYYY'), ',')
WITHIN GROUP (ORDER BY date_of_last_visit DESC)
AS date_of_last_visit,
SYSDATE AS today,
TRUNC(SYSDATE - MAX(date_of_last_visit)) AS days_since_last_visit
FROM (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY division, region
ORDER BY date_of_last_visit DESC) AS rn
FROM table_name t
)
WHERE rn <= 5
GROUP BY
division,
region
Which, for the sample data:
CREATE TABLE table_name (division, region, date_of_last_visit) as
select 1, 2, date '2021-11-20' from dual union all
select 1, 2, date '2021-11-18' from dual union all
select 1, 7, date '2021-10-18' from dual union all
select 1, 7, date '2021-11-19' from dual union all
select 2, 2, date '2021-11-17' from dual union all
select 2, 3, date '2021-09-20' from dual union all
select 2, 3, date '2021-10-20' from dual;
Outputs:
DIVISION
REGION
DATE_OF_LAST_VISIT
TODAY
DAYS_SINCE_LAST_VISIT
1
2
20/11/2021,18/11/2021
30-NOV-21
10
1
7
19/11/2021,18/10/2021
30-NOV-21
11
2
2
17/11/2021
30-NOV-21
13
2
3
20/10/2021,20/09/2021
30-NOV-21
41
db<>fiddle here
Here you go; read comments within code.
SQL> with test (division, region, datum) as
2 -- sample data
3 (select 1, 2, date '2021-11-20' from dual union all
4 select 1, 2, date '2021-11-18' from dual union all
5 select 1, 7, date '2021-10-18' from dual union all
6 select 1, 7, date '2021-11-19' from dual union all
7 select 2, 2, date '2021-11-17' from dual union all
8 select 2, 3, date '2021-09-20' from dual union all
9 select 2, 3, date '2021-10-20' from dual
10 ),
11 temp as
12 -- rank rows per division/region, sorted by date column in descending order
13 (select t.*,
14 rank() over (partition by division, region order by datum desc) rnk
15 from test t
16 )
17 -- select up to 5 last rows per division/region
18 select division, region,
19 listagg(datum, ', ') within group (order by datum) dates,
20 trunc(sysdate) today,
21 --
22 (select trunc(sysdate) - a.datum
23 from temp a
24 where a.division = t.division
25 and a.region = t.region
26 and a.rnk = 1) days_since
27 from temp t
28 where rnk <= 5
29 group by division, region
30 order by division, region;
DIVISION REGION DATES TODAY DAYS_SINCE
---------- ---------- ------------------------------ ---------- ----------
1 2 11/18/2021, 11/20/2021 11/30/2021 10
1 7 10/18/2021, 11/19/2021 11/30/2021 11
2 2 11/17/2021 11/30/2021 13
2 3 09/20/2021, 10/20/2021 11/30/2021 41
SQL>

How to achieve string concatentation of entries in column having same id in Oracle Analytics Cloud Professional Edition?

I have a dataset in which one column is Branch-ID and other one is Branch Manager and it looks as follows in the given url.
dataset
I want to combine the branch managers into one single column based on the branch-id. For example if Bob and Sandra are two different branch-managers but have the same branch id which is branch-id=1, then we should concatenate them together as Bob-Sandra and place them in a separately created column.
I have attached the expected output for the above dataset. expected_output_dataset
I am currently using Oracle Analytics Cloud Professional Version.
I don't know Oracle Analytics, but - if it has anything to do with an Oracle database and its capabilities, then listagg helps.
Sample data in lines #1 - 10; query you might be interested in begins at line #11.
SQL> with test (account_id, branch_id, branch_manager) as
2 (select 1, 123, 'Sandra' from dual union all
3 select 3, 124, 'Martha' from dual union all
4 select 4, 125, 'John' from dual union all
5 select 6, 126, 'Andrew' from dual union all
6 select 7, 126, 'Mathew' from dual union all
7 select 2, 123, 'Michael' from dual union all
8 select 5, 125, 'David' from dual union all
9 select 8, 126, 'Mark' from dual
10 )
11 select a.account_id, a.branch_id, a.branch_manager,
12 b.concatenated_column
13 from test a join (select branch_id,
14 listagg(branch_manager, '-') within group (order by null) concatenated_column
15 from test
16 group by branch_id
17 ) b on b.branch_id = a.branch_id;
ACCOUNT_ID BRANCH_ID BRANCH_ CONCATENATED_COLUMN
---------- ---------- ------- -------------------------
1 123 Sandra Michael-Sandra
3 124 Martha Martha
4 125 John David-John
6 126 Andrew Andrew-Mark-Mathew
7 126 Mathew Andrew-Mark-Mathew
2 123 Michael Michael-Sandra
5 125 David David-John
8 126 Mark Andrew-Mark-Mathew
8 rows selected.
SQL>

ORACLE Recursive query

I'm trying to build a recursive query and I'm facing a problem.
please find below my dataset
WITH table1 ( ID, Code, Label ) as(
SELECT 123, 'C1', 'LABEL_1' from dual UNION ALL
SELECT 1, 'C2', 'LABEL_2' from dual UNION ALL
SELECT 30, 'C3', 'LABEL_3' from dual UNION ALL
SELECT 44, 'C4', 'LABEL_4' from dual UNION ALL
SELECT 5, 'C5', 'LABEL_5' from dual
),
table2 ( ID, id_table1, code_child, label_child ) as (
SELECT 1, 123, 'C1_1','LABEL_1_1' from dual UNION ALL
SELECT 2, 123, 'C1_2','LABEL_1_2' from dual UNION ALL
SELECT 3, 123, 'C1_3','LABEL_1_3' from dual UNION ALL
SELECT 4, 123, 'C1_4','LABEL_1_4' from dual UNION ALL
SELECT 6, 30, 'C3_1','LABEL_3_1' from dual UNION ALL
SELECT 7, 30, 'C3_2','LABEL_3_2' from dual UNION ALL
SELECT 8, 30, 'C3_3','LABEL_3_3' from dual UNION ALL
SELECT 9, 30, 'C3_4','LABEL_3_4' from dual UNION ALL
SELECT 10, 5, 'C5_1','LABEL_5_1' from dual
),
hierarchy as (
Select
a.id, code, label, CODE_CHILD,id_table1
from table1 a
left join table2 b on b.id_table1 = a.ID
)
,recursive (base, id, code, label, CODE_CHILD,id_table1) as (
SELECT
id as base,
id,
code,
label,
CODE_CHILD,
id_table1
FROM hierarchy
UNION ALL
SELECT
previous_level.base,
current_level.id,
current_level.code,
current_level.label,
current_level.CODE_CHILD,
current_level.id_table1
FROM recursive previous_level,
hierarchy current_level
WHERE 1=1
and current_level.id = previous_level.id_table1
)
SELECT * FROM recursive order by base;
And i'm getting this error :
32044. 00000 - "cycle detected while executing recursive WITH query"
*Cause: A recursive WITH clause query produced a cycle and was stopped
in order to avoid an infinite loop.
*Action: Rewrite the recursive WITH query to stop the recursion or use
the CYCLE clause.
Where i'm wrong ?
I need to merge these two tables into one.
here's what I'd like to get as a result.
id code label id_parent
1 C1 LABEL_1
2 C2 LABEL_2
3 C3 LABEL_3
4 C4 LABEL_4
5 C5 LABEL_5
6 C1_1 LABEL_1_1 1
7 C1_2 LABEL_1_2 1
8 C1_3 LABEL_1_3 1
9 C1_4 LABEL_1_4 1
10 C3_1 LABEL_3_1 3
11 C3_2 LABEL_3_2 3
12 C3_3 LABEL_3_3 3
13 C3_4 LABEL_3_4 3
14 C5_1 LABEL_5_1 5
Thank you
Not sure why you want a recursive query? It appears that you could just use UNION ALL and join the two tables:
WITH table1 ( ID, Code, Label ) as(
SELECT 1, 'C1', 'LABEL_1' from dual UNION ALL
SELECT 2, 'C2', 'LABEL_2' from dual UNION ALL
SELECT 3, 'C3', 'LABEL_3' from dual UNION ALL
SELECT 4, 'C4', 'LABEL_4' from dual UNION ALL
SELECT 5, 'C5', 'LABEL_5' from dual
),
table2 ( ID, id_table1, code_child, label_child ) as (
SELECT 1, 1, 'C1_1','LABEL_1_1' from dual UNION ALL
SELECT 2, 1, 'C1_2','LABEL_1_2' from dual UNION ALL
SELECT 3, 1, 'C1_3','LABEL_1_3' from dual UNION ALL
SELECT 4, 1, 'C1_4','LABEL_1_4' from dual UNION ALL
SELECT 6, 3, 'C3_1','LABEL_3_1' from dual UNION ALL
SELECT 7, 3, 'C3_2','LABEL_3_2' from dual UNION ALL
SELECT 8, 3, 'C3_3','LABEL_3_3' from dual UNION ALL
SELECT 9, 3, 'C3_4','LABEL_3_4' from dual UNION ALL
SELECT 10, 5, 'C5_1','LABEL_5_1' from dual
)
SELECT ROW_NUMBER() OVER ( ORDER BY table_no, code ) AS id,
code,
label,
id_parent
FROM (
SELECT code,
label,
1 AS table_no,
NULL AS id_parent
FROM table1
UNION ALL
SELECT code_child,
label_child,
2 AS table_no,
id_table1
FROM table2
)
order by table_no, code;
Which outputs:
ID | CODE | LABEL | ID_PARENT
-: | :--- | :-------- | --------:
1 | C1 | LABEL_1 | null
2 | C2 | LABEL_2 | null
3 | C3 | LABEL_3 | null
4 | C4 | LABEL_4 | null
5 | C5 | LABEL_5 | null
6 | C1_1 | LABEL_1_1 | 1
7 | C1_2 | LABEL_1_2 | 1
8 | C1_3 | LABEL_1_3 | 1
9 | C1_4 | LABEL_1_4 | 1
10 | C3_1 | LABEL_3_1 | 3
11 | C3_2 | LABEL_3_2 | 3
12 | C3_3 | LABEL_3_3 | 3
13 | C3_4 | LABEL_3_4 | 3
14 | C5_1 | LABEL_5_1 | 5
db<>fiddle here
A recursive WITH clause query produced a cycle and was stopped in order to avoid an infinite loop.
This issue is coming due to bad data in the DB. There are some records which are causing circular relationship among them which is causing infinite loops.
For example: P is parent of C and C is again parent of P.
You can fetch the above output simple using UNION ALL and join of the tables.

Oracle Stored Procedure - Number of heat waves (number of pattern matches in a series)

I need to postprocess a Oracle dataset in order to find the number of heat waves.
By definition, a heat waves occurs when the data value is greater than a threshold at least two consecutive times.
For example, given the threshold=20 and the sequence
23 31 32 17 16 23 16 21 22 18
the heat waves are 2:
{23,31,32} and {21,22}
and the lenght of the longest one is 3 (size of bigger subset)
My input dataset consists of several sequences; a sample input result set is:
-----------------------------
| ID | DAY | VALUE |
-----------------------------
| 100 | 1/1/17 | 20 |
| 100 | 2/1/17 | 21 |
| 200 | 1/1/17 | 12 |
| 200 | 2/1/17 | 24 |
| ... ... ...
In other words, I have a sequence per each ID and I need to output something like that:
-----------------------
| ID | #heat waves |
-----------------------
| 100 | 3 |
| 200 | 1 |
Here the current version of my stored procedure:
create or replace PROCEDURE sp (
p_query IN VARCHAR2,
cursor_ out sys_refcursor
) AS
processed processed_data_table := processed_data_table();
c sys_refcursor;
BEGIN
OPEN c FOR p_query;
processed.EXTEND;
processed(processed.count) := processed_data_obj();
fetch c INTO processed(processed.count).ID,
processed(processed.count).DAY, processed(processed.count).VALUE;
while c%found
processed.EXTEND;
processed(processed.count) := processed_data_obj();
fetch c INTO processed(processed.count).ID,
processed(processed.count).DAY, processed(processed.count).VALUE;
END loop;
CLOSE c;
processed.TRIM;
// HERE I NEED TO PROCESS processed TABLE AND STORE RESULT IN output
TABLE
OPEN cursor_ FOR
SELECT *
FROM TABLE( output);
END sp;
Anyone could help me providing a solution?
Thanks
In Oracle 12c, use MATCH_RECOGNIZE:
select id, count(*) "# of heatwaves" from series_data
match_recognize ( partition by id
order by day
one row per match
after match skip past last row
pattern ( over_threshold{2,} )
define
over_threshold as value > 20 )
group by id
UPDATE: Also show longest heat wave for each series
To get the longest heatwave in each series, we have to introduce a MEASURES clause to the MATCH_RECOGNIZE, as below:
select id,
max(heatwave_length) "longest heatwave",
count(distinct heatwave_number) "# of heatwaves"
from series_data
match_recognize ( partition by id
order by day
measures
FINAL COUNT(*) as heatwave_length,
MATCH_NUMBER() heatwave_number
all rows per match
after match skip past last row
pattern ( over_threshold{2,} )
define
over_threshold as value > 20 )
group by id
order by id;
Full example with data:
with series_data ( id, day, value ) as
( SELECT 100, date '2017-01-01', 23 from dual union all
SELECT 100, date '2017-01-02', 31 from dual union all
SELECT 100, date '2017-01-03', 32 from dual union all
SELECT 100, date '2017-01-04', 44 from dual union all
SELECT 100, date '2017-01-05', 16 from dual union all
SELECT 100, date '2017-01-06', 23 from dual union all
SELECT 100, date '2017-01-07', 16 from dual union all
SELECT 100, date '2017-01-08', 21 from dual union all
SELECT 100, date '2017-01-09', 22 from dual union all
SELECT 100, date '2017-01-10', 18 from dual union all
SELECT 200, date '2017-01-01', 23 from dual union all
SELECT 200, date '2017-01-02', 31 from dual union all
SELECT 200, date '2017-01-03', 32 from dual union all
SELECT 200, date '2017-01-04', 17 from dual union all
SELECT 200, date '2017-01-05', 16 from dual union all
SELECT 200, date '2017-01-06', 23 from dual union all
SELECT 200, date '2017-01-07', 16 from dual union all
SELECT 200, date '2017-01-08', 21 from dual union all
SELECT 200, date '2017-01-09', 22 from dual union all
SELECT 200, date '2017-01-10', 22 from dual union all
SELECT 200, date '2017-01-11', 6 from dual union all
SELECT 200, date '2017-01-12', 22 from dual union all
SELECT 200, date '2017-01-13', 22 from dual )
select id,
max(heatwave_length) "longest heatwave",
count(distinct heatwave_number) "# of heatwaves"
from series_data
match_recognize ( partition by id
order by day
measures
FINAL COUNT(*) as heatwave_length,
MATCH_NUMBER() heatwave_number
all rows per match
after match skip past last row
pattern ( over_threshold{2,} )
define
over_threshold as value > 20 )
group by id
order by id;
Results:
ID longest heatwave # of heatwaves
----- -------------- --------------
100 4 2
200 3 3

Resources