How Can I Make Oracle Query Sort Order Dynamic? - oracle

I have a Oracle procedure inside a package like this
PROCEDURE getEmployee
(
pinLanguage IN VARCHAR2,
pinPage IN NUMBER,
pinPageSize IN NUMBER,
pinSortColumn IN VARCHAR2,
pinSortOrder IN VARCHAR2,
poutEmployeeCursor OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN poutEmployeeCursor FOR
SELECT * FROM (
SELECT EMPLOYEE_ID, USERNAME, FULL_NAME, DATE_OF_BIRTH, EMP.GENDER_ID, GEN_TR.GENDER, EMP.WORK_TYPE_ID, WT_TR.WORK_TYPE, SALARY, EMAIL, PROFILE_IMAGE,
ROW_NUMBER() OVER (ORDER BY EMPLOYEE_ID ASC) RN
FROM EMPLOYEES EMP
INNER JOIN GENDERS GEN ON EMP.GENDER_ID = GEN.GENDER_ID
LEFT JOIN GENDERS_MLD GEN_TR ON GEN.GENDER_ID = GEN_TR.GENDER_ID AND GEN_TR.LANGUAGE = pinLanguage
INNER JOIN WORK_TYPES WT ON EMP.WORK_TYPE_ID = WT.WORK_TYPE_ID
LEFT JOIN WORK_TYPES_MLD WT_TR ON WT.WORK_TYPE_ID = WT_TR.WORK_TYPE_ID AND WT_TR.LANGUAGE = pinLanguage
)
WHERE RN BETWEEN (((pinPage - 1) * pinPageSize) + 1) AND (pinPage * pinPageSize);
END;
I need to make the sort order of the above query dynamic
If I pass the text FullName to pinSortColumn parameter, it need to sort FULL_NAME column
If I pass the text DateOfBirth to pinSortColumn parameter, it need to sort DATE_OF_BIRTH column
If I pass the text Gender to pinSortColumn parameter, it need to sort GEN_TR.GENDER column
I can pass the text asc or desc to pinSortOrder parameter and the query need to be sorted accordingly.
Can you please help me to achieve this?

You can use separate order by for asc and desc as following:
ORDER BY
CASE pinSortOrder WHEN 'asc' THEN
CASE pinSortColumn
WHEN 'FullName' THEN FULL_NAME
WHEN 'DateOfBirth' THEN to_char(DATE_OF_BIRTH,'yyyymmddhh24miss')
WHEN 'Gender' THEN GEN_TR.GENDER
END
END,
CASE pinSortOrder WHEN 'desc' THEN
CASE pinSortColumn
WHEN 'FullName' THEN FULL_NAME
WHEN 'DateOfBirth' THEN to_char(DATE_OF_BIRTH,'yyyymmddhh24miss')
WHEN 'Gender' THEN GEN_TR.GENDER
END
END DESC
Let's say you have passed pinSortColumn as FullName and pinSortOrder as asc then order by clause will be ORDER BY FULL_NAME, NULL DESC (please note that default order will be asc so I have not write it in the code. Query will be ordered by FULL_NAME in ascending manner)
Now, If you have passed pinSortColumn as FullName and pinSortOrder as desc then order by clause will be ORDER BY NULL, FULL_NAME DESC.
Null will not impact ordering.
I hope it is clear now.
Cheers!!

Try this one:
WHERE ...
ORDER BY
CASE pinSortColumn
WHEN 'FullName' THEN FULL_NAME
WHEN 'DateOfBirth' THEN DATE_OF_BIRTH
WHEN 'Gender' THEN GEN_TR.GENDER
END;
However, I don't think you can use ASC, DESC is this way. You to create a dynamic query. For an OPEN ... FOR ... statement it is trivial:
sqlstr := 'SELECT * FROM (
SELECT EMPLOYEE_ID, USERNAME, FULL_NAME, DATE_OF_BIRTH, EMP.GENDER_ID, GEN_TR.GENDER, EMP.WORK_TYPE_ID, WT_TR.WORK_TYPE, SALARY, EMAIL, PROFILE_IMAGE,
ROW_NUMBER() OVER (ORDER BY EMPLOYEE_ID ASC) RN
FROM EMPLOYEES EMP
INNER JOIN GENDERS GEN ON EMP.GENDER_ID = GEN.GENDER_ID
LEFT JOIN GENDERS_MLD GEN_TR ON GEN.GENDER_ID = GEN_TR.GENDER_ID AND GEN_TR.LANGUAGE = :pinLanguage
INNER JOIN WORK_TYPES WT ON EMP.WORK_TYPE_ID = WT.WORK_TYPE_ID
LEFT JOIN WORK_TYPES_MLD WT_TR ON WT.WORK_TYPE_ID = WT_TR.WORK_TYPE_ID AND WT_TR.LANGUAGE = :pinLanguage
)
WHERE RN BETWEEN (((:pinPage - 1) * :pinPageSize) + 1) AND (:pinPage * :pinPageSize)
ORDER BY '
CASE pinSortColumn
WHEN 'FullName' THEN sqlstr := sqlstr || 'FULL_NAME ';
WHEN 'DateOfBirth' THEN sqlstr := sqlstr || 'DATE_OF_BIRTH ';
WHEN 'Gender' THEN sqlstr := sqlstr || 'GEN_TR.GENDER ';
END CASE;
sqlstr := sqlstr || pinSortOrder;
OPEN poutEmployeeCursor FOR sqlstr USING pinLanguage, pinLanguage, pinPage, pinPageSize, pinPage, pinPageSize;
Not in case you run Oracle 12c or newer you can use the Row Limiting Clause instead of dealing with row number.

