Oracle - Connect By Prior - oracle

I know I have to use CONNECT BY PRIOR in this query, but I'm not sure how to implement it.
We have customers who purchase monthly subscriptions, and those get auto-renewed each month. We have a log table which can show what your current order ID is, and what your previous order ID is. So, table records could like like this:
CUSTOMER ID: 1 ORDER ID: 123 PREV_ORDER_ID: STATUS: Complete
CUSTOMER ID: 1 ORDER ID: 456 PREV_ORDER_ID: 123 STATUS: Complete
CUSTOMER ID: 1 ORDER ID: 789 PREV_ORDER_ID: 456 STATUS: Complete
CUSTOMER ID: 1 ORDER ID: 888 PREV_ORDER_ID: 789 STATUS: Complete
CUSTOMER ID: 1 ORDER ID: 999 PREV_ORDER_ID: 888 STATUS: Active
I am looking to count how many customers have had at least 13 months of consecutive subscriptions, with no gaps with the most recent subscription will have an "Active" status. If there is a break in subscriptions, the PREV_ORDER_ID will be NULL.
Hoping to do this in a query, and not having to write an anonymous block for it.
Many thanks!

You could do something like this (using your actual table and column names instead of the with clause from the query, which you should delete). Note that the hierarchical recursion starts from the end (from 'Active' status) and proceeds backwards; in my query I stop it at level 4 since I didn't feel like writing enough rows to get to level 13. Of course, you will have to replace 4 with 13 in the where clause.
with
test_data (customer_id, order_id, prev_order_id, status) as (
select 1, 123, null, 'Complete' from dual union all
select 1, 456, 123, 'Complete' from dual union all
select 1, 789, 456, 'Complete' from dual union all
select 1, 888, 789, 'Complete' from dual union all
select 1, 999, 888, 'Active' from dual union all
select 2, 100, null, 'Complete' from dual union all
select 2, 200, 100, 'Active' from dual union all
select 5, 105, null, 'Complete' from dual union all
select 5, 106, 105, 'Complete' from dual union all
select 5, 205, null, 'Complete' from dual union all
select 5, 206, 205, 'Active' from dual
)
select customer_id
from test_data
where level = 4
start with status = 'Active'
connect by customer_id = prior customer_id and order_id = prior prev_order_id
;
CUSTOMER_ID
-----------
1

More data and tests are needed.
Maybe it will help you
CREATE TABLE CUSTOMER_LOG
(
CUSTOMER_ID number(5),
ORDER_ID number(5),
PREV_ORDER_ID number(5),
STATUS VARCHAR(50)
);
INSERT INTO CUSTOMER_LOG(CUSTOMER_ID,ORDER_ID,PREV_ORDER_ID,STATUS) values (1,123,NULL, 'Complete');
INSERT INTO CUSTOMER_LOG(CUSTOMER_ID,ORDER_ID,PREV_ORDER_ID,STATUS) values (1,456,123, 'Complete');
INSERT INTO CUSTOMER_LOG(CUSTOMER_ID,ORDER_ID,PREV_ORDER_ID,STATUS) values (1,789,456, 'Complete');
INSERT INTO CUSTOMER_LOG(CUSTOMER_ID,ORDER_ID,PREV_ORDER_ID,STATUS) values (1,888,789, 'Complete');
INSERT INTO CUSTOMER_LOG(CUSTOMER_ID,ORDER_ID,PREV_ORDER_ID,STATUS) values (1,999,888, 'Active');
Select
l.*,
(
select count(*)
from CUSTOMER_LOG s
where s.customer_id=1
start with s.ORDER_ID=l.ORDER_ID
connect by s.ORDER_ID= prior s.PREV_ORDER_ID
) QTDE
from CUSTOMER_LOG l
where l.status='Active'

Related

Use listagg to group all students for teacher, course, semster

