Oracle Sort by column value - sql

I am trying to interpret my real time scenario as follows. I have 2 tables and I need a query to return sorted students based on Course value. In the following example, the resulted Student Ids will be in order of 2, 3, 1.
STUDENT
ID NAME PRIORITY STATUS
1 ABC
2 BCD
3 CDE
VARIABLE
V_ID STU_ID KEY VALUE
1 1 name name1
2 1 **course** MCA
3 1 place place1
4 2 name name2
5 2 **course** BCA
6 2 place place2
7 3 name name1
8 3 **course** FCA
9 3 place place1
Desired result(after sort, show data from both tables in the same sort order.):
ID NAME KEY VALUE
2 BCD name name2
2 BCD **course** BCA
2 BCD place place2
3 CDE name name1
3 CDE **course** FCA
3 CDE place place1
1 ABC name name1
1 ABC **course** MCA
1 ABC place place1
Your help will be appreciated.
thanks,
Swamy.

It seems that you just need to use a regular join, like this
SELECT student.id
from variable
join student
on variable.stu_id= student.id
where variable.key= '**course**'
order by variable.value asc

Just need to join to Variable twice once for course to get the order and once for all the data. The join for the order needs to be just on the student and key so all records get the "sort order course" applied.
Though I'm not sure what you're sorting on after the course value for each student to get your results. the key makes some sense but without hardcoding a case statement I can't get to your results.
With student (ID, NAME, PRIORITY, STATUS) as (
SELECT 1, 'ABC', NULL, NULL FROM DUAL UNION ALL
SELECT 2, 'BCD', NULL, NULL FROM DUAL UNION ALL
SELECT 3, 'CDE', NULL, NULL FROM DUAL),
"VARIABLE" (V_ID, STU_ID, "KEY", "VALUE") as (
SELECT 1, 1, 'name', 'name1' FROM DUAL UNION ALL
SELECT 2, 1, '**course**', 'MCA' FROM DUAL UNION ALL
SELECT 3, 1, 'place', 'place1' FROM DUAL UNION ALL
SELECT 4, 2, 'name', 'name2' FROM DUAL UNION ALL
SELECT 5, 2, '**course**', 'BCA' FROM DUAL UNION ALL
SELECT 6, 2, 'place', 'place2' FROM DUAL UNION ALL
SELECT 7, 3, 'name', 'name1' FROM DUAL UNION ALL
SELECT 8, 3, '**course**', 'FCA' FROM DUAL UNION ALL
SELECT 9 , 3, 'place', 'lace1' FROM DUAL)
SELECT V.STU_ID, S.Name, V.Key, V.VALUE
FROM STUDENT S
INNER JOIN VARIABLE V
on S.ID = V.Stu_ID
LEFT JOIN VARIABLE V2
on S.ID = V2.Stu_ID and V2.Key = '**course**'
ORDER BY V2.Value, V.Key

Related

Join a table that depends on another table