You can avoid all these duplicated cases. Multiply row number by -1 when descending order is required:
order by
case pinSortOrder when 'desc' then -1 else 1 end *
row_number() over (
order by case pinSortColumn when 'FullName' then full_name end,
case pinSortColumn when 'Gender' then gender end,
case pinSortColumn when 'DateOfBirth' then date_of_birth end)
dbfiddle demo
Also this way you do not have to convert data the same type, no need to use to_char.

Related

Function in Oracle that returns 2 different columns of a table

I'm trying to create a function that returns 2 different columns in a single table, can anyone help me with that?
I've already tried this way:
CREATE FUNCTION return_id_grade(subjectId IN NUMBER, semesterYear IN DATE , n IN INT, option IN INT)
RETURN NUMBER
IS studentId NUMBER(5),
IS studentGrade NUMBER(2,1);
BEGIN
SELECT DISTINCT student_id INTO studentId,
grade INTO studentGrade
FROM (SELECT studentId, grade, dense_rank() over (ORDER BY grade desc) rank FROM old_students)
WHERE subject_id = subjectId
AND semester_year = semesterYear
AND rank = n
AND rownum <= 1
CASE
WHEN option = 1 then RETURN(student_id)
WHEN option = 2 then RETURN(grade)
END;
END;
I expected to output the n'NTH grade of an university class and the student Id, but the actual can just output the option received on parameter field.
a.You cant use Select colum1 INTO variable1 , colum2 INTO variable2 . It has to be like :
Select column1 , column2 INTO variable1 , variable 2
b.Create an object type and use it as out parameter in a procedure
c.Have the option condition after the procedure is called.
Sample Code:
CREATE OR REPLACE TYPE ty_obj_idgrade AS OBJECT
(studentId NUMBER(5)
,studentGrade NUMBER(2,1)
);
CREATE OR REPLACE PROCEDURE return_id_grade(
subjectId IN NUMBER,
semesterYear IN DATE ,
n IN INT,
-- options IN INT
,p_idgrade OUT ty_obj_idgrade) IS
BEGIN
SELECT DISTINCT student_id --INTO studentId,
,grade --INTO studentGrade
INTO p_idgrade.studentId
,p_idgrade.grade
FROM (SELECT studentId
,grade
,dense_rank() over (ORDER BY grade desc) rank
,subject_id
,semester_year
FROM old_students )
WHERE subject_id = subjectId
AND semester_year = semesterYear
AND rank = n
AND rownum <= 1;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('we are inside when others -->'||sqlerrm);
END;
Call your procedure.
Since options was used as IN parameter , it should be availabe outside the prc/fnc
So this can be done after the prc/fnc call
If options = 1
THEN
value := p_idgrade.conatct
ELSE
value := p_idgrade.grade
END IF;
Hope it helps.
Did not try compiling it, but something like this should be close.
CREATE FUNCTION return_id_grade(subjectId IN NUMBER, semesterYear IN DATE , n IN INT, option IN INT)
RETURN NUMBER IS
studentId NUMBER(5),
studentGrade NUMBER(2,1);
BEGIN
SELECT DISTINCT student_id, grade
INTO studentId, studentGrade
FROM (SELECT studentId, grade, dense_rank() over (ORDER BY grade desc) rank FROM old_students)
WHERE subject_id = subjectId
AND semester_year = semesterYear
AND rank = n
AND rownum <= 1;
IF option = 1 then
RETURN studentId ;
ELSE
RETURN studentGrade ;
END IF;
END;
END;
However, this function is really not a good design. A function should perform a single task. If you want to return both items, create a PL/SQL record type, and use a stored procedure with an OUT parameter and return that in the procedure.
You can directly try to use OPTION in query as following:
CREATE FUNCTION RETURN_ID_GRADE (
SUBJECTID IN NUMBER,
SEMESTERYEAR IN DATE,
N IN INT,
OPTION IN INT
) RETURN NUMBER IS
LV_RETURN_NUMBER NUMBER(6, 1);
BEGIN
-- QUERY TO FETCH REQUIRED DATA ONLY
SELECT -- DISTINCT -- DISTINCT IS NOT NEEDED AS ROWNUM <= 1 IS USED
CASE
WHEN OPTION = 1 THEN STUDENT_ID
ELSE GRADE
END AS LV_RETURN_NUMBER
INTO LV_RETURN_NUMBER -- STORE VALUE BASED ON OPTION
FROM
(
SELECT
STUDENTID,
GRADE,
DENSE_RANK() OVER(
ORDER BY
GRADE DESC
) RANK
FROM
OLD_STUDENTS
)
WHERE
SUBJECT_ID = SUBJECTID
AND SEMESTER_YEAR = SEMESTERYEAR
AND RANK = N
AND ROWNUM <= 1;
RETURN LV_RETURN_NUMBER; -- RETURN THE VARIABLE
END;
Cheers!!

