how to apply PIVOT conditionally in below case - sql

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

Related

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 Sql SUM MAX

I have following scenario:
ID Campus Credit_Hr
===== ====== ====
1 MIC 3
1 Warrens 4
1 Online 3
1 Online 3
2 MIC 5
2 Warrens 3
2 Online 6
3 Online 3
3 Online 3
3 West 2
4 Warrens 3
4 MIC 3
4 West 7
5 Online 3
5 West 3
5 East 3
Warrens and MIC are major campus. So, when Warrens and MIC has equal credit hr, like in ID 4, chose either Warrens / MIC
For ID 1: Warrens > MIC , chose Warrens though sum(Online) = 6 and is greater
For ID 2: MIC> Warrens, chose MIC
For ID 3: no Major Campus (Warrens/MIC) so chose max credit hr. er sum(online) is maximum so chose Online
For ID 5: West / East /Online all are minor campus, so chose any of them.
There are more than 50 campuses in real.
Assign information about MAJOR campuses, then use this column for ordering, in addition to the sum of hours:
dbfiddle demo
select *
from (
select a.*, row_number() over (partition by id order by major, sm desc) rn
from (
select id, campus,
case when campus in ('MIC', 'Warrens') then 1 else 2 end major,
sum(credit_hr) over (partition by id, campus) sm
from t) a)
where rn = 1
If all you need is to select max credit hours for each ID, but in such a way that if credit hours exist for 'MIC' or 'Warrens' for a given ID, then all other campuses for the same ID should be ignored, then the most efficient way is to use the FIRST aggregate function, like so:
with
sample_data(id, campus, credit_hr) as (
select 1, 'MIC' , 3 from dual union all
select 1, 'Warrens', 4 from dual union all
select 1, 'Online' , 3 from dual union all
select 1, 'Online' , 3 from dual union all
select 2, 'MIC' , 5 from dual union all
select 2, 'Warrens', 3 from dual union all
select 2, 'Online' , 6 from dual union all
select 3, 'Online' , 3 from dual union all
select 3, 'Online' , 3 from dual union all
select 3, 'West' , 2 from dual union all
select 4, 'Warrens', 3 from dual union all
select 4, 'MIC' , 3 from dual union all
select 4, 'West' , 7 from dual union all
select 5, 'Online' , 3 from dual union all
select 5, 'West' , 3 from dual union all
select 5, 'East' , 3 from dual
)
select id,
max(credit_hr) keep (dense_rank first
order by case when campus in ('MIC', 'Warrens') then 0 end)
as max_hr
from sample_data
group by id
order by id
;
ID MAX_HR
----- ------------------
1 4
2 5
3 3
4 3
5 3
You can also modify the query (add more columns) to show whether the max was from a main campus (that is, if that ID had ANY credit hours from one of the major campuses), and/or to show which campus had the max hours for that ID (or one of the campuses, if there was a tie for most hours).

Oracle Sort by column value

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

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

SQL - Find unique min value and associated columns