I have a relationship where a teacher -> courses -> enrollmenr-> students. I am trying to use listagg to get all the students first/last names on the same line as the teacher_id and course_id. In addition, I want to add the count of each teacher_id, course_id, semester.
Below is my test CASE, which has the tables, data and a part of the query. I would appreciate any help completing the query. Thanks in advance to all who answer.
CREATE TABLE teachers(teacher_id, first_name, last_name) AS
SELECT 101, 'Keith', 'Stein' FROM dual UNION ALL
SELECT 102, 'Roger', 'Wood' FROM dual UNION ALL
SELECT 103, 'Douglas', 'Kern' FROM dual UNION ALL
SELECT 104, 'Paul', 'Weber' FROM dual UNION ALL
SELECT 105, 'Jeffrey', 'Lebowitz' FROM dual UNION ALL
SELECT 106, 'Carol', 'Seltzer' FROM dual;
CREATE TABLE students(student_id, first_name, last_name) AS
SELECT 1, 'Faith', 'Aaron' FROM dual UNION ALL
SELECT 2, 'Lisa', 'Saladino' FROM dual UNION ALL
SELECT 3, 'Leslee', 'Altman' FROM dual UNION ALL
SELECT 4, 'Patty', 'Kern' FROM dual UNION ALL
SELECT 5, 'Beth', 'Cooper' FROM dual UNION ALL
SELECT 99, 'Jill', 'Coralnick' FROM dual;
CREATE TABLE courses(course_id, course_name, teacher_id, semester) AS
SELECT 1, 'Geometry', 101, '2022-2' FROM DUAL UNION ALL
SELECT 2, 'Trigonometry', 102, '2022-2' FROM DUAL UNION ALL
SELECT 3, 'Calculus', 103, '2022-2' FROM DUAL UNION ALL
SELECT 4, 'Chemistry', 104, '2022-2' FROM DUAL UNION ALL
SELECT 5, 'Biology', 105, '2022-2' FROM DUAL UNION ALL
SELECT 6, 'Physcology', 106, '2022-2' FROM DUAL;
CREATE TABLE enrollment(student_id,course_id) AS
SELECT 1, 1 FROM dual UNION ALL
SELECT 2, 1 FROM dual UNION ALL
SELECT 3, 1 FROM dual UNION ALL
SELECT 4, 1 FROM dual UNION ALL
SELECT 5, 1 FROM dual UNION ALL
SELECT 1, 2 FROM dual UNION ALL
SELECT 2, 2 FROM dual UNION ALL
SELECT 3, 2 FROM dual UNION ALL
SELECT 4, 2 FROM dual UNION ALL
SELECT 5, 2 FROM dual UNION ALL
SELECT 1, 3 FROM dual UNION ALL
SELECT 2, 3 FROM dual UNION ALL
SELECT 3, 3 FROM dual UNION ALL
SELECT 4, 3 FROM dual UNION ALL
SELECT 5, 3 FROM dual UNION ALL
SELECT 99, 3 FROM dual;
/* list all teachers, courses, student count, all students for teacher_id, course_id, semester
*/
SELECT
t.teacher_id
, t.first_name
, t.last_name
, c.course_id
, c.course_name
, c.semester
FROM teachers t
LEFT JOIN courses c
ON t.teacher_id = c.teacher_id
ORDER BY teacher_id;
TEACHER_ID FIRST_NAME LAST_NAME COURSE_ID COURSE_NAME SEMESTER
101 Keith Stein 1 Geometry 2022-2
102 Roger Wood 2 Trigonometry 2022-2
103 Douglas Kern 3 Calculus 2022-2
104 Paul Weber 4 Chemistry 2022-2
105 Jeffrey Lebowitz 5 Biology 2022-2
106 Carol Seltzer 6 Physcology 2022-2
You can use a correlated sub-query:
SELECT t.teacher_id
, t.first_name
, t.last_name
, c.course_id
, c.course_name
, c.semester
, (
SELECT LISTAGG(s.last_name || ', ' || s.first_name, '; ')
WITHIN GROUP (ORDER BY s.last_name, s.first_name)
FROM enrollment e
INNER JOIN students s
ON (e.student_id = s.student_id)
WHERE e.course_id = c.course_id
) AS students
FROM teachers t
LEFT JOIN courses c
ON t.teacher_id = c.teacher_id
ORDER BY
teacher_id;
Which, for the sample data, outputs:
TEACHER_ID
FIRST_NAME
LAST_NAME
COURSE_ID
COURSE_NAME
SEMESTER
STUDENTS
101
Keith
Stein
1
Geometry
2022-2
Aaron, Faith; Altman, Leslee; Cooper, Beth; Kern, Patty; Saladino, Lisa
102
Roger
Wood
2
Trigonometry
2022-2
Aaron, Faith; Altman, Leslee; Cooper, Beth; Kern, Patty; Saladino, Lisa
103
Douglas
Kern
3
Calculus
2022-2
Aaron, Faith; Altman, Leslee; Cooper, Beth; Coralnick, Jill; Kern, Patty; Saladino, Lisa
104
Paul
Weber
4
Chemistry
2022-2
null
105
Jeffrey
Lebowitz
5
Biology
2022-2
null
106
Carol
Seltzer
6
Physcology
2022-2
null
Or you can use JOINs and aggregate:
SELECT t.teacher_id
, MAX(t.first_name) AS first_name
, MAX(t.last_name) AS last_name
, c.course_id
, MAX(c.course_name) AS course_name
, MAX(c.semester) AS semester
, LISTAGG(
NVL2(s.student_id, s.last_name || ', ' || s.first_name, NULL),
'; '
) WITHIN GROUP (ORDER BY s.last_name, s.first_name) AS students
, COUNT(s.student_id) AS num_students
FROM teachers t
LEFT OUTER JOIN courses c
ON t.teacher_id = c.teacher_id
LEFT OUTER JOIN (
enrollment e
INNER JOIN students s
ON (e.student_id = s.student_id)
)
ON (e.course_id = c.course_id)
GROUP BY
t.teacher_id,
c.course_id
ORDER BY
t.teacher_id,
c.course_id;
db<>fiddle here