Oracle Function To Return List

I'm trying to develop a PLSQL function that outputs a list of employee names that I can then run through another script. I can't quite get it right though. I'm fairly new to PLSQL and am primarily used to building functions in Python, so I may be thinking about this the wrong way. At the end of all of this, I'd like to use the output of this within another script I'm writing.
Baseline Script:
SELECT EMPLOYEE FROM (
SELECT ID, EMPLOYEE, ROLE, STARTDATE,
ROW_NUMBER() OVER (PARTITION BY EMPLOYEE ORDER BY STARTDATE DESC, ID DESC) RN
FROM (
SELECT DISTINCT E.EMPLOYEE EMPLOYEE,
E.ID ID,
LR.DESCRIPTION ROLE,
ROLE_START_DATE STARTDATE
FROM EMPLOYEES E
JOIN ROLES R ON E.EMPLOYEE_ID = R.EMPLOYEE_ID
JOIN LU_ROLES LR ON R.ROLE_ID = LR.ROLE_ID
WHERE ROLE_START_DATE <= DATE '2017-12-03'))
WHERE RN = 1
My attempt at writing a PLSQL function:
CREATE FUNCTION get_employees(EMPLOYEE IN VARCHAR2)
RETURN VARCHAR2
IS EMPLOYEE_LIST;
BEGIN
SELECT EMPLOYEE FROM (
SELECT ID, EMPLOYEE, ROLE, STARTDATE,
ROW_NUMBER() OVER (PARTITION BY EMPLOYEE ORDER BY STARTDATE DESC, ID DESC) RN
FROM (
SELECT DISTINCT E.EMPLOYEE EMPLOYEE,
E.ID ID,
LR.DESCRIPTION ROLE,
ROLE_START_DATE STARTDATE
FROM EMPLOYEES E
JOIN ROLES R ON E.EMPLOYEE_ID = R.EMPLOYEE_ID
JOIN LU_ROLES LR ON R.ROLE_ID = LR.ROLE_ID
WHERE ROLE_START_DATE <= DATE '2017-12-03'))
WHERE RN = 1
RETURN EMPLOYEE_LIST
END;
I know I'm missing some syntax, I just don't know what and why...I'm reading through the docs at the moment to try to understand this. Any help you all could provide would be much appreciated!
Thanks!
Without your tables or sample data this has to be a bit of a guess, but a "fixed" version might be something like this:
create or replace type short_string_tt as table of varchar2(100)
/
create or replace function get_employees
( p_role_date_cutoff roles.role_start_date%type )
return short_string_tt
as
l_employee_list short_string_tt;
begin
select employee bulk collect into l_employee_list
from ( select employee
, row_number() over(partition by employee order by role_start_date desc, id desc) rn
from ( select distinct e.employee, e.id, lr.description, role_start_date
from employees e
join roles r
on r.employee_id = e.id
join lu_roles lr
on lr.role_id = r.role_id
where role_start_date <= p_role_date_cutoff )
)
where rn = 1;
return l_employee_list;
end;
/
If the number of rows returned is likely to be significant then you might look at making it a pipelined function, as these stream rows back as they are fetched rather than building the entire collection in memory before returning anything.
try this would it work ?
CREATE FUNCTION get_employees(EMPLOYEE IN VARCHAR2)
RETURN VARCHAR2
IS EMPLOYEE_LIST VARCHAR2(200);
BEGIN
SELECT EMPLOYEE into EMPLOYEE_LIST FROM (
SELECT ID, EMPLOYEE, ROLE, STARTDATE,
ROW_NUMBER() OVER (PARTITION BY EMPLOYEE ORDER BY STARTDATE DESC, ID DESC) RN
FROM (
SELECT DISTINCT EMPLOYEE EMPLOYEE,
E.ID ID,
LR.DESCRIPTION ROLE,
ROLE_START_DATE STARTDATE
FROM EMPLOYEES E
JOIN ROLES R ON E.EMPLOYEE_ID = R.EMPLOYEE_ID
JOIN LU_ROLES LR ON R.ROLE_ID = LR.ROLE_ID
WHERE ROLE_START_DATE <= DATE '2017-12-03'))
WHERE RN = 1;
RETURN EMPLOYEE_LIST;
END;

