Here's a scenario with a product we purchased. THe product allows us to create custom fields, but these fields are stored as ROWS in a CUSTOM table.
I want to write a query that will connect to more than one custom field and fetch a single row.
Let me give you an example.
1) PERSON TABLE (ID int, NAME varchar2(30));
2) CUSTOMFIELDS TABLE(CFID int, CFTable varchar2(30), CFFieldName varchar2(30), CFFieldType varchar2(30));
3) CUSTOMFIELDVALUE TABLE(CFVID int, CFID int, CFFieldName varchar2(100), CFFieldValue varchar2(100));
My person table has one record
1) 1001 - Clark Kent
Let's say I create two custom fields for person called AGE and WEIGHT.
In that case two records will get created in teh CUSTOMFIELDS table.
1) 100 - PERSON - AGE - INTEGER
2) 200 - PERSON - WEIGHT - INTEGER
Now the values for these custom fields will be stored in the CUSTOMFIELDVALUE table like this.
1) 100 - 100 - 1001 - 44
2) 101 - 200 - 1001 - 200 lbs
I want to write a select query that will fetch the record like this
PERSON, AGE , WEIGHT
Clark Kent, 44, 200 lbs
I am thinking how this can be achieved by pure SQL. The number of custom fields can increase or decrease depending on the configuration of the product.
It is an interesting problem. You want to change dynamically number and name of columns. This is not possible to create with "normal" SQL. I tried to create sample with using of PIPELINED FUNCTION.
I first created table:
CREATE TABLE PERSON (ID int, NAME varchar2(30));
CREATE TABLE CUSTOMFIELDS (CFID int, CFTable varchar2(30), CFFieldName varchar2(30), CFFieldType varchar2(30));
CREATE TABLE CUSTOMFIELDVALUE (CFVID int, CFID int, CFFieldName varchar2(100), CFFieldValue varchar2(100));
INSERT INTO PERSON(id, name) values(1001, 'Clark Kent');
INSERT INTO CUSTOMFIELDS(CFID, CFTable, CFFieldName, CFFieldType) values(100, 'PERSON', 'AGE', 'INTEGER');
INSERT INTO CUSTOMFIELDS(CFID, CFTable, CFFieldName, CFFieldType) values(200, 'PERSON', 'WEIGHT', 'INTEGER');
...and I placed some data:
INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(100, 100, 1001, 44);
INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(101, 200, 1001, 200);
Then I created an object type:
CREATE TYPE CustomFieldType AS OBJECT
(
row_id number,
fieldType varchar2(200),
person_id number,
fieldValue1 varchar2(2000),
fieldValue2 varchar2(2000),
fieldValue3 varchar2(2000),
fieldValue4 varchar2(2000),
fieldValue5 varchar2(2000)
)
/
CREATE TYPE CustomFieldTypeSet AS TABLE OF CustomFieldType
/
And also created PIPELINED FUNCTION:
CREATE OR REPLACE
FUNCTION GET_PERSON_FIELDS(person_id_in IN NUMBER
,field_names_in IN VARCHAR2) RETURN CustomFieldTypeSet
PIPELINED
IS
-- constructor CustomFieldType()
l_header_row CustomFieldType := CustomFieldType(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
l_data_row CustomFieldType := CustomFieldType(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
l_tablen BINARY_INTEGER;
l_tab DBMS_UTILITY.uncl_array;
l_num_of_field_values PLS_INTEGER := 5;
l_counter PLS_INTEGER := 1;
l_position PLS_INTEGER;
l_field_names_in VARCHAR2(2000) := field_names_in;
TYPE type_header_hash IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(200);
l_header_hash type_header_hash;
BEGIN
-- 1) check, what fields you can display
IF (l_field_names_in IS NULL) THEN
<<get_all_fields>>
FOR cur_all_fields IN (SELECT DISTINCT flds.CFFIELDNAME
FROM CUSTOMFIELDS flds
,CUSTOMFIELDVALUE cfv
WHERE cfv.CFID = flds.CFID
AND flds.CFTable = 'PERSON' ) LOOP
l_field_names_in := l_field_names_in ||
cur_all_fields.CFFIELDNAME ||
',';
END LOOP get_all_fields;
END IF;
-- 2) generate header (function RTRIM prevent ORA-00931 exception!)
DBMS_UTILITY.comma_to_table(list => RTRIM(l_field_names_in, ','), tablen => l_tablen, tab => l_tab);
l_header_row.row_id := 1;
l_header_row.fieldType := 'HEADER';
<<header_cursor>>
FOR i IN 1..l_tablen LOOP
IF (i = 1) THEN
l_header_row.fieldValue1 := l_tab(i);
l_header_hash(l_tab(i)) := i;
ELSIF (i = 2) THEN
l_header_row.fieldValue2 := l_tab(i);
l_header_hash(l_tab(i)) := i;
ELSIF (i = 3) THEN
l_header_row.fieldValue3 := l_tab(i);
l_header_hash(l_tab(i)) := i;
ELSIF (i = 4) THEN
l_header_row.fieldValue4 := l_tab(i);
l_header_hash(l_tab(i)) := i;
ELSIF (i = 5) THEN
l_header_row.fieldValue5 := l_tab(i);
l_header_hash(l_tab(i)) := i;
END IF;
END LOOP header_cursor;
-- 3) print data to SQL (over pipe)...
PIPE ROW(l_header_row);
FOR cur_persons IN (SELECT ID
FROM PERSON
WHERE ID = COALESCE(person_id_in, ID) ) LOOP
l_data_row.row_id := NULL;
l_data_row.person_id := NULL;
l_data_row.fieldType := NULL;
l_data_row.fieldValue1 := NULL;
l_data_row.fieldValue2 := NULL;
l_data_row.fieldValue3 := NULL;
l_data_row.fieldValue4 := NULL;
l_data_row.fieldValue5 := NULL;
l_data_row.fieldType := 'DATA';
FOR cur_data IN (SELECT p.ID AS person_id
,cfv.CFID
,flds.CFTABLE
,flds.CFFIELDNAME
,cfv.CFFIELDVALUE
FROM PERSON p
,CUSTOMFIELDS flds
,CUSTOMFIELDVALUE cfv
WHERE p.ID = cur_persons.ID
AND p.ID = cfv.CFFIELDNAME
AND cfv.CFID = flds.CFID ) LOOP
l_data_row.person_id := cur_persons.ID;
l_position := NULL;
IF (l_header_hash.EXISTS(cur_data.CFFIELDNAME)) THEN
l_position := l_header_hash(cur_data.CFFIELDNAME);
END IF;
IF (l_position = 1) THEN
l_data_row.fieldValue1 := cur_data.CFFIELDVALUE;
ELSIF (l_position = 2) THEN
l_data_row.fieldValue2 := cur_data.CFFIELDVALUE;
ELSIF (l_position = 3) THEN
l_data_row.fieldValue3 := cur_data.CFFIELDVALUE;
ELSIF (l_position = 4) THEN
l_data_row.fieldValue4 := cur_data.CFFIELDVALUE;
ELSIF (l_position = 5) THEN
l_data_row.fieldValue5 := cur_data.CFFIELDVALUE;
END IF;
END LOOP;
l_counter := l_counter + 1;
l_data_row.row_id := l_counter;
PIPE ROW(l_data_row);
END LOOP;
RETURN;
END GET_PERSON_FIELDS;
Than you can use SQL to get sample data (note: prevent exception ORA-22905, you must set session variable "ALTER SESSION SET CURSOR_SHARING=EXACT;"):
SELECT * FROM TABLE(GET_PERSON_FIELDS(1001,'AGE,WEIGHT'));
And here is output:
ROW_ID FIELDTYPE PERSON_ID FIELDVALUE FIELDVALUE FIELDVALUE
------ ---------- --------- ---------- ---------- ----------
1 HEADER AGE
2 DATA 1001 44
In the first column is header, where is stored information about field names and after header are stored data. You can use combination of these SQLs:
SELECT * FROM TABLE(GET_PERSON_FIELDS(1001,'AGE,WEIGHT'));
SELECT * FROM TABLE(GET_PERSON_FIELDS(1002,'AGE,GENDER'));
SELECT * FROM TABLE(GET_PERSON_FIELDS(1001,NULL));
SELECT * FROM TABLE(GET_PERSON_FIELDS(NULL,NULL));
First argument is person_id
Second argument is list of items that you want to see the output
if first argument is NULL, you can see list of all persons
if second argument is NULL, you can see list of all arguments
Script is not complete, and have some limitations:
in object CustomFieldType you cannot dynamically change number of fields (I mean fieldValue1, fieldValue2...)
in body of function GET_PERSON_FIELDS as you can see, dynamically problem is also in IF statements (IF (l_position = 1) THEN l_data_row.fieldValue1, IF (l_position = 2) THEN l_data_row.fieldValue1)...
Finally, when I entered some sample data, like this:
INSERT INTO PERSON(id, name) values(1002, 'Lois Lane');
INSERT INTO CUSTOMFIELDS(CFID, CFTable, CFFieldName, CFFieldType) values(300, 'PERSON', 'GENDER', 'VARCHAR');
INSERT INTO CUSTOMFIELDS(CFID, CFTable, CFFieldName, CFFieldType) values(400, 'PERSON', 'SINGLE', 'VARCHAR');
INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(102, 100, 1002, 45);
INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(103, 300, 1002, 'FEMALE');
INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(104, 400, 1002, 'YES');
... and ran this SQL command:
SELECT * FROM TABLE(GET_PERSON_FIELDS(NULL,NULL));
... output looked like this:
ROW_ID FIELDTYPE PERSON_ID FIELDVALUE FIELDVALUE FIELDVALUE FIELDVALUE
------ ---------- --------- ---------- ---------- ---------- ----------
1 HEADER AGE GENDER SINGLE WEIGHT
2 DATA 1001 44 200
3 DATA 1002 45 FEMALE YES
Something like this perhaps:
select p.name, age.CFFieldValue, weight.CFFieldValue
from person p
inner join CUSTOMFIELDVALUE age
on p.id = age.CFFieldName
inner join CUSTOMFIELDS age_f
on age_f.cfid = age.cfid and age_f.CFFieldName = 'AGE' and age_f.CFTable = 'PERSON'
inner join CUSTOMFIELDVALUE weight
on p.id = weight.CFFieldName and weight_f.CFFieldName = 'AGE' and weight_f.CFTable = 'PERSON'
inner join CUSTOMFIELDS weight_f
on weight_f.cfid = weight.cfid;
Its not completely clear to me that person.id joins to customfieldvalue.cfieldname but you do say 100 - 100 - 1001 - 44 so I guess you are trying to say 1001 is the third column? Essentially the query filters the customfieldvalues table and uses it as two separate tables. Which I think is what you want - you question doesn't make it very clear.
Related
In an attempt to generate random unique values, which can be exposed (ie customer_id) and used as a PRIMARY KEY I am doing the following:
Generate random number, grabbing the seconds and fractions of a second from the current timestamp, and also I append to that a fixed width sequence to guarantee the uniqueness. This is all working fine. See below as I am generating some customer information.
My question is how can I make the seed column virtual as I really don't need to have it stored but I would like to see the value if needed.
Thanks in advance to all who answer and apologies for the verbose test CASE
CREATE OR REPLACE PACKAGE mf_names IS
FUNCTION random_first_name(
gender IN VARCHAR2 DEFAULT NULL,
percentage_mf IN NUMBER DEFAULT 50
) RETURN VARCHAR2;
FUNCTION random_last_name RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY mf_names IS
first_names_male SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'Tom', 'Andy', 'Paul', 'Peter', 'Keith', 'Mark', 'Solomon', 'Joseph', 'John', 'Roger', 'Douglas','Harry', 'Barry', 'Larry', 'Gary', 'Jeffrey', 'David', 'Stuart', 'Karl', 'Seth', 'David', 'Brian', 'Sidney', 'James', 'Shane', 'Zachary', 'Anthony'
);
first_names_female SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'Alice', 'Anna', 'Lee', 'Barbara', 'Carol', 'Debra', 'Madison', 'Faith', 'Cheryl', 'Beth', 'Kathy', 'Abigail', 'Jill', 'Grayce', 'Lynn', 'Roz', 'Carolyn', 'Deena', 'Laura', 'Sophia', 'Elise'
);
last_names SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'Cooper', 'Dimeo', 'Caygle', 'Luppo', 'Coralnick', 'Torchiano', 'Fazio', 'Behrens', 'Zaza', 'Lebowitz', 'Stern', 'Malden', 'Kramer', 'Stein', 'Tessio', 'Weinreb', 'Dillon', 'Zanona', 'Rucker', 'Zanzone', 'Santoro', 'Barese', 'Silverberg', 'Aarron', 'Kern', 'Saladino', 'Rice', 'Sanford', 'Orr', 'Roth'
);
FUNCTION random_first_name(
gender IN VARCHAR2 DEFAULT NULL,
percentage_mf IN NUMBER DEFAULT 50
) RETURN VARCHAR2
IS
BEGIN
IF UPPER(gender) LIKE 'M%' THEN
RETURN first_names_male(FLOOR(DBMS_RANDOM.VALUE(1, first_names_male.COUNT + 1)));
ELSIF UPPER(gender) LIKE 'F%' THEN
RETURN first_names_female(FLOOR(DBMS_RANDOM.VALUE(1, first_names_female.COUNT + 1)));
ELSIF DBMS_RANDOM.VALUE(0, 100) < percentage_mf THEN
RETURN random_first_name('M');
ELSE
RETURN random_first_name('F');
END IF;
END;
FUNCTION random_last_name RETURN VARCHAR2
IS
BEGIN
RETURN last_names(FLOOR(DBMS_RANDOM.VALUE(1, last_names.COUNT + 1)));
END;
END;
/
CREATE TABLE CUSTOMERS (
customer_id VARCHAR2 (20),
seed NUMBER,
first_name VARCHAR2 (20),
last_name VARCHAR2 (20),
constraint customer_id_pk primary key (customer_id));
create sequence customer_seq start with 1000000 minvalue 1000000 maxvalue 9999999 cycle;
create or replace function base34(p_num number) return varchar2 is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := p_num;
l_str varchar2(38);
begin
loop
l_str := substr(l_dig,mod(l_num,34)+1,1) || l_str ;
l_num := trunc(l_num/34);
exit when l_num = 0;
end loop;
return l_str;
end;
/
create or replace function dec34(p_str varchar2) return number is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := 0;
begin
for i in 1 .. length(p_str) loop
l_num := l_num * 34 + instr(l_dig,upper(substr(p_str,i,1)))-1;
end loop;
return l_num;
end;
/
create or replace trigger customer_trg
before update on customers for each row
begin
if ( updating('customer_id') )
then
raise_application_error(-20000,'Cant Update customer_id');
end if;
if ( updating('seed') )
then
raise_application_error(-20001,'Cant Update seed');
end if;
end;
/
DECLARE
seed NUMBER;
begin
for i in 1 .. 100 loop
seed := (to_number(trunc(dbms_random.value(1000,9999))|| to_char(systimestamp,'FFSS')||customer_seq.nextval));
INSERT into customers(
customer_id,
seed,
first_name,
last_name
) VALUES (
base34(seed),
seed,
mf_names.random_first_name(),
mf_names.random_last_name()
);
end loop;
end;
/
select to_char(seed) from customers where rownum = 1
union all
select base34(seed) from customers where rownum = 1
union all
select to_char(dec34(base34(seed))) from customers where rownum = 1;
TO_CHAR(SEED)
5444355405000301000000
BZCK24C0D3CHYE6
5444355405000301000000
select base34(seed),
dump(base34(seed)) from customers where rownum = 1
BASE34(SEED) DUMP(BASE34(SEED))
BZCK24C0D3CHYE6 Typ=1 Len=15: 66,90,67,75,50,52,67,48,68,51,67,72,89,69,54
I'm afraid you can't do that because dbms_random.value and systimestamp functions you'd want to use aren't deterministic (they should return the same value whenever called, while these two don't).
For example: this works because 1 + 2 is always 3:
SQL> create table test
2 (id number generated always as identity,
3 seed number generated always as (1 + 2)
4 );
Table created.
SQL> drop table test;
Table dropped.
Can you use dbms_random.value? Nope:
SQL> create table test
2 (id number generated always as identity,
3 seed number generated always as (to_number(trunc(dbms_random.value(1000,9999))))
4 );
seed number generated always as (to_number(trunc(dbms_random.value(1000,9999))))
*
ERROR at line 3:
ORA-30553: The function is not deterministic
If the function is deterministic, mark it DETERMINISTIC. If it is not deterministic (it depends on package state, database state, current time, or anything other than the function inputs) then do not create the index. The values returned by a deterministic function should not change even when the function is rewritten or recompiled.
Can you use sysdate (or systimestamp, no difference)? No:
SQL> create table test
2 (id number generated always as identity,
3 seed number generated always as (to_number(to_char(sysdate, 'yyyymmdd')))
4 );
seed number generated always as (to_number(to_char(sysdate, 'yyyymmdd')))
*
ERROR at line 3:
ORA-54002: only pure functions can be specified in a virtual column expression
Column expression contained a function whose evaluation is non-deterministic
Therefore, that won't work.
Wouldn't it be simpler to just use SYS_GUID function to generate the primary key value ? It is a unique number. We do this for all the tables in our projects.
CREATE TABLE some_name
( some_name_id NUMBER DEFAULT ON NULL to_number(sys_guid(), 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') NOT NULL
PRIMARY KEY
--other columns
) ;
No function needed, no seeding needed, no virtual column needed.
I'm new to triggers, facing an issue during update trigger.
Scenario:
I have two tables [table1 and table2] when a particular column changes in table1, I need to update the table2 records based on few conditions
Here is my trigger,
create or replace TRIGGER UPDATE_TABLE1
BEFORE UPDATE OF LOWTHRESHOLD, HIGHTHRESHOLD ON TABLE1
for each row
when(nvl(old.LOWTHRESHOLD,0) <> nvl(new.LOWTHRESHOLD,0)
OR nvl(old.HIGHTHRESHOLD,0) <> nvl(new.HIGHTHRESHOLD,0))
declare
PID number(3) := nvl(:old.PID,0);
oldLOWTH number(2) := nvl(:old.LOWTHRESHOLD,0);
oldHIGHTH number(2) := nvl(:old.HIGHTHRESHOLD,0);
newLOWTH number(2) := nvl(:new.LOWTHRESHOLD,0);
newHIGHTH number(2) := nvl(:new.HIGHTHRESHOLD,0);
ratio DOUBLE PRECISION;
oldTHCrossed number(1) := 0;
objID number(10) := 0;
objType number(1) := 0;
CURSOR profileUtil_Cursor IS
SELECT * FROM TABLE2 where upid = PID;
begin
dbms_output.enable(1000);
FOR v_record in profileUtil_Cursor LOOP
ratio := v_record.UTILIZATIONRATIO;
oldTHCrossed := v_record.THCROSSED;
objID := v_record.objID;
objType := v_record.objType;
IF(ratio < newLOWTH AND ratio < newHIGHTH ) THEN
UPDATE TABLE2 SET THCROSSED = 1, LASTUPDATED = date_to_miniseconds(sysdate)
WHERE objID = objID and objType = objType and upid = PID;
ELSIF(newLOWTH <= ratio AND ratio <= newHIGHTH) THEN
UPDATE TABLE2 SET THCROSSED = 2, LASTUPDATED = date_to_miniseconds(sysdate)
WHERE objID = objID and objType = objType and upid = PID;
ELSIF(ratio > newHIGHTH) THEN
UPDATE TABLE2 SET THCROSSED = 3, LASTUPDATED = date_to_miniseconds(sysdate)
WHERE upid = PID and objID = objID and objType = objType;
END IF;
END LOOP;
IF SQL%NOTFOUND THEN
DBMS_OUTPUT.put_line('NOT FOUND');
END IF;
END;
But when this trigger is executed, all the records in TABLE2 are getting updated.
Please provide some pointers to solve this issue.
Cursor selects all rows whose UPID = :OLD.PID. According to what you said, all rows in table2 have the same upid column value, and it is equal to table1.pid which is being updated right now.
UPDATE statements will modify rows that satisfy their WHERE conditions. All 3 of them are the same:
WHERE objID = objID --> that's 0
AND objType = objType --> that's 0 as well
AND upid = PID --> that's "old" table1.pid
which - additionally to my 1st statement - means that all rows in table2 not only share the same upid, but their objID and objType column values are 0 (at least, that's what you declared).
Furthermore (as #astentx nicely observed), if you name variables the same as column names, it behaves as if you put where 1 = 1 which is always true. Use prefix for local variables, e.g. l_ so where clause would turn into
WHERE objID = l_objID --> that's 0
AND objType = l_objType --> that's 0 as well
AND upid = l_PID --> that's "old" table1.pid
So: if everything above is true, then yes - trigger will update all rows in table2.
declare
user_id number(10);
begin
if(P10_USER_TYPE = 'FACULTY') then
user_id := :P10_BORROWER_FACULTY;
insert into LMS_BOOK_ISSUE(BOOK_ISSUE_ID,BOOK_ID,BORROWER_ID,ISSUED_BY,BORROWED_FROM_DATE,BORROWED_TO_DATE,BORROWER_TYPE,BOOK_RT_STATUS)
values (LMS_BOOK_ISSUE_SEQ.nextval,**;P10_BOOK_NAME,user_id, 1, :P10_BORROWED_DATE,:P10_FACULTY_RET_DATE,'FACULTY',1** );
update LMS_BOOK_DETAIL set BOOK_NO_OF_COPIES_CURRENT = BOOK_NO_OF_COPIES_CURRENT-1 where BOOK_ID=:P10_BOOK_NAME;
else
user_id := :P10_STUDENT;
insert into LMS_BOOK_ISSUE(BOOK_ISSUE_ID,BOOK_ID,BORROWER_ID,ISSUED_BY,BORROWED_FROM_DATE,BORROWED_TO_DATE,BORROWER_TYPE,BOOK_RT_STATUS)
values (LMS_BOOK_ISSUE_SEQ.nextval,;P10_BOOK_NAME,user_id, 1, :P10_BORROWED_DATE,:P10_FACULTY_RET_DATE,'STUDENT',1 );
update LMS_BOOK_DETAIL set BOOK_NO_OF_COPIES_CURRENT = BOOK_NO_OF_COPIES_CURRENT-1 where BOOK_ID=:P10_BOOK_NAME;
end if;
end;
it said
ORA-06550: line 8, column 44: PL/SQL: ORA-00936: missing expression
How do i fix this?
I don't have your tables so I can't test it, but - page items should be referenced with a colon in front of their names, not semi-colon.
DECLARE
user_id NUMBER (10);
BEGIN
IF (P10_USER_TYPE = 'FACULTY')
THEN
user_id := :P10_BORROWER_FACULTY;
INSERT INTO LMS_BOOK_ISSUE (BOOK_ISSUE_ID,
BOOK_ID,
BORROWER_ID,
ISSUED_BY,
BORROWED_FROM_DATE,
BORROWED_TO_DATE,
BORROWER_TYPE,
BOOK_RT_STATUS)
VALUES (LMS_BOOK_ISSUE_SEQ.NEXTVAL,
:P10_BOOK_NAME,
user_id,
1,
:P10_BORROWED_DATE,
:P10_FACULTY_RET_DATE,
'FACULTY',
1);
UPDATE LMS_BOOK_DETAIL
SET BOOK_NO_OF_COPIES_CURRENT = BOOK_NO_OF_COPIES_CURRENT - 1
WHERE BOOK_ID = :P10_BOOK_NAME;
ELSE
user_id := :P10_STUDENT;
INSERT INTO LMS_BOOK_ISSUE (BOOK_ISSUE_ID,
BOOK_ID,
BORROWER_ID,
ISSUED_BY,
BORROWED_FROM_DATE,
BORROWED_TO_DATE,
BORROWER_TYPE,
BOOK_RT_STATUS)
VALUES (LMS_BOOK_ISSUE_SEQ.NEXTVAL,
:P10_BOOK_NAME,
user_id,
1,
:P10_BORROWED_DATE,
:P10_FACULTY_RET_DATE,
'STUDENT',
1);
UPDATE LMS_BOOK_DETAIL
SET BOOK_NO_OF_COPIES_CURRENT = BOOK_NO_OF_COPIES_CURRENT - 1
WHERE BOOK_ID = :P10_BOOK_NAME;
END IF;
END;
I need to use collections as below, but want to print the output dynamically instead of specifying PL01, PL02, PL03...as this is going to be bigger list. If not possible using RECORD type, how can this be achieved using collections.
Business scenario : Having a item price list in an excel sheet as price list code in the first row (each column refers to a price list) and different items in each row. The number of price list and the items will differ every time. Now i need to populate this excel in collections in same format (Table with items in rows and price list code as columns) and use it to update the correct price list.
DECLARE
TYPE RT IS RECORD (ITEM VARCHAR2(20),
PL01 NUMBER,
PL02 NUMBER,
PL03 NUMBER,
PL04 NUMBER,
PL05 NUMBER);
TYPE TT IS TABLE OF RT;
MY_REC RT;
MY_TAB TT := TT();
BEGIN
MY_TAB.EXTEND;
MY_TAB(1).ITEM := 'ABC';
MY_TAB(1).PL01 := '40';
MY_TAB(1).PL02 := '42';
MY_TAB(1).PL03 := '44';
MY_TAB(1).PL04 := '46';
MY_TAB(1).PL05 := '48';
MY_TAB.EXTEND;
MY_TAB(2).ITEM := 'DEF';
MY_TAB(2).PL01 := '60';
MY_TAB(2).PL02 := '62';
MY_TAB(2).PL03 := '64';
MY_TAB(2).PL04 := '66';
MY_TAB(2).PL05 := '68';
FOR I IN 1..2
LOOP
Dbms_Output.PUT_LINE(MY_TAB(I).ITEM||' - '||MY_TAB(I).PL01||' - '||MY_TAB(I).PL02||' - '||
MY_TAB(I).PL03||' - '||MY_TAB(I).PL04||' - '||MY_TAB(I).PL05);
END LOOP;
END;
/
One way is to create TYPE as objects and using a TABLE function to display by passing a REFCURSOR.
CREATE OR REPLACE TYPE RT AS OBJECT ( ITEM VARCHAR2(20),
PL01 NUMBER,
PL02 NUMBER,
PL03 NUMBER,
PL04 NUMBER,
PL05 NUMBER
);
/
CREATE OR REPLACE TYPE TT AS TABLE OF RT;
/
VARIABLE x REFCURSOR;
DECLARE
MY_TAB TT := TT();
BEGIN
MY_TAB.EXTEND(2); --allocate 2 elements
MY_TAB(1) := RT ( 'ABC',40,42,44,46,48);--you can assign all once/index
MY_TAB(2) := RT ( 'DEF',60,62,64,66,68);
OPEN :x FOR SELECT * FROM TABLE(MY_TAB);
END;
/
PRINT x
ITEM PL01 PL02 PL03 PL04 PL05
-------------------- ---------- ---------- ---------- ---------- ----------
ABC 40 42 44 46 48
DEF 60 62 64 66 68
You can use a PL/SQL table inside the RECORD. Try this:
DECLARE
TYPE PL_TABLE_TYPE IS TABLE OF NUMBER;
TYPE RT IS RECORD (ITEM VARCHAR2(20), PL_TABLE PL_TABLE_TYPE);
TYPE TT IS TABLE OF RT;
MY_TAB TT := TT();
BEGIN
MY_TAB.EXTEND;
MY_TAB(MY_TAB.LAST).ITEM := 'ABC';
MY_TAB(MY_TAB.LAST).PL_TABLE := PL_TABLE_TYPE(40,42,33,46,48);
--> Why do you assign strings like '40' if you have a NUMBER data type?
MY_TAB.EXTEND;
MY_TAB(MY_TAB.LAST).ITEM := 'DEF';
MY_TAB(MY_TAB.LAST).PL_TABLE := PL_TABLE_TYPE(60,62,64,66,68);
FOR r IN MY_TAB.FIRST..MY_TAB.LAST LOOP
DBMS_OUTPUT.PUT(MY_TAB(r).ITEM || ' ');
FOR i IN MY_TAB(r).PL_TABLE.FIRST..MY_TAB(r).PL_TABLE.LAST LOOP
DBMS_OUTPUT.PUT(MY_TAB(r).PL_TABLE(i) || ' ');
END LOOP;
DBMS_OUTPUT.NEW_LINE;
END LOOP;
END;
Hi What I would like to do is take the search criterias as parameters to my function and search through the table depending on the input parameters.. for example: IF ALL the parameters are NULL then the function will return evey field in the table, if a parameter is not NULL that parameter will be a search criteria. I could do an If statement but it would be too long and assuming that I would have had a bigger table it would be very painful, Is there any way I could accomplish this Task? Thanks
My Code:
create or replace type ordersinf as object(orderDate DATE, orderName VARCHAR2(20), orderPrice NUMBER)
create or replace type orders_tab IS TABLE OF ordersinf
and My Code is:
create or replace function execImmFunc(orderDate DATE, orderName in
VARCHAR2, orderPrice in NUMBER) return orders_tab is
Result orders_tab := orders_tab();
begin
SELECT ordersinf(orderDate => orders_Orderdate, orderName => orderName,orderPrice => orders_price)
BULK COLLECT INTO RESULT
FROM orders_table
WHERE orders_Orderdate >= orderDate OR orders_name = orderName OR orders_price = orderPrice ;
return(Result);
end execImmFunc;
Something like this:
CREATE OR REPLACE FUNCTION execImmFunc(
orderDate IN DATE,
orderName IN VARCHAR2,
orderPrice IN NUMBER
) RETURN orders_tab
IS
Result orders_tab;
BEGIN
SELECT ordersinf(
orderDate => orders_OrderDate,
orderName => orders_Name,
orderPrice => orders_price
)
BULK COLLECT INTO Result
FROM orders_table
WHERE ( orderDate IS NULL OR orders_OrderDate >= orderDate )
AND ( orderName IS NULL OR orders_name = orderName )
AND ( orderPrice IS NULL OR orders_price = orderPrice );
RETURN Result;
END execImmFunc;
Is this what you mean
WHERE (orderDate is null OR orders_Orderdate >= orderDate) AND (orderName is null OR orders_name = orderName) AND (orderPrice is null OR orders_price = orderPrice );
If a parameter orderDate is null - the clause passes and is effectively ignored?
Hey I dont have workspace with me but below is a very simple snippet
which can be used for dynamic sql formation as per the input provided.
Hope this helps
SET SERVEROUTPUT ON;
DECLARE
lv_col1 VARCHAR2(100):=123;
lv_col2 VARCHAR2(100):=1;
lv_col3 VARCHAR2(100):=NULL;
lv_col4 VARCHAR2(100):=45;
lv_col5 VARCHAR2(100):=8;
lv_sql LONG;
lv_where LONG;
BEGIN
lv_sql:='SELECT * FROM TABLE WHERE 1 = 1 ';
SELECT DECODE(lv_col1,NULL,'',' AND col1 = '''
||lv_col1
||'''')
|| DECODE(lv_col2,NULL,'',' AND col2 = '''
||lv_col2
||'''')
|| DECODE(lv_col3,NULL,'',' AND col3 = '''
||lv_col3
||'''')
|| DECODE(lv_col4,NULL,'',' AND col4 = '''
||lv_col4
||'''')
|| DECODE(lv_col5,NULL,'',' AND col5 = '''
||lv_col5
||'''')
INTO lv_where
FROM dual;
dbms_output.put_line(lv_sql||lv_where);
END;
Are you trying to set up so a parameter is either returned in the resultset or used in the where-clause? It does not look like you do, but rather you want to return all columns and use parameters in where only when they are not null.
If so, why not just a classic nvl-setup?
SELECT ordersinf(orderDate => orders_Orderdate
,orderName => orderName
,orderPrice => orders_price)
BULK COLLECT INTO RESULT
FROM orders_table
WHERE orders_Orderdate >= nvl(orderDate, orders_Orderdate)
OR orders_name = nvl(orderName, orders_name)
OR orders_price = nvl(orderPrice, orders_price);
I imagine those ORs ought to be AND as you probably want all conditions sent in to be true, but that is purely a desugn/requirement issue.