How to do a pivot on the oracle database with field type varchar2 - oracle

I am newbie to Oracle but have worked on mysql previously. We have migrated the database from mysql to oracle and want help in this scenario.
I have table in the oracle 10g which is in the following format:
student_id student_key student_value
---------- ----------- -------------
1 name john
2 name bill
1 age 28
2 age 26
2 result pass
now i want to create a pivot on it so i use the following query:
select student_id, decode(student_key, 'name', student_Value, null) as studentName
from student_table
the output comes as
student_id studentName
---------- -----------
1 john
1 null
2 bill
2 null
2 null

Oracle 11 has specialized PIVOT functionality (a nice introduction is here), but 10g doesn't. Here's how to do it in 10g:
SELECT
student_id,
MAX(CASE WHEN student_key = 'name' THEN student_value END) AS StudentName,
MAX(CASE WHEN student_key = 'age' THEN student_value END) AS Age,
MAX(CASE WHEN student_key = 'result' THEN student_value END) AS Result
FROM myTable
GROUP BY student_id
As with the Oracle PIVOT command, you need to know the number of output columns ahead of time.
Also note that MAX is used because we're rolling rows up to columns, which means we have to group, which means we need an aggregate function, so why not MAX? If you use MIN instead of MAX you'll get the same results.

Related

How to filter and retrieve the results after a Specific characters from stored procedure in oracle plsql?