combine multiple rows result in single row based on one column value

I i want to combine multiple rows result into single row based on one column called type.
Ex say suppose i have below result from my query .
seqnum type name
456 SH Google2
456 CN transwork
123 SH partyshipper
123 CN consignee
Actual result i want is something like below table
seqnum consigneename shippername
456 transwork Google2
123 consignee partyshipper
Basically i want to get result like consignee name when type is CN and shipper name is when type is SH if its not both then i can add extra column with name just like otherparty.
I can get result and iterate result set and set value of object. but i think this will be better if we get formatted result in query only.can some one help in in getting this.
Thanks for the help.
Something like this usually helps; lines #1 - 7 represent your sample data. Code you need begins at line #8.
SQL> with test (seqnum, type, name) as
2 (select 456, 'SH', 'Google2' from dual union all
3 select 456, 'CN', 'transwork' from dual union all
4 select 123, 'SH', 'partyshipper' from dual union all
5 select 123, 'CN', 'consignee' from dual union all
6 select 999, 'XX', 'littlefoot' from dual
7 )
8 select seqnum,
9 max(case when type = 'CN' then name end) consigneename,
10 max(case when type = 'SH' then name end) shipppername,
11 max(case when type not in ('CN', 'SH') then name end) otherparty
12 from test
13 group by seqnum;
SEQNUM CONSIGNEENAM SHIPPPERNAME OTHERPARTY
---------- ------------ ------------ ------------
123 consignee partyshipper
999 littlefoot
456 transwork Google2
SQL>
Borrowing the query from #Littlefoot. You may also use PIVOT for this getting the expected result.
with test (seqnum, type, name) as
(select 456, 'SH', 'Google2' from dual union all
select 456, 'CN', 'transwork' from dual union all
select 123, 'SH', 'partyshipper' from dual union all
select 123, 'CN', 'consignee' from dual union all
select 999, 'OT', 'littlefoot' from dual
)
select * from test
pivot (
min(name) for type in
(
'SH' shippingname
, 'CN' consigneename
, 'OT' someother
)
)
;
SEQNUM SHIPPINGNAME CONSIGNEENAM SOMEOTHER
---------- ------------ ------------ ------------
999 littlefoot
456 Google2 transwork
123 partyshipper consignee
I'd self join the table and filter different types in each side of the join:
SELECT COALESCE(c.seqnum, s.seqnum) AS seqnum,
COALESCE(c.name, 'other') AS consigneename,
COALESCE(s.name, 'other') AS shippername
FROM (SELECT *
FROM mytable
WHERE type = 'CN') c
FULL OUTER JOIN (SELECT *
FROM mytable
WHERE type = 'SN') s ON c.seqnum = s.seqnum