I have a POST table, a CATEGORY table, a TAG table and a MIGTATION_TAG table, I explain the MIGTATION_TAG table contains the movement of the tags between the categories, for example the tag whose ID = 1 belongs to the category whose l 'ID = 10 if I change its category to 12 a line will be added to the MIGTATION_TAG table as follows:
ID 1 TAG_ID 1 CATEGOTY_ID 12
the POST table
id title content tag_id
---------- ---------- ---------- ----------
1 title1 Text... 1
2 title2 Text... 3
3 title3 Text... 1
4 title4 Text... 2
5 title5 Text... 5
6 title6 Text... 4
the CATEGORY table
id name
---------- ----------
1 category_1
2 category_2
3 category_3
the TAG table
id name fist_category_id
---------- ---------- ----------------
1 tag_1 1
2 tag_2 1
3 tag_3 3
4 tag_4 1
5 tag_5 2
the MIGTATION_TAG table
id tag_id category_id
---------- ---------- ----------------
9 1 3
8 5 1
7 1 2
5 3 1
4 2 2
3 5 3
2 3 3
1 1 3
so i would like to know how many posts are registered for each category.
in some cases if there has been no change of category for a tag then it keeps its first category,
I manage to join the TAG table to the POST table via LEFT JOIN but the problem is that the join must depend on the MIGTATION_TAG table which must check if there has been a migration, if so then it must bring me back the last MAX (tag_id ) for each tag ,
here is my query
select category, COUNT(*) AS numer_of_posts
from(
select CATEGORY.name,
case
when POST.tag_id is not null then CATEGORY.name
end as category
from POST
left join TAG ON POST.tag_id = TAG.id
left join (
select id, MAX(tag_id) tag_id
from MIGTATION_TAG
group by id, tag_id
) MIGTATION_TAG
ON TAG.id = MIGTATION_TAG.tag_id
left join CATEGORY on MIGTATION_TAG.category_id = CATEGORY.id
)
GROUP BY category
;
here is the result i want to display with my query
Important ! for the post with id = 6 the tag_id = 4 whish was not changed so it will be using the fist_category_id in TAG table
category numer_of_posts
---------- --------------
category_1 3
category_2 1
category_3 2
Best regards
You can use:
SELECT MAX(c.name) AS category,
COUNT(*)
FROM post p
INNER JOIN tag t
ON (p.tag_id = t.id)
LEFT OUTER JOIN (
SELECT tag_id,
MAX(category_id) KEEP (DENSE_RANK LAST ORDER BY id) AS category_id
FROM migration_tag
GROUP BY tag_id
) m
ON (t.id = m.tag_id)
INNER JOIN category c
ON ( COALESCE(m.category_id, t.first_category_id) = c.id )
GROUP BY c.id
ORDER BY category
Which, for the sample data:
CREATE TABLE POST ( id, title, content, tag_id ) AS
SELECT 1, 'title1', 'Text...', 1 FROM DUAL UNION ALL
SELECT 2, 'title2', 'Text...', 3 FROM DUAL UNION ALL
SELECT 3, 'title3', 'Text...', 1 FROM DUAL UNION ALL
SELECT 4, 'title4', 'Text...', 2 FROM DUAL UNION ALL
SELECT 5, 'title5', 'Text...', 5 FROM DUAL UNION ALL
SELECT 6, 'title6', 'Text...', 4 FROM DUAL;
CREATE TABLE CATEGORY ( id, name ) AS
SELECT 1, 'category_1' FROM DUAL UNION ALL
SELECT 2, 'category_2' FROM DUAL UNION ALL
SELECT 3, 'category_3' FROM DUAL;
CREATE TABLE TAG (id, name, first_category_id) AS
SELECT 1, 'tag_1', 1 FROM DUAL UNION ALL
SELECT 2, 'tag_2', 1 FROM DUAL UNION ALL
SELECT 3, 'tag_3', 3 FROM DUAL UNION ALL
SELECT 4, 'tag_4', 1 FROM DUAL UNION ALL
SELECT 5, 'tag_5', 2 FROM DUAL;
CREATE TABLE MIGRATION_TAG ( id, tag_id, category_id ) AS
SELECT 9, 1, 3 FROM DUAL UNION ALL
SELECT 8, 5, 1 FROM DUAL UNION ALL
SELECT 7, 1, 2 FROM DUAL UNION ALL
SELECT 5, 3, 1 FROM DUAL UNION ALL
SELECT 4, 2, 2 FROM DUAL UNION ALL
SELECT 3, 5, 3 FROM DUAL UNION ALL
SELECT 2, 3, 3 FROM DUAL UNION ALL
SELECT 1, 1, 3 FROM DUAL;
Outputs:
CATEGORY
COUNT(*)
category_1
3
category_2
1
category_3
2
fiddle
One option uses a left join to bring the tag table, and the a lateral join to lookup the latest migration, ifi any. We can then use conditional logic:
select coalesce(t2.category_id, t.first_category_id) category, count(*) number_of_posts
from post p
inner join tag t on t.id = p.tag_id
outer apply (
select mt.category_id
from migration_tag mt
where mt.tag_id = p.tag_id
order by mt.id desc fetch first row only
) t2
group by coalesce(t2.category_id, t.first_category_id)

how to apply PIVOT conditionally in below case