I have a column "Names" in the "Employee" table that has following values. The values either contain only single name (first, last, username) or Multiple names separated with semicolon (;). I need to search the values from that table either by first name or last name or username.
I have created a procedure but it is fetching only 1st,4th,5th records. Please let me know how to retrieve 2nd and 3rd records as well.
Firstname and lastname can be given by user with minimum of 2 characters length.
Username is given entire.
Employee:
ID Name Title
1 Andrea Warbutton (awr01) Manager
2 Claire Taylor (cta02);Mark Kites (mak03);Anitha Rooney (anr06) HOD;Supervisor;Business
3 Dave Rites (dar12);Jessica Simpson (jesi10) Lead;Analyst
4 Nick Ken (nik56) Product (Local,Regional)
5 Claire Pilkington (cpt09) Sales Owner
Code:
Create or replace empl (pm_firstname varchar2(100),
pm_lastname varchar2(100),
pm_username varchar2(100))
BEGIN
Select * from Employee
where Upper(Name) like Upper(pm_firstname ||'%'||) -- this will fetch 1st,4th,5th record
OR Upper(SUBSTR(Name, INSTR(Name),' '+1)) like Upper(pm_lastname ||'%'||) -- this will fetch 1st,4th,5th record
OR upper(REGEXP_SUBSTR(Name,'\((.+)\)',1,1,NULL,1)) = Upper(pm_username); -- -- this will fetch 1st,4th,5th record
END;
End empl ;
Please let me know how to retrieve 2nd and 3rd records as well.
Desired Output:
When searched with firstname = "Andrea", the output is below
ID Name Title
1 Andrea Warbutton (awr01) Manager
When searched with firstname = "Claire", the output is below
ID Name Title
2 Claire Taylor (cta02) HOD
5 Claire Pilkington (cpt09) Sales Owner
When searched with lastname = "Simps", the output is below
ID Name Title
3 Jessica Simpson (jesi10) Analyst
When searched with username = "mak03", the output is below
ID Name Title
2 Mark Kites (mak03) Supervisor
When searched with username = "nik56", the output is below
ID Name Title
4 Nick Ken (nik56) Product (Local,Regional)
with
x as (select id, name, '"'||replace(name, ';', '","')||'"' xml from employee),
n as (select id, name, column_value as cv from x, xmltable(xml))
select id,
trim(regexp_substr(cv, '(\S*)(\s)')) fname,
trim(regexp_substr(cv, '(\S*)(\s)', 1, 2)) lname,
regexp_substr(cv, '\((.+)\)', 1, 1, NULL, 1) uname
from n
Your task would be much easier if you normalize these data. Above query outputs:
ID FNAME LNAME UNAME
1 Andrea Warbutton awr01
2 Claire Taylor cta02
2 Mark Kites mak03
2 Anitha Rooney anr06
3 Dave Rites dar12
3 Jessica Simpson jesi10
4 Nick Ken nik56
5 Claire Pilkington cpt09
demo
Now you can search first, last, usernames however you want. First expression finds first word, then second and word between brackets.
Edit:
I posted the table structure with just ID and Name columns. However, I
have Titles column also in the same format separated with (semicolon).
In this case, How can I Normalize Titles as well along with Names
This query worked for provided examples:
with
x as (select id, name, '"'||replace(name, ';', '","')||'"' xmln,
'"'||replace(title, ';', '","')||'"' xmlt
from employee),
n1 as (select id, trim(xn.column_value) nm, rownum rn from x, xmltable(xmln) xn),
n2 as (select id, trim(xt.column_value) tt, rownum rn from x, xmltable(xmlt) xt)
select id, trim(regexp_substr(nm, '(\S*)(\s)')) fname,
trim(regexp_substr(nm, '(\S*)(\s)', 1, 2)) lname,
regexp_substr(nm, '\((.+)\)', 1, 1, NULL, 1) uname,
tt title
from n1 join n2 using (id, rn)
dbfiddle demo
Be careful however, because we cannot write ideal query. If you have entries like Benicio Del Toro, Mary Jo Catlett, Jean Claude Van Damme, it's impossible to write correct regexp. Sometimes second word is part of lastname, sometimes it is firstname, middlename etc.
The proper way is to modify table structure, divide rows, check results and put correct values in correct name columns. Now you have lists which are hard to search and every method may return wrong results.
No need for PL/SQL.
SQL> with temp as
2 (select id,
3 regexp_substr(name, '[^;]+', 1, column_value) name
4 from employee cross join
5 table(cast(multiset(select level from dual
6 connect by level <= regexp_count(name, ';') + 1
7 ) as sys.odcinumberlist))
8 )
9 select id, name
10 from temp
11 where instr(name, '&search_for_name') > 0;
Enter value for search_for_name: Claire
ID NAME
---------- ------------------------------
2 Claire Taylor (cta02)
5 Claire Pilkington (cpt09)
SQL> /
Enter value for search_for_name: mak03
ID NAME
---------- ------------------------------
2 Mark Kites (mak03)
SQL>
What does it do?
temp CTE splits semi-colon separated values into rows
final query uses a simple instr function which detects whether "rows" (extracted previously) contain value you're looking for
If it must be a function, that code can be reused. As you didn't say what exactly (which datatype, I mean) you want to return, I returned a string.
SQL> create or replace function f_search (par_what in varchar2)
2 return sys.odcivarchar2list
3 is
4 retval sys.odcivarchar2list;
5 begin
6 with temp as
7 (select id,
8 regexp_substr(name, '[^;]+', 1, column_value) name
9 from employee cross join
10 table(cast(multiset(select level from dual
11 connect by level <= regexp_count(name, ';') + 1
12 ) as sys.odcinumberlist))
13 )
14 select id ||' - '|| name
15 bulk collect into retval
16 from temp
17 where instr(name, par_what) > 0;
18
19 return retval;
20 end;
21 /
Function created.
SQL> select * from table(f_search('Andrea'));
COLUMN_VALUE
--------------------------------------------------------------------------------
1 - Andrea Warbutton (awr01)
SQL> select * from table(f_search('Claire'));
COLUMN_VALUE
--------------------------------------------------------------------------------
2 - Claire Taylor (cta02)
5 - Claire Pilkington (cpt09)
SQL>

How to get 'IS NULL' where clause values if initial condition returns none in oracle

