I have below procedure giving me error as
Error(17,3): PLS-00103: Encountered the symbol "FOR" when expecting one of the following: ( - + case mod new not null select with continue avg count current exists max min prior sql stddev sum variance execute fora
Here is the procedure.
PROCEDURE DeactiveUsers (
P_DeactiveUsers_OUT OUT SYS_REFCURSOR ) AS
BEGIN
OPEN P_DeactiveUsers_OUT FOR
for P_DeactiveUsers_Lst in(
select * from (
select username, MAX(TRANSACTION_DATE) As last_login_date
from r4g_application_activity_log
Group By username
) where last_login_date <= sysdate-90
order by 2 desc
)
update r4g_application_activity_log
set ISACTIVE = 1
where USERNAME = P_DeactiveUsers_OUT.username;
EXCEPTION
WHEN no_data_found THEN
INS_UMS_ERRORLOG(SQLCODE||' : '||SUBSTR(SQLERRM, 1, 200),null,'DeactiveUsers',null,null,null,'DB : DeactiveUsers','Scheduler - UMS_DeactiveUser');
WHEN others THEN
INS_UMS_ERRORLOG(SQLCODE||' : '||SUBSTR(SQLERRM, 1, 200),null,'DeactiveUsers',null,null,null,'DB : DeactiveUsers','Scheduler - UMS_DeactiveUser');
END DeactiveUsers;
Hm, yes - procedure is here, but - what is its purpose? The way you put it, it seems that it shouldn't return anything so you don't really want to open a ref cursor, but use a cursor FOR loop which then updates the log table.
If that's so,
remove OUT parameter
remove OPEN clause (btw. you've got two FORs)
use FOR loop
remove WHEN NO_DATA_FOUND as there's nothing that could raise it
PROCEDURE DeactiveUsers -- (P_DeactiveUsers_OUT OUT SYS_REFCURSOR)
AS
BEGIN
--OPEN P_DeactiveUsers_OUT FOR
FOR P_DeactiveUsers_Lst
IN ( SELECT *
FROM ( SELECT username, MAX (TRANSACTION_DATE) AS last_login_date
FROM r4g_application_activity_log
GROUP BY username)
WHERE last_login_date <= SYSDATE - 90
ORDER BY 2 DESC)
LOOP
UPDATE r4g_application_activity_log
SET ISACTIVE = 1
WHERE USERNAME = P_DeactiveUsers_OUT.username;
END LOOP;
EXCEPTION
-- WHEN no_data_found THEN
-- INS_UMS_ERRORLOG(SQLCODE||' : '||SUBSTR(SQLERRM, 1, 200),null,'DeactiveUsers',null,null,null,'DB : DeactiveUsers','Scheduler - UMS_DeactiveUser');
WHEN OTHERS
THEN
INS_UMS_ERRORLOG (SQLCODE || ' : ' || SUBSTR (SQLERRM, 1, 200),
NULL,
'DeactiveUsers',
NULL,
NULL,
NULL,
'DB : DeactiveUsers',
'Scheduler - UMS_DeactiveUser');
END DeactiveUsers;
[EDIT]
As you want to return list of deactivated users, then you could do it as follows: instead of a ref cursor, just loop through result set, do the update and return a collection of deactivated users:
PROCEDURE deactiveusers (p_deactivataed OUT SYS.odcivarchar2list)
IS
l_deactivated SYS.odcivarchar2list := sys.odcivarchar2list ();
BEGIN
FOR cur_r
IN ( SELECT *
FROM ( SELECT username, MAX (TRANSACTION_DATE) AS last_login_date
FROM r4g_application_activity_log
GROUP BY username)
WHERE last_login_date <= SYSDATE - 90
ORDER BY 2 DESC)
LOOP
UPDATE r4g_application_activity_log
SET isactive = 1
WHERE username = cur_r.username;
l_deactivated.EXTEND;
l_deactivated (l_deactivated.LAST) := cur_r.username;
END LOOP;
p_deactivated := l_deactivated;
END;
You'd call it as e.g.
DECLARE
l_deactivated SYS.odcivarchar2list;
BEGIN
deactiveusers (l_deactivated);
FOR i IN l_deactivated.FIRST .. l_deactivated.LAST
LOOP
DBMS_OUTPUT.put_line (l_deactivated (i));
END LOOP;
END;
/
I am not sure how to resolve the above error I am getting. How should I declare it? I created the procedure below to allow a passenger to buy a metro card and add it to an existing account. Input includes account id, initial balance
SET SERVEROUTPUT ON
-- procedure to buy new metro, output error if account already exists.
CREATE OR REPLACE PROCEDURE NewCard (
input_Account_ID IN varchar,
input_Balance IN varchar,
input_age IN varchar
)
IS
acct varchar(255);
-- compare the input account id to see if it exists in the table
CURSOR S1 is SELECT Account_id from Rider_Account where ACCOUNT_ID = input_Account_ID;
BEGIN
--THIS MEANS THE RECORD EXISTS
open S1;
LOOP
FETCH S1 into acct;
EXIT WHEN S1%notfound;
END LOOP;
-- go through the records
IF (acct = input_Account_ID) THEN
Dbms_ouput.Put_line('Account exists');
ELSE
BEGIN
INSERT into Metro_Card(Card_ID, Account_ID, Balance) VALUES(Card_Sequence.NEXTVAL,input_Account_ID, input_Balance);
INSERT into rider_account(Age) VALUES (input_age);
END;
END IF;
IF (input_age <= 12) THEN
UPDATE Metro_Card
SET Discount_type = 2
Where Metro_Card.Account_ID = input_Account_ID;
ELSIF (input_age >= 65) THEN
UPDATE Metro_Card
SET Discount_type = 3
Where Metro_Card.Account_ID = input_Account_ID;
ELSE
UPDATE Metro_Card
SET Discount_type = 1
Where Metro_Card.Account_ID = input_Account_ID;
END IF;
END;
It is not
Dbms_ouput.Put_line('Account exists');
but
Dbms_ouTput.Put_line('Account exists');
-
^
missing "T"
On table "Types", the rows order shall be controled by the field "ordem".
After any data modifications(Delete, Insert or Update), the others rows should be ajusted to assure correct exbitions of the content.
To implement this funcionality, I tried to code a trigger to correct the values in two similar ways (I, II).
CREATE TABLE TYPES (
ID NUMBER PRIMARY KEY,
ARG_1 VARCHAR2(20) NOT NULL,
ARG_2 VARCHAR2(20) NOT NULL,
ORDEM NUMBER NOT NULL
);
-----------------------------------------------
-- I
-----------------------------------------------
CREATE OR REPLACE TRIGGER TGR_TYPES
AFTER INSERT OR UPDATE OR DELETE ON TYPES
DECLARE
V_NORDEM NUMBER := NEW.ORDEM;
CURSOR C_TYPES IS
SELECT ID, ORDEM
FROM TYPES
WHERE ORDEM >= V_NORDEM;
BEGIN
IF UPDATING OR INSERTING THEN
BEGIN
FOR R_TYPE IN C_TYPES LOOP
UPDATE TYPEA SET ORDEM = (ORDEM + 1) WHERE ID = R_TYPE.ID;
END LOOP;
END;
ELSE
DECLARE V_ORDEM NUMBER := 0;
BEGIN
FOR R_TYPE IN C_TYPES LOOP
UPDATE OSP_TP_ADDR_COMPLEMENTOS SET ORDEM = (V_ORDEM + 1) WHERE ID = R_COMPLEMENTO.ID;
END LOOP;
END;
END IF;
END;
/*
ERROR ON COMPILE:
ORA-04082: referências NEW ou OLD não permitidas nos gatilhos de nível de tabela
04082. 00000 - "NEW or OLD references not allowed in table level triggers"
*Cause: The trigger is accessing "new" or "old" values in a table trigger.
*Action: Remove any new or old references.
*/
-----------------------------------------------
-- II
-----------------------------------------------
CREATE OR REPLACE
TRIGGER TRG_TYPES
AFTER INSERT OR UPDATE OR DELETE ON TYPES
FOR EACH ROW
BEGIN
IF UPDATING OR INSERTING THEN
BEGIN
FOR TP IN (
SELECT *
FROM TYPES
WHERE ORDEM >= :NEW.ORDEM
) LOOP
UPDATE TYPES SET ORDEM = (ORDEM + 1) WHERE ID = TP.ID;
COMMIT;
END LOOP;
END;
ELSE
DECLARE V_ORDEM NUMBER := 0;
BEGIN
FOR TP IN (
SELECT *
FROM TYPES
ORDER BY ORDEM
) LOOP
UPDATE TYPES SET ORDEM = (V_ORDEM + 1) WHERE ID = TP.ID;
END LOOP;
END;
END IF;
END;
/*
ERROR ON UPDATE:
UPDATE TYPES
SET ORDEM = 14
WHERE ID=26
Relatório de erros -
ORA-04091: a tabela TYPES é mutante; talvez o gatilho/função não possa localizá-la
ORA-06512: em "", line 9
ORA-04088: erro durante a execução do gatilho ''
*/
I expect one solution to assure the trigger operation or other approach to the integrity of the order field. I have thinking about tring the Index-Organized Table structure, but not sure.
I'm inserting lots of rows into a table and some of the columns are blank for some of the rows.
How can I skip insert if some important fields are blank?
for example there is a table 'people' and my important fields are name,cityName and age.
1 INSERT INTO people VALUES('customerid1','name', 'cityName', 50, 'anotherValue')
2 INSERT INTO people VALUES('customerid2','', '', '' , 'anotherValue')
3 INSERT INTO people VALUES('customerid3','name', 'cityName', 20, 'anotherValue')
4 INSERT INTO people VALUES('customerid4','name', 'cityName', 19, 'anotherValue')
here 2nd row name,cityName and age are blank.
if those three fields are blank then dont insert that row.this is just an example i have more fields to check so need to avoid 'if condition' to check blank or not.
another example
FUNCTION TEST_FUN (increment_i in VARCHAR2, increment_j IN VARCHAR2,mod_id IN VARCHAR2 )
RETURN numeric IS
j_val VARCHAR2(100);
i_val VARCHAR2(100);
BEGIN
i_val := increment_i;
j_val := increment_j;
IF mod_id != 'loop' THEN
j_val := i_val;
END IF;
INSERT
INTO TEST.testpartytable
(
reffer_id,
customer_id,
customer_joint,
fullname,
nature,
counter_bus,
country,
status
)
VALUES
(
REFFER_ID_AR,
CUSTOMER_ID_ARR(i_val),
CUSTOMER_JOINT,
LEGALNAME_KBC_ARR(i_val),
NATURERE_KBC_ARR(j_val),
COUNTERBUSACT_KBC_ARR(j_val),
COUNTRY_KBC_ARR(j_val),
STATUS
);
return i_val;
END TEST_FUN ;
skip insert if 'fullname,nature,counter_bus,country' fields are blank .Datas coming from colletion.
Any help is appreciated.
Thanks!
well you can check first if the values are null or not null:
declare CHECK_VAL varchar2(100);
BEGIN
select CUSTOMER_ID_ARR(i_val) into CHECK_VAL from dual;
if(i_val is not null) then
insert...
end if;
END;
You can alternavely make the column to not null, and raise exeception when you get error for a value not null.
You can apply a NOT NULL constraint to your important columns so that when any of them assigned with a NULL value an error will be raised, specifically ORA -1400 (cannot insert null) exception. You can then catch this exception in your program and just do nothing when this exception is raised.
Sample below,
CREATE TABLE TEST_TABLE
(col1 NUMBER NOT NULL, col2 NUMBER NOT NULL);
DECLARE
CANNOT_INSERT_NULL EXCEPTION;
PRAGMA EXCEPTION_INIT(cannot_insert_null, -1400);
num NUMBER;
BEGIN
FOR i IN 1..10 LOOP
num := 2;
IF i BETWEEN 3 AND 5 THEN
num := NULL; //since this is null, insert statement below will error and handled and won't be inserted
END IF;
BEGIN
INSERt INTO test_table
(col1,col2)
VALUES
(i, num);
EXCEPTION
WHEN CANNOT_INSERT_NULL THEN
NULL;
END;
END LOOP;
END;
/
SELECT *
FROM test_table;
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.