This qes is about oracle db.
I have a table (T1) with several columns:
group
id
price
A
1
50
A
5
40
B
4
54
C
1
33
C
6
33
D
5
13
D
3
4
And another table (T2) with 2 columns:
id
description
1
aaa
2
bbb
3
ccc
4
ddd
5
eee
6
fff
7
ggg
The Id in this table (t2) is unique.
the connection between the two tables is by the "id" column.
I need to check for each (!) Group in T1 (A, B, C, D),
which of the "id" from T2 - not found for this group in T1,
and the result needs to be: group + id (that does not exist in this group)
for the example on the above tables, I expected to get this result:
group
id
A
2
A
3
A
4
A
6
A
7
B
1
B
2
B
3
B
5
B
6
B
7
C
2
C
3
C
4
C
5
C
7
D
1
D
2
D
4
D
6
D
7
Thank You!
In Oracle you may use partitioned join
with a(grp, id, price) as (
select 'A', 1, 50 from dual union all
select 'A', 5, 40 from dual union all
select 'B', 4, 54 from dual union all
select 'C', 1, 33 from dual union all
select 'C', 6, 33 from dual union all
select 'D', 5, 13 from dual union all
select 'D', 3, 4 from dual
)
, b (id, descr) as (
select 1, 'aaa' from dual union all
select 2, 'bbb' from dual union all
select 3, 'ccc' from dual union all
select 4, 'ddd' from dual union all
select 5, 'eee' from dual union all
select 6, 'fff' from dual union all
select 7, 'ggg' from dual
)
select
grp
, id
from a
partition by (grp)
right join b
using (id)
where a.price is null
order by grp, id
GRP | ID
:-- | -:
A | 2
A | 3
A | 4
A | 6
A | 7
B | 1
B | 2
B | 3
B | 5
B | 6
B | 7
C | 2
C | 3
C | 4
C | 5
C | 7
D | 1
D | 2
D | 4
D | 6
D | 7
db<>fiddle here
The idea here is to create all possible record combinations by using a Cartesian join and then match all groups and select non existing via left join and not null
create table T1 (c1 varchar2(10), c2 number(10), c3 number(10))
insert into T1 values('A', 1, 50);
insert into T1 values('A', 5, 40);
insert into T1 values('B', 4 , 54);
insert into T1 values('C', 1, 33);
insert into T1 values('C', 6, 33);
insert into T1 values('D', 5, 13);
insert into T1 values('D', 3, 4);
create table T2 (c6 number(10), c7 varchar2(10))
insert into T2 values( 1, 'aaa');
insert into T2 values( 2, 'bbb');
insert into T2 values( 3, 'ccc');
insert into T2 values( 4, 'ddd');
insert into T2 values( 5, 'eee');
insert into T2 values( 6, 'fff');
insert into T2 values( 7, 'ggg');
SELECT tx.c1, tx.c6, ty.c1
FROM
(select c1, c6
from
(select distinct c1 from T1) ta,
(select distinct c6 from T2) tb) tx -- tx is the cartisian product
left join
(select c1, c2 from T1 group by c1, c2) ty
on tx.c1 = ty.c1 and tx.c6 = ty.c2
WHERE
ty.c1 is null
ORDER BY 1, 2
This gets you exactly what you're looking for.
Tested and verified
First, find all the possible combinations, then determine which of them don't exist:
WITH cteCombinations
AS (SELECT DISTINCT T1."group", T2.ID
FROM T1
CROSS JOIN T2)
SELECT c."group", c.ID
FROM cteCombinations c
LEFT OUTER JOIN T1
ON T1.ID = c.ID AND
T1."group" = c."group"
WHERE T1.ID IS NULL
ORDER BY c."group", c.ID
The CTE (Common Table Expression) uses a CROSS JOIN to find all of the unique combinations of group and ID; then the LEFT OUTER JOIN is used to determine which of the combinations don't exist in T1.
Another way to do it is:
WITH cteCombinations
AS (SELECT DISTINCT T1."group", T2.ID
FROM T1
CROSS JOIN T2)
SELECT c."group", c.ID
FROM cteCombinations c
WHERE (c."group", c.ID) NOT IN (SELECT "group", ID
FROM T1)
ORDER BY c."group", c.ID
Here we use the same CTE to generate the possible combinations, but instead of a LEFT OUTER JOIN we use a NOT IN comparison to determine which of the combinations are not present in table T1.
db<>fiddle here
Related
Let say I have the next table:
ID_1
ID_2
Value
1
11
A
2
12
A
2
13
A
2
13
B
3
12
A
3
13
B
I want to transform it to:
ID_1
ID_2
Value_A
Value_B
1
11
1
0
2
12
1
0
2
13
1
1
3
12
1
0
3
13
0
1
When there is a value A but no Value B, so put 0 in Value B
When there is a value B but no Value A, so put 0 in Value A
How can I do it in oracle?
Use a PIVOT:
SELECT *
FROM table_name
PIVOT ( COUNT(*) FOR value IN ('A' AS Value_A, 'B' AS Value_B) )
Which, for the sample data:
CREATE TABLE table_name (ID_1, ID_2, Value) AS
SELECT 1, 11, 'A' FROM DUAL UNION ALL
SELECT 2, 12, 'A' FROM DUAL UNION ALL
SELECT 2, 13, 'A' FROM DUAL UNION ALL
SELECT 2, 13, 'B' FROM DUAL UNION ALL
SELECT 3, 12, 'A' FROM DUAL UNION ALL
SELECT 3, 13, 'B' FROM DUAL;
Outputs:
ID_1
ID_2
VALUE_A
VALUE_B
1
11
1
0
3
12
1
0
2
12
1
0
3
13
0
1
2
13
1
1
fiddle
You could use below solution to get the job done
select ID_1, ID_2
, decode(Value_A, null, 0, 1) Value_A
, decode(Value_B, null, 0, 1) Value_B
from your_Table t
pivot (
max(Value) for value in (
'A' as Value_A
, 'B' as Value_B
)
)
order by ID_1, ID_2
;
demo
I have the following setup, which is working perfectly. I am difficulty figuring out the syntax how to display the course name in the output. In my test CASE all the rows should have the value Geometry.
In addition, how could I use rank or rank_dense to limit the output to display only 1 row with the highest average?
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, 'Betty', 'Bowers' FROM dual;
CREATE TABLE courses(course_id, course_name) AS
SELECT 1, 'Geometry' FROM dual UNION ALL
SELECT 2, 'Trigonometry' FROM dual UNION ALL
SELECT 3, 'Calculus' FROM DUAL;
CREATE TABLE grades(student_id,
course_id, grade) AS
SELECT 1, 1, 75 FROM dual UNION ALL
SELECT 1, 1, 81 FROM dual UNION ALL
SELECT 1, 1, 76 FROM dual UNION ALL
SELECT 2, 1, 100 FROM dual UNION ALL
SELECT 2, 1, 95 FROM dual UNION ALL
SELECT 2, 1, 96 FROM dual UNION ALL
SELECT 3, 1, 80 FROM dual UNION ALL
SELECT 3, 1, 85 FROM dual UNION ALL
SELECT 3, 1, 86 FROM dual UNION ALL
SELECT 4, 1, 88 FROM dual UNION ALL
SELECT 4, 1, 85 FROM dual UNION ALL
SELECT 4, 1, 91 FROM dual UNION ALL
SELECT 5, 1, 98 FROM dual UNION ALL
SELECT 5, 1, 74 FROM dual UNION ALL
SELECT 5, 1, 81 FROM dual;
/* average grade of each student */
select s.student_id
, s.first_name
, s.last_name
, round(avg(g.grade), 1) as student_avg
from students s
join grades g
on s.student_id = g.student_id
group by s.student_id, s.first_name, s.last_name
ORDER BY avg(g.grade) DESC;
Something like this?
SQL> with temp as
2 (select s.student_id
3 , s.first_name
4 , s.last_name
5 , c.course_name
6 , round(avg(g.grade), 1) as student_avg
7 , rank() over (order by avg(g.grade) desc) rnk
8 from students s join grades g on s.student_id = g.student_id
9 join courses c on c.course_id = g.course_id
10 group by s.student_id, s.first_name, s.last_name, c.course_name
11 )
12 select student_id, first_name, last_name, course_name, student_avg
13 from temp
14 where rnk <= 3
15 order by rnk;
STUDENT_ID FIRST_ LAST_NAM COURSE_NAME STUDENT_AVG
---------- ------ -------- ------------ -----------
2 Lisa Saladino Geometry 97
4 Patty Kern Geometry 88
5 Betty Bowers Geometry 84.3
SQL>
the table t has two column: a and b.
a is an integer, b is collection.
I would like to to select for each line a and the first value of the collection b.
I tried this but it doesn't work
WITH
s (a, b)
AS
(SELECT 1, 'ff' FROM DUAL
UNION ALL
SELECT 1, 'ee' FROM DUAL
UNION ALL
SELECT 1, 'ee' FROM DUAL
UNION ALL
SELECT 2, 'ee' FROM DUAL),
t (a, b)
AS
( SELECT s.a, COLLECT (s.b)
FROM s
group BY s.a)
select t.a, t.b.first()
from t
[Error] Execution (42: 16): ORA-00904: "T"."B"."FIRST": invalid identifier
Is there a way to to do that?
code
this query do the same things witout the collection method first.
WITH
s (a, b)
AS
(SELECT 1, 'ff' FROM DUAL
UNION ALL
SELECT 1, 'ee' FROM DUAL
UNION ALL
SELECT 1, 'ee' FROM DUAL
UNION ALL
SELECT 2, 'ee' FROM DUAL),
t (a, b)
AS
( SELECT s.a, COLLECT (s.b)
FROM s
GROUP BY s.a),
v (a, b)
AS
(SELECT t.a, tb.b
FROM t
OUTER APPLY (SELECT x.COLUMN_VALUE b
FROM TABLE (t.b) x
FETCH FIRST 1 ROW ONLY) tb)
SELECT *
FROM v;
code
I have two tables User_details and Level_details.
User_details table:
ID Name
1 A
2 B
3 C
4 D
5 E
Level_details table:
trns_id Lvl usr_id
66 1 1
66 1 5
77 1 2
77 2 3
66 2 4
66 2 3
77 2 3
66 2 4
I am getting the result like:
trns_id Lvl name
66 1 A, E
66 2 D, C, D
77 1 B
77 2 C, C
I am using LISTAG function to get name
LISTAGG(( SELECT name FROM User_details l WHERE l.usr_id = id and trns_id=t1.trns_id and lvl=t1.lvl ), ',') WITHIN GROUP( ORDER BY lvl ) AS Name
You can use the distinct modifier in a listagg function call:
SELECT trns_id, lvl, LISTAGG(DISTINCT name, ', ') WITHIN GROUP (ORDER BY name)
FROM level_details l
JOIN user_details u ON l.usr_id = u.id
GROUP BY trns_id, lvl
If your database version doesn't support DISTINCT within LISTAGG, then you'll have to first select distinct values (lines #21 - 23), then aggregate them (line #20). Lines #1 - 17 represent sample data; you already have that and don't type it. Query you need begins at line #18.
SQL> with user_details (usr_id, name) as
2 (select 1, 'A' from dual union all
3 select 2, 'B' from dual union all
4 select 3, 'C' from dual union all
5 select 4, 'D' from dual union all
6 select 5, 'E' from dual
7 ),
8 level_details (trns_id, lvl, usr_id) as
9 (select 66, 1, 1 from dual union all
10 select 66, 1, 5 from dual union all
11 select 77, 1, 2 from dual union all
12 select 77, 2, 3 from dual union all
13 select 66, 2, 4 from dual union all
14 select 66, 2, 3 from dual union all
15 select 77, 2, 3 from dual union all
16 select 66, 2, 4 from dual
17 )
18 select x.trns_id,
19 x.lvl,
20 listagg(x.name, ', ') within group (order by x.lvl) name
21 from (select distinct u.usr_id, u.name, d.trns_id, d.lvl
22 from user_details u join level_details d on d.usr_id = u.usr_id
23 ) x
24 group by x.trns_id,
25 x.lvl;
TRNS_ID LVL NAME
---------- ---------- ---------------
66 1 A, E
66 2 C, D
77 1 B
77 2 C
SQL>
LISTAGG gives duplicate values if you have duplicate values
trns_id Lvl usr_id
77 2 3
77 2 3
You can remove duplicates first:
select trns_id, Lvl, LISTAGG(name)
from (
select distinct l.trns_id l.Lvl, u.name
from User_details u
join Level_details l on l.usr_id=u.ID
)
group by trns_id, Lvl
I have 3 columns a,b,c in table.i need to find the duplicates for the columns a & b but with distinct value in c column.
Maybe you need something like this:
with test(a, b, c) as (
select 1, 2, 10 from dual union all
select 1, 2, 20 from dual union all
select 4, 5, 30 from dual union all
select 4, 5, 30 from dual union all
select 3, 2, 3 from dual union all
select 6, 2, 2 from dual
)
select a, b
from test
group by a,b
having count(distinct c) > 1
That is, you need to aggregate for A,B, but only keeping pairs for which there are more DISTINCT values for column C