I have a table with values as follows
![ID S_ID NAME
1 James
2 2455 Patrick
3 2566 Sam
4 25988 Rick]1
My requirement is to get the NAME based on S_ID and if the given S_ID is not in the table then get the NAME with no S_ID
EX:
SELECT NAME FROM STUDENTS WHERE S_ID=2455
The result would be Patrick
SELECT NAME FROM STUDENTS WHERE S_ID=2411
The result should be James
I have tried
select NAME from STUDENTS where S_ID=2455 or S_ID IS NULL
It gives be bot James and Patrick
I would really appreciate any help
Here's one option:
SQL> set ver off
SQL> with students (id, s_id, name) as
2 (select 2, 2389, 'Patrick' from dual union all
3 select 1, null, 'James' from dual union all
4 select 3, 2566, 'Sam' from dual
5 )
6 select name from students where s_id = &&par_s_id
7 and exists (select null from students where s_id = &&par_s_id)
8 union all
9 select name from students where s_id is null
10 and not exists (select null from students where s_id = &&par_s_id);
Enter value for par_s_id: 2389
NAME
-------
Patrick --> because S_ID = 2389 exists in a table
SQL> undefine par_s_id
SQL> /
Enter value for par_s_id: 123456
NAME
-------
James --> because S_ID = 123456 doesn't exist
SQL>
As you already have the table, you'd use code from line #6 onward.
#Joe,
As per my understanding if S_ID is found in the STUDENT table it should return the NAME of the student, if S_ID is not found the query should return a message as 'No #S_ID'.
Please let me know if I am making any mistake in understanding your question !
[Solution]
Its a Tsql so we can use it as is in the sql or in any language. And note I have tested it in MSSQL but the approach remains the same.
declare #sidInput int
set #sidInput = 2455
if exists (select NAME from STUDENTS where S_ID = #sidInput)
begin
select NAME from STUDENTS where S_ID = #sidInput
end
else
begin
declare #outputMsg varchar(20)
set #outputMsg = 'No ' + convert( varchar(10), #sidInput)
select top 1 #outputMsg as NAME from STUDENTS where S_ID not in ( #sidInput )
end
[Test Cases]
1) For Input #sidInput = 2455
Output - NAME as Patrick
2) For Input #sidInput = 2411
Output - NAME as James
3) For Input #sidInput = 2222 #S_ID value not found in STUDENT table
Output - NAME as No 2222
Let me know if it works for you.
Regards,
Arnab
#Joe,
try this t-sql (and pardon me I was using maria db - so used it ) . basically it remains the same as for my above response its just that I have updated the else block. ( Note - If you are using MySQL can simply use this as it for testing )
DROP PROCEDURE IF EXISTS prod_studentName;
DELIMITER $$
CREATE PROCEDURE`prod_studentName`()`
BEGIN
declare sidValue int;
set sidValue = 2389;
if exists (select NAME from STUDENT where S_ID = sidValue )
then
select NAME from STUDENT where S_ID = sidValue;
else
select NAME from STUDENT where S_ID is NULL;
end if;`
END$$
DELIMITER ;
refer to the attached image for the table inputs -
test outcomes
positive test - lookup for s_id = 2389
outcome -
negative test - lookup for s_id = 7777 this id doesn't even exist at all
outcome -
hope this helps you

Okay so I am trying to a rownum into a variable but I need it to give me only one value, so 2 if it's the second number in the row

select rownum into v_rownum
from waitlist
where p_callnum=callnum
order by sysdate;
tried doing this but gives too many values.
and if I do p_snum=snum, it will keep returning 1. I need it to return 2 if it's #2 on the waitlist.
select rn into v_rownum
from (select callnum,
row_number() over (order by sysdate) rn
from waitlist)
where p_snum=snum;
Almost got it to work. Running into issues in the first select. I believe I might have to use v_count instead. Also Ordering by Sysdate even if a second apart will order it correctly.
SNU CALLNUM TIME
--- ---------- ---------
101 10125 11-DEC-18
103 10125 11-DEC-18
BTW time is = date which I entered people into waitlist using sysdate. So I suppose ordering by time could work.
create table waitlist(
snum varchar2(3),
callnum number(8),
time date,
constraint fk_waitlist_snum foreign key(snum) references students(snum),
constraint fk_waitlist_callnum foreign key(callnum) references schclasses(callnum),
primary key(snum,callnum)
);
is the waitlist table.
I used Scott's DEPT table to create your WAITLIST; department numbers represent CALLNUM column:
SQL> select * From waitlist;
CALLNUM WAITER
---------- --------------------
10 ACCOUNTING
20 RESEARCH
30 SALES
40 OPERATIONS
How to fetch data you need?
using analytic function (ROW_NUMBER) which orders values by CALLNUMs, you'll know the order
that query will be used as an inline view for the main query that returns number in the waitlist for any CALLNUM
Here's how:
SQL> select rn
2 from (select callnum,
3 row_number() over (order by callnum) rn
4 from waitlist
5 )
6 where callnum = 30;
RN
----------
3
SQL>
rownum in oracle is a generated column, it does not refer to any specific row, it is just the nth row in a set.
With a select into it can only return one row (hence the two many rows error) so rownum will always be 1.
Without more details about your table structure, and how you are uniquely identifying records it is hard to give assist you further with a solution.