show the schema of a table in oracle

The following mysql query returns the constraints and the default values along with column_name, is_null and other details -
mysql query - select TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, COLUMN_TYPE, COLUMN_KEY, EXTRA from INFORMATION_SCHEMA.COLUMNS where TABLE_SCHEMA = 'DB_NAME'
I want to write a similar query in Oracle, the following query returns data_type and is_null but doesn't return constraints and default values -
Oracle query - SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, NULLABLE FROM DBA_TAB_COLUMNS where owner = 'USERNAME'
How can I extract those information from an oracle table.
Note: I don't want to use describe table
Select tc.TABLE_NAME, tc.COLUMN_NAME, tc.DATA_TYPE, tc.NULLABLE, tc.DATA_DEFAULT,
con.cons
from DBA_TAB_COLUMNS tc
left join
( select listagg( cc.constraint_name, ',') within group (order by cc.constraint_name) cons,
table_name, owner , column_name
from DBA_CONS_COLUMNS cc
group by table_name, owner , column_name ) con
on con.table_name = tc.table_name and
con.owner = tc.owner and
con.column_name = tc.column_name
where tc.owner = 'USERNAME'
order by 1 ,2
There can be multiple constraints (or none) for each column. Because of that left join is used and listagg function to display all constraint in one column.
TABLE_NAME COLUMN_NAME DATA_TYPE NULLABLE DATA_DEFAULT CONS
AQ$_QUEUE_TABLES OBJNO NUMBER N AQ$_QUEUE_TABLES_PRIMARY,SYS_C001643
AQ$_QUEUE_TABLES SCHEMA VARCHAR2 N SYS_C001640
AQ$_QUEUE_TABLES SORT_COLS NUMBER N SYS_C001645
AQ$_QUEUE_TABLES TABLE_COMMENT VARCHAR2 Y
AQ$_QUEUE_TABLES TIMEZONE VARCHAR2 Y
You can still try to use this below snippet to get the requested details. Hope this helps.
Note : This is for indexed columns as I thought you might need this too
SELECT DISTINCT col.owner,
col.table_name,
col.DATA_TYPE,
Col.Column_Name,
DECODE(nullable,'Y','Yes','N','No') nullable,
high_value(col.table_name,col.column_name), -- This is own created function to deal with LONG datatype columns
Ind.Index_Name
FROM SYS.All_Tab_Cols col,
All_Ind_Columns ind
WHERE Col.Table_Name = Ind.Table_Name
AND Col.Column_Name = Ind.Column_Name(+)
AND Col.Table_Name = UPPER('<TABLE_NAME>')
AND Col.Owner = '<SCHEMA_NAME>';