I have following tables and trying to PIVOT both parent and child as column headers. In my case "author and books". I am able to PIVOT either author or books at a time but not able to getting both at a time as comma separated with condition ( condition explained below). I have given sample data and output.author1 and author 2 column shows "AVG" of reviews along with color.
r=red &
y=yellow
In the sample output, we can see color as r & y. I have applied a condition here. if an author gets "r" at any time, Then output is always "r" otherwise "y". In my first case author1 gets y & r. so the output gives r. Other case "r" is not getting so "y" is displayed. If no color assigned it should be "NA"
user
Aid userName
1 author1
2 author2
books
bid NAME Aid
1 x 1
2 y 1
3 z 2
Location
loc_id Loc_name
1 UK
2 USA
3 Europe
UserAssign
uid Aid bid loc_d color reviews
1 1 1 1 y 12
2 1 2 1 r 14
3 2 3 1 y 11
4 1 1 2 y 10
5 2 3 2 y 112
Expected o/p
--------------------------------------------
Location author1 x y author2 z
Uk r,13 12 14 y,11 11
USA y,10 10 y,112 112
A useful query in the format you are requesting is not possible. When using PIVOT, the expected number of columns in the result should always be the same. In the format that you are displaying the data, additional columns would get added if there was a new book or a new author.
The query below will only work with the specified set of books and authors:
WITH
users (aid, username)
AS
(SELECT 1, 'author1' FROM DUAL
UNION ALL
SELECT 2, 'author2' FROM DUAL),
books (bid, name, aid)
AS
(SELECT 1, 'x', 1 FROM DUAL
UNION ALL
SELECT 2, 'y', 1 FROM DUAL
UNION ALL
SELECT 3, 'z', 2 FROM DUAL),
location (loc_id, loc_name)
AS
(SELECT 1, 'UK' FROM DUAL
UNION ALL
SELECT 2, 'USA' FROM DUAL
UNION ALL
SELECT 3, 'Europe' FROM DUAL),
UserAssign (u_UID,
Aid,
bid,
loc_id,
color,
reviews)
AS
(SELECT 1, 1, 1, 1, 'y', 12 FROM DUAL
UNION ALL
SELECT 2, 1, 2, 1, 'r', 14 FROM DUAL
UNION ALL
SELECT 3, 2, 3, 1, 'y', 11 FROM DUAL
UNION ALL
SELECT 4, 1, 1, 2, 'y', 10 FROM DUAL
UNION ALL
SELECT 5, 2, 3, 2, 'y', 112 FROM DUAL)
SELECT l.loc_name, p.*
FROM ( --authors
SELECT loc_id,
u.username AS colheader,
MIN (color) || ',' || AVG (ua1.reviews) AS avg_reviews
FROM userassign ua1 JOIN users u ON (ua1.aid = u.aid)
GROUP BY loc_id, u.username
UNION ALL
--books
SELECT loc_id, b.name, TO_CHAR (ua2.reviews) AS avg_reviews
FROM userassign ua2 JOIN books b ON (ua2.bid = b.bid))
PIVOT (MIN (avg_reviews)
FOR colheader
IN ('author1' AS author1,
'x' AS x,
'y' AS y,
'author2' AS author2,
'z' AS z)) p
JOIN location l ON (p.loc_id = l.loc_id)
ORDER BY l.loc_name;
LOC_NAME LOC_ID AUTHOR1 X Y AUTHOR2 Z
___________ _________ __________ _____ _____ __________ ______
UK 1 r,13 12 14 y,11 11
USA 2 y,10 10 y,112 112

select only those users whose contacts length is not 5

I have table like this:
id
name
contact
1
A
65489
1
A
1
A
45564
2
B
3
C
12345
3
C
1234
4
D
32
4
D
324
I only want users who have no contact or the contact length is not five.
If the user has two or more contacts and the length of one of them is five and the rest is not, then such users should not be included in the table.
so,If the customer has at least one contact length of five, I do not want that.
so, i want table like this:
id
name
contact
2
B
4
D
32
4
D
324
Can you halp me?
You could actually do a range check here:
SELECT id, name, contact
FROM yourTable t1
WHERE NOT EXISTS (
SELECT 1
FROM yourTable t2
WHERE t2.id = t1.id AND TO_NUMBER(t2.contact) BETWEEN 10000 AND 99999
);
Note that if contact already be a numeric column, then just remove the calls to TO_NUMBER above and compare directly.
Yet another option:
SQL> with test (id, name, contact) as
2 (select 1, 'a', 65879 from dual union all
3 select 1, 'a', null from dual union all
4 select 1, 'a', 45564 from dual union all
5 select 2, 'b', null from dual union all
6 select 3, 'c', 12345 from dual union all
7 select 3, 'c', 1234 from dual union all
8 select 4, 'd', 32 from dual union all
9 select 4, 'd', 324 from dual
10 )
11 select *
12 from test a
13 where exists (select null
14 from test b
15 where b.id = a.id
16 group by b.id
17 having nvl(max(length(b.contact)), 0) < 5
18 );
ID N CONTACT
---------- - ----------
2 b
4 d 32
4 d 324
SQL>
COUNT analytic function can also be used to get the job done.
select id, name, contact
from (
select id, name, contact
, count( decode( length(contact), 5, 1, null ) ) over( partition by id, name ) cnt
from YourTable
)
where cnt = 0
demo