Oracle sql Select the first and last name of the customer with most orders in 2017

I have the following tables
f_orders
ORDER_NUMBER NUMBER(5,0)
ORDER_DATE DATE
ORDER_TOTAL NUMBER(8,2)
CUST_ID NUMBER(5,0)
STAFF_ID NUMBER(5,0)
with the following data
ORDER_NUMBER ORDER_DATE ORDER_TOTAL CUST_ID STAFF_ID
5678 10-Dec-2017 103.02 123 12
9999 10-Dec-2017 10 456 19
9997 09-Dec-2017 3 123 19
9989 10-Dec-2016 3 123 19
and
f_customers
ID NUMBER(5,0)
FIRST_NAME VARCHAR2(25)
LAST_NAME VARCHAR2(35)
ADDRESS VARCHAR2(50)
with the following data
ID FIRST_NAME LAST_NAME ADDRESS
123 Cole Bee 123 Main Street
456 Zoe Twee 1009 Oliver Avenue
I'm supposed to display the name of the customer wthi the most orders placed in the year 2017.
My query looks like this
SELECT f_customers.first_name,
f_customers.last_name,
count(order_total)
FROM f_orders JOIN f_customers
ON f_customers.id = f_orders.CUST_ID
WHERE TO_CHAR(order_date, 'DD-Mon-YYYY') LIKE '%2017'
GROUP BY f_customers.first_name, f_customers.last_name
HAVING count(order_total) = (SELECT max(count(cust_id))
FROM f_orders
GROUP BY cust_id)
The problem is that whenever I insert the where statement it returns no data found, even though it should return the name Cole Bee with 2 orders
If I remove the where statement it will show that Cole Bee has placed 3 orders
I can't figure out why I get the no data found result. Any ideas?
Your main query is filtering on the year; the subquery on the right hand side of the having clause is not. The max(count()) is 3 if you run that subquery on its own, and you’re comparing that with the filtered list which (as you expect) only finds 2 rows for that customer.
Run the whole query with just the having part removed (rather than the where clause), and run just the subquery; and compare the results.
The simple answer is to repeat the filter:
SELECT f_customers.first_name,
f_customers.last_name,
count(order_total)
FROM f_orders JOIN f_customers
ON f_customers.id = f_orders.CUST_ID
WHERE TO_CHAR(order_date, 'DD-Mon-YYYY') LIKE '%2017'
GROUP BY f_customers.first_name, f_customers.last_name
HAVING count(order_total) = (SELECT max(count(cust_id))
FROM f_orders
WHERE TO_CHAR(order_date, 'DD-Mon-YYYY') LIKE '%2017'
GROUP BY cust_id)
Both filters could be written more simply as:
WHERE TO_CHAR(order_date, 'YYYY') = '2017'
or even:
WHERE EXTRACT(YEAR FROM order_date) = 2017
You can avoid hitting the table twice using analytic queries and other tricks but as this seems to be an assignment that may be getting beyond what you’ve been taught and are expected to know/use.

How to find records in one table which aren't in another

I'm very new to oracle sql and programming and I need some help with one of my first projects. I'm working with this table schema:
Column Data Type Length Precision Scale Nullable
EMPLOYEE_ID NUMBER 22 6 0 No
START_DATE DATE 7 - - No
END_DATE DATE 7 - - No
JOB_ID VARCHAR2 10 - - No
DEPARTMENT_ID NUMBER 22 4 0 Yes
I want to display all employees who have never changed their jobs, not even once(employees not listed in the above table) This table is labeled job_history. How would I go about doing this? I'm not sure on how to get this started.
select * from employees
where employee_id not in ( select employee_id from job_history)
/
You can use a left join and check for a null employee_id on the job_history table.
select * from employees
left join job_history
on job_history.employee_id = employees.employee_id
where job_history.employee_id is NULL
Fairly often an execution plan is better for
select * from employees e where not exists
(select 1 from job_history jh.employee_id = e.employee_id)
than "not in ()".
And if the tables have the same structure the best result will with
select * from employees
minus
select * from job_history

Resources