Oracle: Combine two group by queries (which use aggregate function count()) by union or so to get a consolidated result - oracle

I have two tables. TABLE_A and TABLE_B.
Both tables maintain columns to save CREATION_USER. But this column has different name in respective tables.
My motive is to get a count of records each user has created in both tables.
That is, combining result of these two queries with few conditions. The user name should not get repeated and for user names who have created records in both tables, the count should be their sum.
SELECT A.CREATION_USER_A AS "USER",
COUNT(*)
FROM TABLE_A A
GROUP BY A.CREATION_USER_A;
SELECT B.CREATION_USER_B AS "USER",
COUNT(*)
FROM TABLE_B B
GROUP BY B.CREATION_USER_B;
For e.g.,
USER_A has created 2 records in TABLE_A,
USER_B has created 3 records in TABLE_B and
USER_C has created 4 records in TABLE_A and 3 records in TABLE_B.
So the output should look like this:
| USER | COUNT |
| USER_A | 2 |
| USER_B | 3 |
| USER_C | 7 |
I have written a query which does this but it performs really bad.
SELECT A.CREATION_USER_A AS "USER",
(COUNT(A.CREATION_USER_A)+(SELECT COUNT(CREATION_USER_B) FROM TABLE_B WHERE CREATION_USER_B = A.CREATION_USER_A)) AS "COUNT"
FROM TABLE_A A
GROUP BY A.CREATION_USER_A
UNION
SELECT B.CREATION_USER_B,
COUNT(B.CREATION_USER_B)
FROM TABLE_B B
WHERE B.CREATION_USER_B NOT IN (SELECT CREATION_USER_A FROM TABLE_A)
GROUP BY B.CREATION_USER_B;
Please suggest a way to get this done.

You can simply build a set given by the union (keeping duplicates) of all the records in your tables, and then count the records grouping by creation user:
Bulding some sample data:
create table table_a(id, creation_user_a) as (
select 1, 'USER_A' from dual union all
select 1, 'USER_A' from dual union all
select 1, 'USER_C' from dual union all
select 1, 'USER_C' from dual union all
select 1, 'USER_C' from dual union all
select 1, 'USER_C' from dual
);
create table table_b(id, creation_user_b) as (
select 1, 'USER_B' from dual union all
select 1, 'USER_B' from dual union all
select 1, 'USER_B' from dual union all
select 1, 'USER_C' from dual union all
select 1, 'USER_C' from dual union all
select 1, 'USER_C' from dual
)
The query:
select count(1), creation_user
from ( /* the union of all the records from table_a and table_b */
select creation_user_a as creation_user from table_a
union all /* UNION ALL keeps duplicates */
select creation_user_B from table_b
)
group by creation_user
order by creation_user
The result:
2 USER_A
3 USER_B
7 USER_C
The explain plan:
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 12 | 96 | 8 (25)| 00:00:01 |
| 1 | SORT ORDER BY | | 12 | 96 | 8 (25)| 00:00:01 |
| 2 | HASH GROUP BY | | 12 | 96 | 8 (25)| 00:00:01 |
| 3 | VIEW | | 12 | 96 | 6 (0)| 00:00:01 |
| 4 | UNION-ALL | | | | | |
| 5 | TABLE ACCESS FULL| TABLE_A | 6 | 48 | 3 (0)| 00:00:01 |
| 6 | TABLE ACCESS FULL| TABLE_B | 6 | 48 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------