Oracle listagg - Can I pull data from other table based on the values selected by listagg

I have two tables and want to get data from one table based on the values got from Listtagg in the second table:
T1
ID Name
==============
1 Name1
2 Person2
3 Someone3
4 Mr.4
T2
ID Acct
===============
1 1234
1 5678
2 1234
3 5678
3 8769
4 1234
My listagg query on T2 has returned the following:
Acct Id
====== ========
1234 1,2,4
5678 1,3
I need the result with Names from other table something like:
Acct Id Name
====== ======== ==========
1234 1,2,4 Name1, Person2, Mr.4
5678 1,3 Name1, Someone3
Why would you first aggregate IDs, and then put effort in splitting them to collect NAMEs? Do it immediately. Not that it can't be done (it can, in a relatively simple manner, but - why?!?).
Sample data is from line #1 - 15; query you might need begins at line #16.
SQL> with
2 t1 (id, name) as
3 (select 1, 'Name1' from dual union all
4 select 2, 'Person2' from dual union all
5 select 3, 'Someone3' from dual union all
6 select 4, 'Mr4' from dual
7 ),
8 t2 (id, acct) as
9 (select 1, 1234 from dual union all
10 select 1, 5678 from dual union all
11 select 2, 1234 from dual union all
12 select 3, 5678 from dual union all
13 select 3, 8769 from dual union all
14 select 4, 1234 from dual
15 )
16 select b.acct,
17 listagg(b.id, ', ') within group (order by b.id) id,
18 listagg(a.name, ', ') within group (order by b.id) name
19 from t1 a join t2 b on a.id = b.id
20 group by b.acct;
ACCT ID NAME
---------- ---------- --------------------
1234 1, 2, 4 Name1, Person2, Mr4
5678 1, 3 Name1, Someone3
8769 3 Someone3
SQL>
#Littlefoot's answer is absolutely correct. But just as an addition: don't use listagg if you are going to split those aggregated values. Just use collect() aggregate function to get needed data as a collection.
For example:
select
cast(collect(level) as sys.odcinumberlist) as varray_of_numbers,
cast(collect(level) as ORA_MINING_NUMBER_NT) as nested_table_of_numbers,
cast(collect(sys.ku$_objnum(level)) as sys.KU$_OBJNUMSET) as nested_table_of_objnum
from dual connect by level<=3;
--Result:
VARRAY_OF_NUMBERS NESTED_TABLE_OF_NUMBERS NESTED_TABLE_OF_OBJNUM(OBJ_NUM)
------------------------- ------------------------------ ------------------------------------------------------------
ODCINUMBERLIST(1, 2, 3) ORA_MINING_NUMBER_NT(1, 2, 3) KU$_OBJNUMSET(KU$_OBJNUM(1), KU$_OBJNUM(2), KU$_OBJNUM(3))
Update: This is a query for your tables, as you asked in the comment:
select b.acct,
cast(collect(b.id) as ORA_MINING_NUMBER_NT) as nested_table_of_numbers,
cast(collect(a.name) as ORA_MINING_VARCHAR2_NT) as nested_table_of_varchar2
-- listagg(b.id, ', ') within group (order by b.id) id,
-- listagg(a.name, ', ') within group (order by b.id) name
from t1 a join t2 b on a.id = b.id
group by b.acct;
Full example:
with
t1 (id, name) as
(select 1, 'Name1' from dual union all
select 2, 'Person2' from dual union all
select 3, 'Someone3' from dual union all
select 4, 'Mr4' from dual
),
t2 (id, acct) as
(select 1, 1234 from dual union all
select 1, 5678 from dual union all
select 2, 1234 from dual union all
select 3, 5678 from dual union all
select 3, 8769 from dual union all
select 4, 1234 from dual
)
select b.acct,
cast(collect(b.id) as ORA_MINING_NUMBER_NT) as nested_table_of_numbers,
cast(collect(a.name) as ORA_MINING_VARCHAR2_NT) as nested_table_of_varchar2
-- listagg(b.id, ', ') within group (order by b.id) id,
-- listagg(a.name, ', ') within group (order by b.id) name
from t1 a join t2 b on a.id = b.id
group by b.acct;