OBIEE Sequence Data by Dimensions

First question here, so please be gentle. I've been a lurker for a long time and figured now's a great time to get involved.
Using Oracle OBIEE 12c, I'm looking to create a running counter in my result dataset for instances of a group of dimensions, which reset if the group starts again. Like this - in the below example the counter would consider Cust ID and Status:
Cust ID, Order ID, Status, Counter
111, 123456, APPROVED, 1
111, 123457, APPROVED, 2
111, 123458, APPROVED, 3
111, 123459, DECLINED, 1
111, 123460, APPROVED, 1
111, 123461, APPROVED, 2
222, 123462, APPROVED, 1
222, 123463, APPROVED, 2
Any ideas? I've tried a combination of case statements and RSUM(), but I can't quite get to what I'm after and I'm looking for a fresh approach.
Thanks
I'm not sure how you want your data ordered, so that will matter. It looks like you are ordering by ORDER ID.
You can use a count with Partitioning. Though this may not work depending on how your data is ordered.
SELECT CUSTID, ORDERID, STATUS, count(*)
over (PARTITION BY CUSTID||STATUS ORDER BY ORDERID ROWS UNBOUNDED PRECEDING) COUNTER
FROM MYTEST;
You can also use LAG to check for row changes, for example:
SELECT CUSTID, ORDERID, STATUS, curr_row, prev_row
,CASE WHEN curr_row != prev_row THEN 'Change' ELSE 'Same Group' END as TRACKER
FROM (
SELECT CUSTID, ORDERID, STATUS, CUSTID||STATUS AS curr_row
,LAG(CUSTID||STATUS) OVER (ORDER BY CUSTID, ORDERID, STATUS) AS prev_row
FROM MYTEST
ORDER BY ORDERID
)
;
The above examples are using this set of test data:
Test Data
You should probably calculate the counter outside of OBIEE as part of the extract/load/transform process.
Here is an approximation using the ROW_NUMBER analytical function in Oracle.
with dataset as (
SELECT 111 AS cust_id,
123456 AS order_id,
'APPROVED' AS status,
1 AS expected_counter
FROM dual
UNION ALL
SELECT 111,123457,'APPROVED',2 FROM dual
UNION ALL
SELECT 111,123458,'APPROVED',3 FROM dual
UNION ALL
SELECT 111,123459,'DECLINED',1 FROM dual
UNION ALL
SELECT 111,123460,'APPROVED',1 FROM dual
UNION ALL
SELECT 111,123461,'APPROVED',2 FROM dual
UNION ALL
SELECT 222,1234562,'APPROVED',1 FROM dual
UNION ALL
SELECT 222,1234563,'APPROVED',2 FROM dual
)
select cust_id,order_id,status,expected_counter,row_number() over (partition by cust_id,status order by order_id asc) as counter from dataset ;
The result is:
cust_id /order_id /status / your_counter / my_counter
111 123456 APPROVED 1 1
111 123457 APPROVED 2 2
111 123458 APPROVED 3 3
111 123460 APPROVED 1 4
111 123461 APPROVED 2 5
111 123459 DECLINED 1 1
222 1234562 APPROVED 1 1
222 1234563 APPROVED 2 2
This example helps you find out that customer 111 has 5 approved orders and 1 that was declined; customer 222 has 2 approved orders. I could not match your exact counter. Hope this helps
In OBIEE analysis column formula syntax that'd be sth like
RCOUNT("Orders"."Status" by "Customers"."Cust ID")

Oracle- Multiple rows in single row with adding column of duplicate value