An alternative (but more complicated, and possibly slower - you'd need to test both to check) solution to Aleksej's answer is to use a full outer join to join both grouped by queries, like so:
WITH table_a AS (SELECT 'USER_A' creation_user_a, 10 val FROM dual UNION ALL
SELECT 'USER_A' creation_user_a, 20 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_a, 30 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_a, 40 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_a, 50 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_a, 60 val FROM dual),
table_b AS (SELECT 'USER_B' creation_user_b, 10 val FROM dual UNION ALL
SELECT 'USER_B' creation_user_b, 20 val FROM dual UNION ALL
SELECT 'USER_B' creation_user_b, 30 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_b, 40 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_b, 50 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_b, 60 val FROM dual)
-- end of mimicking your tables with data in them. See the SQL below:
SELECT COALESCE(a.creation_user_a, b.creation_user_b) "USER",
nvl(a.cnt_a, 0) + nvl(b.cnt_b, 0) total_records
FROM (SELECT creation_user_a,
COUNT(*) cnt_a
FROM table_a
GROUP BY creation_user_a) a
FULL OUTER JOIN (SELECT creation_user_b,
COUNT(*) cnt_b
FROM table_b
GROUP BY creation_user_b) b ON a.creation_user_a = b.creation_user_b
ORDER BY "USER";
USER TOTAL_RECORDS
------ -------------
USER_A 2
USER_B 3
USER_C 7

Thank you for helping me guys. I have found a simpler and more efficient solution. It works.
SELECT CREATION_USER, SUM(TOTAL_COUNT) TOTAL_COUNT FROM
(SELECT /*+ PARALLEL */ A.CREATION_USER_A CREATION_USER,
COUNT(A.CREATION_USER_A) TOTAL_COUNT
FROM TABLE_A A
GROUP BY A.CREATION_USER_A
UNION
SELECT /*+ PARALLEL */ B.CREATION_USER_B CREATION_USER,
COUNT(B.CREATION_USER_B) TOTAL_COUNT
FROM TABLE_B B
GROUP BY B.CREATION_USER_B)
GROUP BY CREATION_USER;

Related

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.

calculate the average time difference between each stage

How to calculate the average time difference between each stage.
The challenge with the actual data set is not every id will go through all stages.. some will skip stages and the date is not continuous for all Id's like below.
id date status
1 1/1/18 requirement
1 1/8/18 analysis
1 ? design
1 1/30/18 closed
2 2/1/18 requirement
2 2/18/18 closed
3 1/2/18 requirement
3 1/29/18 analysis
3 ? accepted
3 2/5/18 closed
?--we have missing dates as well
Expected output
id date status time_spent
1 1/1/18 requirement 0
1 1/8/18 analysis 7
1 ? design
1 1/30/18 closed 22
2 2/1/18 requirement 0
2 2/18/18 closed 17
3 1/2/18 requirement 0
3 1/29/18 analysis 27
3 ? accepted
3 2/5/18 closed 24
status avg(timespent)
requirement 0
analysis 17
design
closed 21
You can use windowing functions LAG (or LEAD) to get the data of the previous (or next) status for each id. That will let you compute the time elapsed in each stage. Then, compute the average time elapsed for each stage.
Here is an example of how to do that:
with input_data (id, dte, status) as (
SELECT 1, TO_DATE('1/1/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 1, TO_DATE('1/8/18','MM/DD/YY'), 'analysis' FROM DUAL UNION ALL
SELECT 1, NULL, 'design' FROM DUAL UNION ALL
SELECT 1, TO_DATE('1/30/18','MM/DD/YY'), 'closed' FROM DUAL UNION ALL
SELECT 2, TO_DATE('2/1/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 2, TO_DATE('2/18/18','MM/DD/YY'), 'closed' FROM DUAL UNION ALL
SELECT 3, TO_DATE('1/2/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 3, TO_DATE('1/29/18','MM/DD/YY'), 'analysis' FROM DUAL UNION ALL
SELECT 3, NULL, 'accepted' FROM DUAL UNION ALL
SELECT 3, TO_DATE('2/5/18','MM/DD/YY'), 'closed' FROM DUAL ),
----- Solution begins here
data_with_elapsed_days as (
SELECT id.*, dte-nvl(lag(dte ignore nulls) over ( partition by id order by dte ), dte) elapsed
from input_data id)
SELECT status, avg(elapsed)
FROM data_with_elapsed_days d
group by status
order by decode(status,'requirement',1,'analysis',2,'design',3,'accepted',4,'closed',5,99);
+-------------+-------------------------------------------+
| STATUS | AVG(ELAPSED) |
+-------------+-------------------------------------------+
| requirement | 0 |
| analysis | 17 |
| design | |
| accepted | |
| closed | 15.33333333333333333333333333333333333333 |
+-------------+-------------------------------------------+
As I said in my comment, that logic computes the elapsed days as the time to the given status from the prior status. Since, "requirement" has no prior status, this logic will always show zero days spent in requirements. It would probably be better to compute the time from the given status to the next status. For "closed", there would be no next status. You could just leave that blank or use SYSDATE as the data of the next status. Here is an example of that:
with input_data (id, dte, status) as (
SELECT 1, TO_DATE('1/1/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 1, TO_DATE('1/8/18','MM/DD/YY'), 'analysis' FROM DUAL UNION ALL
SELECT 1, NULL, 'design' FROM DUAL UNION ALL
SELECT 1, TO_DATE('1/30/18','MM/DD/YY'), 'closed' FROM DUAL UNION ALL
SELECT 2, TO_DATE('2/1/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 2, TO_DATE('2/18/18','MM/DD/YY'), 'closed' FROM DUAL UNION ALL
SELECT 3, TO_DATE('1/2/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 3, TO_DATE('1/29/18','MM/DD/YY'), 'analysis' FROM DUAL UNION ALL
SELECT 3, NULL, 'accepted' FROM DUAL UNION ALL
SELECT 3, TO_DATE('2/5/18','MM/DD/YY'), 'closed' FROM DUAL ),
----- Solution begins here
data_with_elapsed_days as (
SELECT id.*, nvl(lead(dte ignore nulls) over ( partition by id order by dte ), trunc(sysdate))-dte elapsed
from input_data id)
SELECT status, avg(elapsed)
FROM data_with_elapsed_days d
group by status
order by decode(status,'requirement',1,'analysis',2,'design',3,'accepted',4,'closed',5,99);
+-------------+------------------------------------------+
| STATUS | AVG(ELAPSED) |
+-------------+------------------------------------------+
| requirement | 17 |
| analysis | 14.5 |
| design | |
| accepted | |
| closed | 361.666666666666666666666666666666666667 |
+-------------+------------------------------------------+
I agree with #MatthewMcPeak. Your requirements seem a bit odd: you spend zero days of requirement stage but spend an average of 21 days on closed? Fnord.
This solution treats the presented date as the start date of the stage and calculates the difference between it and the start_date of the next phase.
with cte as (
select status
, lead(dd ignore nulls) over (partition by id order by dd) - dd as dt_diff
from your_table)
select status, avg(dt_diff) as avg_ela
from cte
group by status
/
If you wish to include all stages for each d and estimate the time spent in each (using linear interpolation) then you can create a sub-query with all the statuses and use a PARTITION OUTER JOIN to join them and then use LAG and LEAD to find the date range the status is in and interpolate between:
Oracle Setup:
CREATE TABLE data ( d, dt, status ) AS
SELECT 1, TO_DATE( '1/1/18', 'MM/DD/YY' ), 'requirement' FROM DUAL UNION ALL
SELECT 1, TO_DATE( '1/8/18', 'MM/DD/YY' ), 'analysis' FROM DUAL UNION ALL
SELECT 1, NULL, 'design' FROM DUAL UNION ALL
SELECT 1, TO_DATE( '1/30/18', 'MM/DD/YY' ), 'closed' FROM DUAL UNION ALL
SELECT 2, TO_DATE( '2/1/18', 'MM/DD/YY' ), 'requirement' FROM DUAL UNION ALL
SELECT 2, TO_DATE( '2/18/18', 'MM/DD/YY' ), 'closed' FROM DUAL UNION ALL
SELECT 3, TO_DATE( '1/2/18', 'MM/DD/YY' ), 'requirement' FROM DUAL UNION ALL
SELECT 3, TO_DATE( '1/29/18', 'MM/DD/YY' ), 'analysis' FROM DUAL UNION ALL
SELECT 3, NULL, 'accepted' FROM DUAL UNION ALL
SELECT 3, TO_DATE( '2/5/18', 'MM/DD/YY' ), 'closed' FROM DUAL;
Query:
WITH statuses ( status, id ) AS (
SELECT 'requirement', 1 FROM DUAL UNION ALL
SELECT 'analysis', 2 FROM DUAL UNION ALL
SELECT 'design', 3 FROM DUAL UNION ALL
SELECT 'accepted', 4 FROM DUAL UNION ALL
SELECT 'closed', 5 FROM DUAL
),
ranges ( d, dt, status, id, recent_dt, recent_id, next_dt, next_id ) AS (
SELECT d.d,
d.dt,
s.status,
s.id,
NVL(
d.dt,
LAG( d.dt, 1 )
IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
),
NVL2(
d.dt,
s.id,
LAG( CASE WHEN d.dt IS NOT NULL THEN s.id END, 1 )
IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
),
LEAD( d.dt, 1, d.dt )
IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id ),
LEAD( CASE WHEN d.dt IS NOT NULL THEN s.id END, 1, s.id + 1 )
IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
FROM data d
PARTITION BY ( d )
RIGHT OUTER JOIN statuses s
ON ( d.status = s.status )
)
SELECT d,
dt,
status,
( next_dt - recent_dt ) / (next_id - recent_id ) AS estimated_duration
FROM ranges;
Output:
D | DT | STATUS | ESTIMATED_DURATION
-: | :-------- | :---------- | ---------------------------------------:
1 | 01-JAN-18 | requirement | 7
1 | 08-JAN-18 | analysis | 7.33333333333333333333333333333333333333
1 | null | design | 7.33333333333333333333333333333333333333
1 | null | accepted | 7.33333333333333333333333333333333333333
1 | 30-JAN-18 | closed | 0
2 | 01-FEB-18 | requirement | 4.25
2 | null | analysis | 4.25
2 | null | design | 4.25
2 | null | accepted | 4.25
2 | 18-FEB-18 | closed | 0
3 | 02-JAN-18 | requirement | 27
3 | 29-JAN-18 | analysis | 2.33333333333333333333333333333333333333
3 | null | design | 2.33333333333333333333333333333333333333
3 | null | accepted | 2.33333333333333333333333333333333333333
3 | 05-FEB-18 | closed | 0
Query 2:
Then of you can easily change that to take the average for each status:
WITH statuses ( status, id ) AS (
SELECT 'requirement', 1 FROM DUAL UNION ALL
SELECT 'analysis', 2 FROM DUAL UNION ALL
SELECT 'design', 3 FROM DUAL UNION ALL
SELECT 'accepted', 4 FROM DUAL UNION ALL
SELECT 'closed', 5 FROM DUAL
),
ranges ( d, dt, status, id, recent_dt, recent_id, next_dt, next_id ) AS (
SELECT d.d,
d.dt,
s.status,
s.id,
NVL(
d.dt,
LAG( d.dt, 1 )
IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
),
NVL2(
d.dt,
s.id,
LAG( CASE WHEN d.dt IS NOT NULL THEN s.id END, 1 )
IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
),
LEAD( d.dt, 1, d.dt )
IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id ),
LEAD( CASE WHEN d.dt IS NOT NULL THEN s.id END, 1, s.id + 1 )
IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
FROM data d
PARTITION BY ( d )
RIGHT OUTER JOIN statuses s
ON ( d.status = s.status )
)
SELECT status,
AVG( ( next_dt - recent_dt ) / (next_id - recent_id ) ) AS estimated_duration
FROM ranges
GROUP BY status, id
ORDER BY id;
Results:
STATUS | ESTIMATED_DURATION
:---------- | ---------------------------------------:
requirement | 12.75
analysis | 4.63888888888888888888888888888888888889
design | 4.63888888888888888888888888888888888889
accepted | 4.63888888888888888888888888888888888889
closed | 0
db<>fiddle here

How to use Oracle's LISTAGG function with a multi values?

I have an 'ITEMS' table like below:
ITEM_NO ITEM_NAME
1 Book
2 Pen
3 Sticky Notes
4 Ink
5 Corrector
6 Ruler
In another 'EMP_ITEMS' table I have the below:
EMPLOYEE ITEMS_LIST
John 1,2
Mikel 5
Sophia 2,3,6
William 3,4
Daniel null
Michael 6
The output has to be like this:
EMPLOYEE ITEMS_LIST ITEM_NAME
John 1,2 Book,Pen
Mikel 5 Corrector
Sophia 2,3,6 Pen,Sticky Notes,Ruler
William 3,4 Sticky Notes,Ink
Daniel null null
Michael 6 Ruler
I used the below query:
SELECT e.EMPLOYEE,e.ITEMS_LIST, LISTAGG(i.ITEM_NAME, ',') WITHIN GROUP (ORDER BY i.ITEM_NAME) ITEM_DESC
FROM EMP_ITEMS e
INNER JOIN ITEMS i ON i.ITEM_NO = e.ITEMS_LIST
GROUP BY e.EMPLOYEE,e.ITEMS_LIST;
But there is an error:
ORA-01722: invalid number
But there is an error: ORA-01722: invalid number
That is because your ITEMS_LIST is a string composed of numeric and comma characters and is not actually a list of numbers and you are trying to compare a single item number to a list of items.
Instead treat it as a string a look for sub-string matches. To do this you will need to surround the strings in the delimiter character and compare to see if one is the substring of the other:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE Items ( ITEM_NO, ITEM_NAME ) As
SELECT 1, 'Book' FROM DUAL UNION ALL
SELECT 2, 'Pen' FROM DUAL UNION ALL
SELECT 3, 'Sticky Notes' FROM DUAL UNION ALL
SELECT 4, 'Ink' FROM DUAL UNION ALL
SELECT 5, 'Corrector' FROM DUAL UNION ALL
SELECT 6, 'Ruler' FROM DUAL;
CREATE TABLE emp_items ( EMPLOYEE, ITEMS_LIST ) AS
SELECT 'John', '1,2' FROM DUAL UNION ALL
SELECT 'Mikel', '5' FROM DUAL UNION ALL
SELECT 'Sophia', '3,2,6' FROM DUAL UNION ALL
SELECT 'William', '3,4' FROM DUAL UNION ALL
SELECT 'Daniel', null FROM DUAL UNION ALL
SELECT 'Michael', '6' FROM DUAL;
Query 1:
SELECT e.employee,
e.items_list,
LISTAGG( i.item_name, ',' )
WITHIN GROUP (
ORDER BY INSTR( ','||e.items_list||',', ','||i.item_no||',' )
) AS item_names
FROM emp_items e
LEFT OUTER JOIN
items i
ON ( ','||e.items_list||',' LIKE '%,'||i.item_no||',%' )
GROUP BY e.employee, e.items_list
Results:
| EMPLOYEE | ITEMS_LIST | ITEM_NAMES |
|----------|------------|------------------------|
| John | 1,2 | Book,Pen |
| Mikel | 5 | Corrector |
| Daniel | (null) | (null) |
| Sophia | 3,2,6 | Sticky Notes,Pen,Ruler |
| Michael | 6 | Ruler |
| William | 3,4 | Sticky Notes,Ink |

Find top users who cumulatively have 75% of all points

I am trying to find out the top users who cumulatively have 75% of all points.
Table is:
In this users list must the result should be users (dick, mary, jack and sam).
I try with (Oracle select..)
SELECT o.users, SUM (o.points)
FROM (SELECT users,
SUM (points),
RANK () OVER (ORDER BY SUM (points) DESC) r
FROM points_tbl) o;
--> error is:
ORA-00904: "o"."points": invalid identifier
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE points ( "user", points ) AS
SELECT 'joe', 10 FROM DUAL UNION ALL
SELECT 'bill', 15 FROM DUAL UNION ALL
SELECT 'dick', 25 FROM DUAL UNION ALL
SELECT 'jack', 32 FROM DUAL UNION ALL
SELECT 'mary', 45 FROM DUAL UNION ALL
SELECT 'noe', 12 FROM DUAL UNION ALL
SELECT 'sam', 18 FROM DUAL;
Query 1:
SELECT "user", points
FROM (
SELECT p.*,
COALESCE(
SUM( points ) OVER (
ORDER BY points DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0
) / SUM( points ) OVER () AS pct
FROM points p
ORDER BY points DESC
)
WHERE pct < .75
Results:
| user | POINTS |
|------|--------|
| mary | 45 |
| jack | 32 |
| dick | 25 |
| sam | 18 |

Delete Oracle with join tables

I have a table that has three columns ( primary key) and I need a delete sentece that allows me to remove the elements I don't need , using a join with the this table and other table, I've tried two delete sentences but they are not working as expected:
First One: This one gets the values I dont need and they are removed from table A, but the issue here is it deletes the values from Table B and C too and those rows can't be removed
DELETE
FROM
(SELECT A.*
FROM TABLE_A A
JOIN TABLE_B B
ON A.CODE =B.CODE
JOIN TABLE_C C
ON B.PRODUCT =C.PRODUCT
WHERE B.VALUE >10000
AND C.RANGE NOT IN (4006, 4005, 4004, 4003, 4002, 4001)
);
**Second One:**The problem with this one is that it removes all the rows from table A, but if I test the query ( select) it returns 5 rows, the ones that should be removed.
DELETE
FROM A WHERE EXIST
(SELECT A.*
FROM TABLE_A A
JOIN TABLE_B B
ON A.CODE =B.CODE
JOIN TABLE_C C
ON B.PRODUCT =C.PRODUCT
WHERE B.VALUE >10000
AND C.RANGE NOT IN (4006, 4005, 4004, 4003, 4002, 4001)
);
So has anyone any idea of what I could be doing wrong?
The first one will delete matched rows across the joins, the second one will delete all rows when there EXISTS any one matched row as you are not correlating the deleted rows to the sub-query.
You can perform this correlation using the ROWID pseudo-column:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_a ( id, code ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 3, 3 FROM DUAL;
CREATE TABLE table_b ( id, code, product, value ) AS
SELECT 1, 1, 1, 10001 FROM DUAL UNION ALL
SELECT 2, 2, 2, 10001 FROM DUAL UNION ALL
SELECT 3, 3, 1, 9999 FROM DUAL;
CREATE TABLE table_c ( id, product, range ) AS
SELECT 1, 1, 1001 FROM DUAL UNION ALL
SELECT 2, 2, 4001 FROM DUAL;
DELETE
FROM table_A
WHERE ROWID IN (
SELECT A.ROWID
FROM TABLE_A A
JOIN TABLE_B B
ON A.CODE = B.CODE
JOIN TABLE_C C
ON B.PRODUCT = C.PRODUCT
WHERE B.VALUE >10000
AND C.RANGE NOT IN (4006, 4005, 4004, 4003, 4002, 4001)
);
Query 1:
SELECT * FROM table_a
Results:
| ID | CODE |
|----|------|
| 2 | 2 |
| 3 | 3 |
Query 2:
SELECT * FROM table_b
Results:
| ID | CODE | PRODUCT | VALUE |
|----|------|---------|-------|
| 1 | 1 | 1 | 10001 |
| 2 | 2 | 2 | 10001 |
| 3 | 3 | 1 | 9999 |
Query 3:
SELECT * FROM table_c
Results:
| ID | PRODUCT | RANGE |
|----|---------|-------|
| 1 | 1 | 1001 |
| 2 | 2 | 4001 |

Resources