Group the column value based on selective rows for an id

I have a table which have 4 dimensions for a foreignid.
I want to find unique combination based on 2 dimensions.
TABLE1
-----------------------------
ID NAME VALUE TABLE2ID
-----------------------------
1 TYPE 10 1
2 DIR IN 1
3 STATE MA 1
4 COUNT 100 1
5 TYPE 10 2
6 DIR IN 2
7 STATE SA 2
8 COUNT 200 2
9 TYPE 20 3
10 DIR OUT 3
11 STATE MA 3
12 COUNT 300 3
-----------------------------
Here, I want the TABLE2IDs based on the combination of TYPE and DIR rows which is unique.
So, here if you aggregate the row values based on TYPE and DIR you will get
-----------------------------
TYPE DIR TABLE2ID
-----------------------------
10 IN 1
10 IN 2
20 OUT 3
-----------------------------
Note:
The above question is answered
Additional Question related to this.
I have another table which have the count for table2 id based on hour.
I want to group all the count for all hours in a day for unique combination in table1(Don't worry about table 2 structure).
TABLE3
-----------------------------
ID TIME COUNT TABLE2ID
-----------------------------
1 2016101601 10 1
2 2016101602 20 1
3 2016101603 30 1
4 2016101604 40 1
5 2016101601 10 2
6 2016101602 20 2
7 2016101603 30 2
8 2016101604 40 2
9 2016101601 10 3
10 2016101602 20 3
11 2016101603 30 3
12 2016101604 40 3
-----------------------------
Here, I want the output be grouped based on unique value of table 1 according to type and name(regardless of table2id)
----------------------------------
TYPE DIR DATE COUNT
----------------------------------
10 IN 20161016 200
20 OUT 20161016 100
---------------------------------
Use a PIVOT:
Oracle Setup:
CREATE TABLE table1 ( id, name, value, table2id ) AS
SELECT 1, 'TYPE', '10', 1 FROM DUAL UNION ALL
SELECT 2, 'DIR', 'IN', 1 FROM DUAL UNION ALL
SELECT 3, 'STATE', 'MA', 1 FROM DUAL UNION ALL
SELECT 4, 'COUNT', '100', 1 FROM DUAL UNION ALL
SELECT 5, 'TYPE', '10', 2 FROM DUAL UNION ALL
SELECT 6, 'DIR', 'IN', 2 FROM DUAL UNION ALL
SELECT 7, 'STATE', 'SA', 2 FROM DUAL UNION ALL
SELECT 8, 'COUNT', '200', 2 FROM DUAL UNION ALL
SELECT 9, 'TYPE', '20', 3 FROM DUAL UNION ALL
SELECT 10, 'DIR', 'OUT', 3 FROM DUAL UNION ALL
SELECT 11, 'STATE', 'MA', 3 FROM DUAL UNION ALL
SELECT 12, 'COUNT', '300', 3 FROM DUAL;
Query:
SELECT *
FROM ( SELECT name, value, table2id FROM table1 )
PIVOT ( MAX(value) FOR name IN ( 'TYPE' AS type, 'DIR' AS DIR ) );
Output:
TABLE2ID TYPE DIR
-------- ---- ---
1 10 IN
2 10 IN
3 20 OUT
Or as an alternative:
SELECT table2id,
MAX( CASE WHEN name = 'TYPE' THEN value END ) AS type,
MAX( CASE WHEN name = 'DIR' THEN value END ) AS dir
FROM table1
GROUP BY table2id;
You could join two subqueries, one that selects the types and one that selects the dirs for the same id:
SELECT type, dir, a.table2id
FROM (SELECT value AS type, table2id
FROM table1
WHERE name = 'TYPE') a
JOIN (SELECT value AS dir, table2id
FROM table1
WHERE name = 'DIR') b ON a.table2id = b.table2id