I'm writing a stored procedure for paginated results and this result can be ordered by certain values. I did have a switch case in a select statement but because it was trying to do an orderby on rownum it was very slow.
Now I am trying to use dyanmic sql to build the query outside the select but I don't know if what I am doing is possible.
Here is my SQL in Oracle SQL Developer:
create or replace PROCEDURE Sp_tsa_trainees_pagination (
schemeid IN INT,
searchval IN VARCHAR2,
pagesize IN INT DEFAULT 20,
currentpage IN INT DEFAULT 1,
--orderby IN VARCHAR2,
cursor_ OUT SYS_REFCURSOR)
AS
-- LOCAL VARIABLES
totalcount INT;
numberofpages INT;
startposition NUMBER;
endposition NUMBER;
orderby VARCHAR2(100) := 'surname asc' ;
dynamic_query VARCHAR(255) := 'row_number() over (order by t.SURNAME DESC, t.FORENAMES DESC) AS rnum';
BEGIN
-- Get total number of trainees in scheme
select COUNT(t.ORG_REGISTRATION_ID)
into totalcount FROM v_trainee t
where t.ORG_REGISTRATION_ID = schemeid
AND t.status = 'A' and LOWER(t.trainee_name) like '%' || LOWER(searchval) || '%';
-- calculate number of pages in the pagination by dividing total number of records by how many to display for each page
numberofpages := totalcount / pagesize;
-- get start position by multiplying number of records to display for each page by current page
startposition := pagesize *( currentpage-1);
-- add calculated start position by number of records to display to get end position
endposition := startposition + pagesize;
CASE orderby
WHEN 'surname desc' THEN dynamic_query := 'row_number() over (order by t.SURNAME DESC, t.FORENAMES DESC) AS rnum';
WHEN 'surname asc' THEN dynamic_query := 'row_number() over (order by t.SURNAME ASC, t.FORENAMES ASC) AS rnum';
END CASE;
OPEN cursor_ FOR
Select * from
(
SELECT
-- order by based on selection
dynamic_query rnum,
t.ORG_REGISTRATION_ID SearchId,
t.FORENAMES Forenames,
t.FORENAME Forename,
t.SURNAME Surname,
t.person_id PersonId,
t.trainee_name TraineeName,
t.STATUS Status,
t.IPD_ANNUAL_REVIEW_DATE AnnualReviewDate,
t.ANNUAL_REVIEW_STATUS AnnualReviewStatus,
t.payment_received PaymentRecieved,
t.TRAINEE_ID TraineeId,
t.IPD_SIGNUP_DATE IpdSignupDate,
t.START_DATE StartDate,
t.END_DATE EndDate,
t.LENGTH_ON_SCHEME LengthOnScheme,
t.EMPLOYEE_NUMBER EmploymentNumber,
t.SELECTED_LEVEL SelectedLevel,
t.SELECTED_LEVEL_DESCRIPTION SelectedLevelDescription,
t.ELIGIBLE_LEVEL EligibleLevel,
t.ELIGIBLE_LEVEL_DESCRIPTION EligibleLevelDescription,
sce.FORENAMES SceForenames,
sce.FORENAME SceForename,
sce.SURNAME SceSurname,
sce.mentor_name SceName,
sce.EMPLOYEE_NUMBER SceEmployeeNumber,
de.FORENAMES DeForenames,
de.FORENAME DeForename,
de.SURNAME DeSurname,
de.mentor_name DeName,
de.EMPLOYEE_NUMBER DeEmployeeNumber,
t.COMPLETED_ATTRIBUTE_LEVELS CompletedAttributeLevels,
t.ATTRIBUTE_LEVEL_COUNT AttributeLevelCount,
-- get percentage
CASE t.ATTRIBUTE_LEVEL_COUNT
WHEN 0 THEN 0
ELSE
COMPLETED_ATTRIBUTE_LEVELS / t.ATTRIBUTE_LEVEL_COUNT * 100
END percentage,
DECODE(F_ISTRAINEEGROUPMEMBER(t.ORG_REGISTRATION_ID, 'S', t.person_id),'Y','N','Y') WithoutTsaGroup,
orr.status SchemeStatus,
(select count(*) from TRAINING_GROUP_TRAINEE tgt where tgt.trainee_id = t.TRAINEE_ID) NUMBER_OF_GROUPS,
TotalCount
FROM v_trainee t
INNER JOIN org_registration orr ON t.ORG_REGISTRATION_ID = orr.id
LEFT OUTER JOIN v_mentor sce ON t.sce_id = sce.MENTOR_ID
LEFT OUTER JOIN v_mentor de ON t.de_id = de.MENTOR_ID
where t.ORG_REGISTRATION_ID = schemeid AND t.status = 'A'
and LOWER(t.trainee_name) like '%' || LOWER(searchval) || '%'
)
where rnum >= startposition and rnum <= endposition;
END;
I want to use this variable with the assigned sql:
dynamic_query rnum,
But when I execute the stored procedure I get this error:
ORA-01722: invalid number ORA-06512: at
"db.SP_TSA_TRAINEES_PAGINATION", line 46 ORA-06512: at line 13
So basically my question is can I assign a SQL to VARCHAR2 and then use it in a select statement dynamically.
You may need dynamic SQL for this. For example:
create or replace procedure testDyn(n in number, C OUT SYS_REFCURSOR) is
vDynamicPart varchar2(1000);
vSQl varchar2(1000);
begin
--
if (n = 1) then
vDynamicPart := 'count(1)';
else
vDynamicPart := 'count(null)';
end if;
--
vSQl := 'select ' || vDynamicPart || ' from dual';
open C for vSQl;
end;
If you call it
declare
n1 number;
n2 number;
C1 SYS_REFCURSOR;
C2 SYS_REFCURSOR;
begin
testDyn(1, C1);
testDyn(2, C2);
fetch C1 into n1;
fetch C2 into n2;
dbms_output.put_line('n1: ' || n1);
dbms_output.put_line('n2: ' || n2);
end;
you get:
n1: 1
n2: 0
Related
I am creating a function in Oracle but it's not getting compiled and I am getting two errors
Error 1.
Error: PL/SQL: Compilation unit analysis terminated
Error 2.
Error(2,16): PLS-00201: identifier 'DIV_DUR_PRICE_TABLE' must be
declared
HERE IS THE CODE
CREATE OR REPLACE FUNCTION DIV_DAR(FID IN VARCHAR, DATE1 IN DATE, DATE2 IN DATE)
RETURN DIV_DUR_PRICE_TABLE PARALLEL_ENABLE AS
PRAGMA AUTONOMOUS_TRANSACTION;
CNT NUMBER;
V_RET DIV_DUR_PRICE_TABLE;
BEGIN;
EXECUTE IMMEDIATE 'DELETE from GTT_DIV_DUR_PRICE_TABLE';
Insert Into GTT_DIV_DUR_PRICE_TABLE
(select mydate, column1, column2, 0 as Final_value from tablename)
-- please refer my previous question to understand the code written ahead
declare
v_num integer := 1;
v_column1 number(8,2);
v_column2 number(8,2);
v_Final_value number(8,2);
begin
for rec in (select * from GTT_DIV_DUR_PRICE_TABLE order by mydate)
loop
if(v_num = 1) then
update tab set Final_value = column1 where mydate = rec.mydate;
else
if(rec.column2 is not null) then
update tab set Final_value =
v_Final_value * (v_column1/rec.column1) +
rec.column2 * (v_column1/v_Final_value) where mydate = rec.mydate;
else
update tab set Final_value =
v_Final_value * (rec.column1 / v_column1) where mydate = rec.mydate;
end if;
end if;
v_num:= v_num +1;
v_column1 := rec.column1;
v_column2 := rec.column2;
select final_value into v_Final_value from GTT_DIV_DUR_PRICE_TABLE
where mydate = rec.mydate;
end loop;
end;
SELECT
CAST(
MULTISET(
SELECT * FROM GTT_DIV_DUR_PRICE_TABLE order by P_DATE desc
)AS DIV_DUR_PRICE_TABLE
) INTO V_RET FROM DUAL;
COMMIT;
RETURN V_RET;
END DIV_DAR;
Here , I want to assign all the value of router_index in the same block with the first row of index value in this block.
E.g. The router_index value from Row 1 to 4 should be 5,383 and from 5 to 7 should be 2,703...
I wonder if I can do it with pure PostgreSQL ?
Thanks for your help!!!!
do $$
declare
cnt integer := 0;
head_index_id varchar := '0';
cur_index_id varchar := '0';
fixed_index varchar := '10';
cnt_limit integer;
begin
select max(per_id) from detailed_router into cnt_limit;
while cnt <= cnt_limit loop
select router_index from detailed_router where per_id = cnt into cur_index_id;
if head_index_id = '0' and cur_index_id != fixed_index then
head_index_id := cur_index_id;
else
if cur_index_id = fixed_index then
-- update
update detailed_router set router_index = head_index_id where per_id = cnt;
else
select router_index from detailed_router where per_id = cnt into head_index_id;
end if;
end if;
raise notice 'cnt %', cnt;
raise notice 'cur_index_id %', cur_index_id;
raise notice 'head_index_id %', head_index_id;
cnt := cnt + 1;
end loop;
end $$
If you want "10" values to be filled with the previous non-"10" value and you have a column that specifies the ordering, you can use window functions:
select t.*,
max(router_index) over (partition by driver_index_code, grp) as imputed_router_index
from (select t.*,
count(*) filter (where router_index > 10) over (partition by driver_index_code order by <ordering column>) as grp
from t
) t;
I have this table informationvalues with the contents:
Now I create a procedure where I need to input a date parameter which should output line the correct attr with given price. If the date doesn't exist the latest date should be selected.
The solution table for to_date('01-jan-19') would look like this:
This would be then output line in the procedure.
Should I select to correct tuple and output line it or would it be best to just bulk collect everything and then check in a for loop with an if statement what tuple I need to display.
What I have so far:
A select statement with the tuples I am looking for:
create or replace procedure print_inf_value(closingDate Date) is
cursor d1 (closingDate Date) is
select t.attr, t.dateOfValue, t.price
from (
select i.*,
row_number() over (
partition by attr
order by case when dateOfValue = closingdate then 1 else 2 end, dateOfValue desc
) rn
from InformationValues i
) t
where t.rn = 1;
BEGIN
dbms_output.put_line('Information Value ');
dbms_output.put_line('--------------------------------');
FOR d1_rec IN d1 LOOP
dbms_output.put_line(d1_rec.attr || ' ' || d1_rec.price );
END LOOP;
END;
Or a procedure where I bulk collect everything and then I need to sort out what tuple I need:
create or replace procedure print_inf_value(closingDate Date) is
TYPE d1 IS TABLE OF informationvalues%rowtype;
emps d1;
begin select * bulk collect into emps
from informationvalues;
FOR i IN 1 .. emps.COUNT LOOP
if emps(i).dateofvalue = closingDate then
dbms_output.put_line(emps(i).attr || ' ' || emps(i).price );
/*else*/
end if;
END LOOP;
END;
Both are not working right, so what am I missing to display tuple with the correct date.
Please try:
CREATE OR REPLACE PROCEDURE print_inf_value (closingDate DATE)
IS
BEGIN
DBMS_OUTPUT.put_line (RPAD ('ATTR', 20) || RPAD ('PRICE', 20));
FOR o
IN (select attr, trim(case when price < 1 then to_char(price,90.9) else to_char(price) end) price from (
select attr, price, dateofvalue,
row_number() over (partition by attr order by dateofvalue desc) rn from informationvalues
) i where dateofvalue = closingdate
or (rn = 1 and not exists (select 1 from informationvalues iv where iv.attr = i.attr and dateofvalue = closingdate) )
)
LOOP
DBMS_OUTPUT.put_line (RPAD (o.attr, 20) || RPAD ( o.price, 20));
END LOOP;
END;
Sample execution:
set serveroutput on;
begin
print_inf_value(date'2019-01-01');
end;
Output:
ATTR PRICE
age 2
electronics 0.5
gender 3
hobbies 0.5
homeAddress 7
maritalStatus 1
mobilePhone 5
musicTaste 0.1
socialContacts 1
I have created a stored procedure for paging. Now I am looking for column sorting.
My working paging stored procedure:
PROCEDURE paging (PageSize IN INT,
PageIndex IN INT,
SortColumn IN VARCHAR,
PageData OUT Page) AS
FirstIndex INT;
LastIndex INT;
SortCol VARCHAR;
BEGIN
LastIndex := PageSize * (PageIndex + 1);
FirstIndex := LastIndex - PageSize + 1;
SortCol := SortColumn;
OPEN PageData FOR
SELECT *
FROM (SELECT a.*, ROWNUM AS rnum
FROM ( SELECT *
FROM table_name
ORDER BY SortCol) a
WHERE ROWNUM <= LastIndex)
WHERE rnum >= FirstIndex;
END paging;
/
I would suggest you make use of a wonderful feature called Dynamic SQL (Oracle Docs).
I have also modified your SQL query and used ROW_NUMBER() instead of rownum. It is a more robust method of ordering and numbering the output rows than the latter.
I have also removed a few variables that I don't think were needed from your PL/SQL:
PROCEDURE paging (PageSize IN INT,
PageIndex IN INT,
SortColumn IN VARCHAR2, -- Assuming this always contains
-- the ordering column name
PageData OUT Page) AS
FirstIndex INT;
LastIndex INT;
v_sql VARCHAR2(4000);
BEGIN
LastIndex := PageSize * (PageIndex + 1);
FirstIndex := LastIndex - PageSize + 1;
v_sql := 'SELECT *'
||' FROM (SELECT a.*,'
||' ROW_NUMBER() '
||' OVER (ORDER BY ' || SortColumn || ') AS rnum'
||' FROM table_name a)'
||' WHERE rnum BETWEEN FirstIndex AND LastIndex';
OPEN PageData FOR v_sql;
END paging;
/
This SP alone doing all the three things in Oracle, Paging, Sorting and Filtering of records.
create or replace procedure GetResults
(
p_userId In Number,
p_dueDateFrom In Date,
p_dueDateTo in Date,
p_durationMax in Number,
p_durationMin in Number,
p_sortColumn In Varchar2,
p_sortOrder In Varchar2,
p_pageSize In Number,
p_pageIndex in number,
cv_1 OUT SYS_REFCURSOR
)
as
v_FirstIndex NUMBER;
v_LastIndex NUMBER;
begin
-- Paging
v_LastIndex := p_pageSize * (p_pageIndex + 1);
v_FirstIndex := v_LastIndex - p_pageSize + 1;
OPEN cv_1 FOR
SELECT * FROM (SELECT a.*, ROWNUM AS rnum
FROM (Select * From Newjob nj Where nj.userId = p_userId
-- Filtering
And ((p_dueDateFrom IS NULL AND p_dueDateTo Is NULL) OR
(nj.Due_Date >= p_dueDateFrom and nj.Due_Date <= p_dueDateTo)
)
And ((p_durationMax IS NULL AND p_durationMin Is NULL) OR
(nj.Duration >= p_durationMax and nj.Duration <= p_durationMin)
)
-- Sorting
order by
Case when p_sortOrder = 'Ascending' And p_sortColumn = 'DUE_DATE' then nj.Due_Date End,
Case When p_sortOrder = 'Ascending' And p_sortColumn = 'DURATION' then nj.DURATION end,
Case when p_sortOrder = 'Descending' And p_sortColumn = 'DUE_DATE' then nj.Due_Date End desc,
Case When p_sortOrder = 'Descending' And p_sortColumn = 'DURATION' then nj.DURATION end desc)a
WHERE ROWNUM <= v_LastIndex)
WHERE rnum >= v_FirstIndex;
end;
Some background:
My framework jQuery jTable, allows me to do pagination and sort columns, in my select query I need to retrieve n rows (from nth, to nth) and previously order the data by the selected column.
I have a table with n columns where would not exist some rows (this is an example):
To achieve the first requirement I wrote the follow procedure:
create or replace
PROCEDURE PR_SHOWVALUESOLD
(
PRMROWMIN IN NUMBER
, PRMROWMAX IN NUMBER
, CURSORRESULT OUT SYS_REFCURSOR
) AS
BEGIN
open CURSORRESULT for
select * from
(select v.*, rownum r,
(
select count(*) TOTALITEMS from TABLE1 v
) TOTALITEMS
from TABLE1 v
) d
where d.r >= PRMROWMIN and d.r <= PRMROWMAX;
END PR_SHOWVALUESOLD;
This work successfully, I execute the procedure with the follows parameters (PRMROWMIN = 6, PRMROWMAX = 9), the result of the procedure are in Output Varibles window.
Now comes the next step, I need to order the data before take from n to x row.
I rewrite the procedure to do this, but doesn't work:
CREATE OR REPLACE PROCEDURE PR_SHOWVALUES
(
PRMROWMIN IN NUMBER
, PRMROWMAX IN NUMBER
, PRMORDERCOL IN VARCHAR2
, PRMORDERDIR IN VARCHAR2
, CURSORRESULT OUT SYS_REFCURSOR
) AS
BEGIN
open CURSORRESULT for
select * from
(select v.*, rownum r,
(
select count(*) TOTALITEMS from TABLE1 v
) TOTALITEMS
from TABLE1 v
order by 'LOWER(' || PRMORDERCOL || ')' || ' ' || PRMORDERDIR
) d
where d.r >= PRMROWMIN and d.r <= PRMROWMAX;
END PR_SHOWVALUES;
I executed the modified procedure with the follows parameters:
PRMROWMIN := 6;
PRMROWMAX := 9;
PRMORDERCOL := 'COLUMNA';
PRMORDERDIR := 'DESC';
I expected the highlighted rows Query Result 2 window (but this new procedure retrieve the same data as old but disordered Output Variables Window):
How to achieve my requirements?
Thanks in advance.
This is your order by:
order by 'LOWER(' || PRMORDERCOL || ')' || ' ' || PRMORDERDIR
It is not applying the function lower(). Instead, it is concatenating the strings. You may mean:
order by LOWER(PRMORDERCOL) ' ' || PRMORDERDIR
I found a solution to my requirement.
I need to use DECODE to match every column to sort.
I can order in subquery, in this case I do two order by in two subqueries.
The documentation of PL/SQL DECODE function are in:
PL/SQL DECODE FUNCTION
The final Procedure are:
CREATE OR REPLACE PROCEDURE PR_SHOWVALUES
(
PRMROWMIN IN NUMBER
, PRMROWMAX IN NUMBER
, PRMORDERCOL IN VARCHAR2
, PRMORDERDIR IN VARCHAR2
, CURSORRESULT OUT SYS_REFCURSOR
) AS
BEGIN
open CURSORRESULT for
select * from (
select rownum r, v.* from
(
select * from
(
select * from table1 tbl
order by decode
(
UPPER(PRMORDERCOL),
'COLUMNA', LOWER(tbl.COLUMNA),
'COLUMNB', LOWER(tbl.COLUMNB),
LOWER(tbl.TABLE1_ID)
)
)
ORDER BY
CASE
WHEN UPPER(PRMORDERDIR) = 'DESC' THEN
ROWNUM * -1
ELSE
ROWNUM
END
) v
)
where r >= PRMROWMIN and r <= PRMROWMAX;
END PR_SHOWVALUES;
Acknowledgment to Jack David Baucum where I found the solution.