I have a set of data as below, showing the history of who has done what with a record. The unique identifier for each record is shown in 'ID' and 'Rec No' is the sequential number assigned to each interaction with the record.
ID Rec No Who Type
1 1 Bob New
1 2 Bob Open
1 3 Bob Assign
1 4 Sarah Add
1 5 Bob Add
1 6 Bob Close
2 1 John New
2 2 John Open
2 3 John Assign
2 4 Bob Assign
2 5 Sarah Add
2 6 Sarah Close
3 1 Sarah New
3 2 Sarah Open
3 3 Sarah Assign
3 4 Sarah Close
I need to find all of the 'Assign' operations. However where multiple 'Assign' are in a certain ID, I want to find the first one. I then also want to find the name of the person who did that.
So ultimately from the above date I would like the output to be-
Who Count (assign)
Bob 1
John 1
Sarah 1
The code I have at the moment is-
SELECT IH.WHO, Count(IH.ID)
FROM Table.INCIDENTS_H IH
WHERE (IH.TYPE = Assign)
GROUP BY IH.WHO
But this gives the output as-
Who Count (assign)
Bob 2
John 1
Sarah 1
As it is finding that Bob did an assign on ID 2, Rec No 4.
Any help would be appreciated. I am using MS SQL.
I think something like this is what you are after:
select
who, count(id)
from (
select ID, Who, row_number() over (partition by ID order by Rec) [rownum]
from Table.INCIDENTS_H IH
WHERE (IH.TYPE = Assign)
) a
where rownum = 1
group by who
This should count only the first Assign (ordered by Rec) within each ID group.
This ought to do it:
SELECT IH.WHO, COUNT(IH.ID)
FROM INCIDENTS_H IH
JOIN (
SELECT ID, MIN([Rec No]) [Rec No]
FROM INCIDENTS_H
WHERE ([Type] = 'Assign')
GROUP BY ID
) IH2
ON IH2.ID = IH.ID AND IH2.[Rec No] = IH.[Rec No]
GROUP BY IH.WHO
You can use row_number to accomplish this
WITH INCIDENTS_H as (
SELECT
1 as ID, 1 as RecNo, 'Bob' as Who, 'New' as type
UNION ALL SELECT 1, 2, 'Bob','Open'
UNION ALL SELECT 1, 3, 'Bob','Assign'
UNION ALL SELECT 1, 4, 'Sarah','Add'
UNION ALL SELECT 1, 5, 'Bob','Add'
UNION ALL SELECT 1, 6, 'Bob','Close'
UNION ALL SELECT 2, 1, 'John','New'
UNION ALL SELECT 2, 2, 'John','Open'
UNION ALL SELECT 2, 3, 'John','Assign'
UNION ALL SELECT 2, 4, 'Bob','Assign'
UNION ALL SELECT 2, 5, 'Sarah','Add'
UNION ALL SELECT 2, 6, 'Sarah','Close'
UNION ALL SELECT 3, 1, 'Sarah','New'
UNION ALL SELECT 3, 2, 'Sarah','Open'
UNION ALL SELECT 3, 3, 'Sarah','Assign'
UNION ALL SELECT 3, 4, 'Sarah','Close')
, GetTheMin AS (
SELECT
ROW_NUMBER() over (partition by id order by recno) row,
ID,
RecNo,
Who,
type
FROM
INCIDENTS_H
WHERE
type = 'Assign'
)
SELECT Who,
COUNT(ID)
FROM GetTheMin
WHERE
row = 1
GROUP BY
who
OR you can use CROSS Apply
SELECT
who,
COUNT(id) id
FROM
(SELECT DISTINCT
MinValues.*
FROM
INCIDENTS_H h
CROSS APPLY ( SELECT TOP 1 *
FROM INCIDENTS_H h2
WHERE h.id = h2.id
ORDER BY ID, RecNo asc) MinValues) getTheMin
GROUP BY WHO
Or you can use Min which uses standard SQL John Fisher's answer demonstrates
Here's a view of everything in the table which should match your "first assign" requirement:
select a.*
from Table.INCIDENTS_H a
inner join
(select ID, min([Rec No]) [Rec No] from Table.INCIDENTS_H where Type = 'Assign' group by ID) b
on a.ID = b.ID and a.[Rec No] = b.[Rec No]
Result:
ID Rec No Who Type
1 3 Bob Assign
2 3 John Assign
3 3 Sarah Assign
select * from
(select
id, rec_no, who
from
operation_history
where
type = 'Assign'
order by rec_no asc) table_alias
group by
id
order by id asc
Tested and here are the results:
id rec_no who
1 3 Bob
2 3 John
3 3 Sarah
(Code not specific to SQL Server)
Here is the query with virtual test data that were mentioned in the original post:
with T (ID, RecNo, Who, Type) as
(
select 1, 1, 'Bob', 'New' union all
select 1, 2, 'Bob', 'open' union all
select 1, 3, 'Bob', 'Assign' union all
select 1, 4, 'Sarah', 'Add' union all
select 1, 5, 'Bob', 'Add' union all
select 1, 6, 'Bob', 'Close' union all
select 2, 1, 'John', 'New' union all
select 2, 2, 'John', 'Open' union all
select 2, 3, 'John', 'Assign' union all
select 2, 4, 'Bob', 'Assign' union all
select 2, 5, 'Sarah', 'Add' union all
select 2, 6, 'Sarah', 'Close' union all
select 3, 1, 'Sarah', 'New' union all
select 3, 2, 'Sarah', 'Open' union all
select 3, 3, 'Sarah', 'Assign' union all
select 3, 4, 'Sarah', 'Close'
)
select top 1 with ties *
from T
where Type = 'Assign'
order by row_number() over(partition by ID order by RecNo)
The "select" statement that can be applied to the real situation from the question might look like:
SELECT TOP 1 WITH TIES
IH.ID, IH.[Rec No], IH.WHO, IH.TYPE
FROM Table.INCIDENTS_H IH
WHERE IH.TYPE = 'Assign'
ORDER BY ROW_NUMBER() OVER(PARTITION BY IH.ID ORDER BY IH.[Rec No]);