Quering all columns of single table in where condition for same input data

If we want to fetch information based on condition on single column, we do like this
SELECT * FROM contact WHERE firstName = 'james'
IF we want to put conditions on multiple columns, we do this
SELECT * FROM contact WHERE firstName = 'james' OR lastName = 'james' OR businessName = 'james'
But What if we have more than 50 columns.
Is there better way other than WHERE Condition with OR Keyword?
The approach should not involve writing all column names.
There is a way to do this in MySql as shown here.
If you're wanting to search all VARCHAR2 columns, then the following script ought to help:
set pages 0;
set lines 200
select case when rn = 1 and rn_desc = 1 then 'select * from '||table_name||' where '||column_name||' = ''james'';'
when rn = 1 then 'select * from '||table_name||' where '||column_name||' = ''james'''
when rn_desc = 1 then ' and '||column_name||' = ''james'';'
else ' and '||column_name||' = ''james'''
end sql_stmt
from (select table_name,
column_name,
column_id,
row_number() over (partition by table_name order by column_id) rn,
row_number() over (partition by table_name order by column_id desc) rn_desc
from user_tab_columns
where data_type in ('VARCHAR2')
-- and table_name in (<list of tables>) -- uncomment and amend as appropriate!
)
order by table_name, column_id;
If you only want to search specific tables, you would have to put a filter in for the table_names you're after.
Running the above as a script will give you a script containing multiple queries that you can then run
There is no way you can avoid writing all the column names,
But you can use an IN condition to make writing this a bit shorter:
SELECT *
FROM contact
WHERE 'james' in (firstName, lastName, businessName)

PLSQL select statement with variable help

Hello i am trying to do this simple statement but i want to add a variable that comes from a select. Here is what i have.
userEmail varChar(50) := SELECT user_id FROM users WHERE email = 'email#email.com';
SELECT *
FROM iphone_alerts
WHERE user_id = userEmail
AND date_added = (SELECT MAX(date_added) FROM iphone_alerts WHERE user_id = userEmail
Do i need to use something along the lines of Declare and Begins? I am new to the sql stuff and am having trouble finding answers.
You need:
declare
userEmail varchar2(200);
begin
select user_id into userEmail
from users
where email = 'email#email.com';
-- you will need a cursor to itare these results
select *
from iphone_alerts
where user_id = userEmail
and date_added = (select max(date_added) from iphone_alerts WHERE user_id);
end;
Edit after comment:
If select should return only ONE row, you don't need a cursor, but you need an into clause to store each retrieved value into a variable. Something like:
declare
userEmail varchar2(200);
v_field1 number;
v_field2 date;
v_field3 varchar2(200);
begin
select user_id into userEmail
from users
where email = 'email#email.com';
-- you will need a cursor to itare these results
select field1, field2, field3
into v_field1, v_field2, v_field3
from iphone_alerts
where user_id = userEmail
and date_added = (select max(date_added) from iphone_alerts WHERE user_id);
end;
If you want to do this in SQL, you'd want something like
SELECT alerts.*
FROM iphone_alerts alerts,
users
WHERE alerts.user_id = users.user_id
AND users.email = 'email#email.com'
AND alerts.date_added = (SELECT MAX(date_added)
FROM iphone_alerts alerts2
WHERE alerts2.user_id = user.user_id)
Probably more efficient would be something like this that lets us hit the IPHONE_ALERTS table just once.
SELECT <<list of columns in IPHONE_ALERTS>>
FROM (
SELECT alerts.*,
RANK() OVER (PARTITION BY alerts.user_id ORDER BY date_added DESC) rnk
FROM iphone_alerts alerts,
users
WHERE alerts.user_id = users.user_id
AND users.email = 'email#email.com'
)
WHERE rnk = 1
I'm not sure what you're trying to do, but I think you might need select into
declare
V_userID varChar(50) ;
begin
/*This will store the value of the user_id field into the v_userID variable.*/
SELECT user_id into v_userID FROM users WHERE email = 'email#email.com';
end;
http://download.oracle.com/docs/cd/B13789_01/appdev.101/b10807/13_elems045.htm

Resources