Id Field_id Field_value
------------------------------
1 10 'A'
1 11 'B'
1 12 'C'
I want to make rows like
Id Field_id Field_value data_1 data_2
--------------------------------------
1 10 'A' 'B' 'C'
Pl help.
Take a look at this:
with t (Id, Field_id, Field_value) as (
select 1, 10, 'A' from dual union all
select 1, 11, 'B' from dual union all
select 1, 12, 'C' from dual
)
select FIELD_ID1, "10_FIELD_ID", "11_FIELD_ID","12_FIELD_ID"
from (select id, field_id, min(field_id) over() field_id1, field_value from t)
pivot (
max(field_value) field_id
for field_id in (10, 11, 12)
)
FIELD_ID1 10_FIELD_ID 11_FIELD_ID 12_FIELD_ID
---------------------------------------------------
10 A B C
Read more about pivot here
Normally, people look for PIVOT query, however, your input data is not at all duplicate as already mentioned in comments.
What you could do is, using subquery factoring, select id, 10 as field_id, field_value, thus making field_it static value as 10. Then use LISTAGG. But, the grouped rows would be a single column as delimeter seperated values.
I hope your requirement is exactly what you stated and not a typo or a mistake while posting.

sum of multiple columns in ORACLE

I have a table that has 97 columns, I want to sum 96 columns.
select sum(col1+col2+col3+.....+col96)
from tableA where meter_id=x;
I do not want to give all 96 column names, what is the best way to do it?
Regards,
RR
There is no way to avoid writing each column name. All you can do is curse the stupid data modeller and get busy with cut'n'paste.
In the case where there are a significant number of columns, I would look at using the data dictionary tables to help create the query by using a query like the one below:
Select column_name || '+' as column_name_list
From user_tab_columns
Where table_name = 'TABLEA'
Order by column_id
It doesn't change the world but does simplify writing one query.
You could create a virtual column that adds up your 96 columns, something like:
alter table TABLEA add (MY_TOTAL_COL NUMBER GENERATED ALWAYS AS (col1+col2+col3...) VIRTUAL);
Then your query can simply do sum(my_total_col).
You might be best to sum the columns and then put the result in Excel to do the sum of sums. Otherwise this query should do what you need:
SELECT SUM(TOTAL_SUM) FROM (
SELECT SUM(column1) AS TOTAL_SUM FROM your_table
UNION
SELECT SUM(column2) AS TOTAL_SUM FROM your_table
UNION
SELECT SUM(column3) AS TOTAL_SUM FROM your_table
);
SELECT A.consol_key,
A.amt_lcy,
B.amt_lcy,
C.amt_lcy
FROM categ A,
spec B,
stmt C;
SELECT Sum(total_sum)
FROM (SELECT Sum(amt_lcy) AS TOTAL_SUM
FROM categ
UNION
SELECT Sum(amt_lcy) AS TOTAL_SUM
FROM spec
UNION
SELECT Sum(amt_lcy) AS TOTAL_SUM
FROM stmt)
WHERE table_id NOT IN (SELECT table_id
FROM categ
WHERE txn_code = 'COR'
AND system_id <> 'AA');
It could be possible:
Using Can an SQL procedure return a table?
and the answer of Mike Meyers you could write a stored procedure using dynamic sql
sumcolumns(columnfilter,tablename,whereclause)
and use it something like
select *
from table(sumcolumns('column_name <> ''col97''','tableA','meter_id=x'))
Try using UNPIVOT as per example below (still need to specify the column list as others have noted):
with tableA as /* prototype tableA just for example */
(
select 1 meter_id, 101 col1, 10 col2, 20 col3, 30 col4, NULL col5, 101 col11, 10 col12, 20 col13, 30 col14, NULL col15, 101 col21, 10 col22, 20 col23, 30 col24, NULL col25 from dual union
select 2, 102, 40, NULL, 50, NULL, 102, 40, NULL, 50, NULL, 102, 40, NULL, 50, NULL from dual union
select 3, 103, 60, 70, 80, 90, 103, 60, 70, 80, 90, 103, 60, 70, 80, 90 from dual union
select 4, 104, 100, NULL, NULL, NULL, 104, 100, NULL, NULL, NULL, 104, 100, NULL, NULL, NULL from dual
)
, unpivoted_tableA as /* UNPIVOT tableA columns into rows */
(
select *
from tableA
unpivot include nulls
(
col_value for col_ in
(COL1,COL2,COL3,COL4,COL5,COL11,COL12,COL13,COL14,COL15,COL21,COL22,COL23,COL24,COL25)
)
)
/* main query - Sum of all columns that were unpivoted to rows */
select meter_id, sum(col_value)
from unpivoted_tableA
group by meter_id
;

Resources