Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I'm looking for a way to return one case-insensitive varchar2 from a table given as a parameter.
The database is configured to have as the first column the one I'll look into.
This is what I got to:
create or replace function generic_return(nam varchar2, table_n varchar2)
return varchar2
as
case_insensitive varchar2(300);
case_ins varchar2(300);
colu varchar2(30);
t_nam varchar2(30):=upper(table_n);
cursor point is execute inmediate select colu from t_nam;
cursor col is select column_name from cols where table_name=t_nam;
non_existent_table exception;
begin
case_ins:=upper(rtrim(ltrim(nam)));
open col;
fetch col into colu;
close col;
select column_name from cols where table_name=upper(table_n);
if colu is null then
raise non_existent_table;
end if;
open point;
loop
fetch point into case_insensitive;
exit when point%notfound;
if upper(case_insensitive)=case_ins then
return case_insensitive;
end if;
end loop;
close point;
return null;
end;
/
The function receives what to look for, and the name of the table to look into, then the first variables are to compare them, t_nam is to use the uppercased version of the second parameter... and from that there's the huge mess: I get the column name from the table in col, from cols, and then I try to make a cursor that goes around checking if what the tuple, going by the same modifications, is the first parameter, or not, if it happens to be, then it should return the unmodified version of the one in the table. Otherwise, it should return "null", which I'll treat differently on each procedure that uses it.
By null I mean nothingness, not the string.
Yes, you are right. That's a pretty huge mess. :-)
For starters, you cannot declare an explicit cursor as an execute immediate statement.
Next, if you want the first column in the table, you need to specify
WHERE column_id = 1
and then you can just grab it via a SELECT-INTO, no need for an explicit cursor.
Then you could try something like:
my_cur SYS_REFCURSOR;
BEGIN
...
OPEN my_cur FOR 'SELECT ' || first_col || ' FROM ' || t_name;
LOOP
FETCH my_cur INTO case_insensitive;
EXIT WHEN my_cur%NOTFOUND;
... logic for match ....
END LOOP;
CLOSE my_cur;
Related
I am trying to get the user_id and group_id for individual user.
I have used user's email in loop because so many users are there, but I need the loop should take one by one, currently its taking all mail ids like : abinnaya.moorthy#abc.com,abinnaya.moorthy#def.com.
Because of this the select query is not returning any value.
The select query should return the value one by one by taking the email id from loop.
code:
DECLARE
L_USERS varchar2(1000);
l_org_group_id varchar2(1000);
l_user_id varchar2(1000);
l_api_body varchar2(1000);
l_retry_after number;
l_status number;
L_NOT_PROVISIONED_USERS varchar2(1000);
l_success boolean;
l_user varchar2(1000);
BEGIN
FOR I IN
(Select REQUESTORS_NAME into L_USER
from Request
where Request_Status = 'Approved'
and Provisioning_Status is NULL )
LOOP
L_USER:= L_USER ||','||I.REQUESTORS_NAME;
select GROUP_ID INTO l_org_group_id
from WORKSPACE_GROUP
where LOWER(email)=(L_USER);
select USER_ID into l_user_id
from slackdatawarehouse.users
where lower(email) = lower(L_USER);
DBMS_OUTPUT.PUT_LINE(l_user_id);
if l_user_id is null then
l_not_provisioned_users := l_not_provisioned_users||','|| L_USER;
else
l_api_body := l_api_body || '{"value" :"'||l_user_id ||'"},';
l_users := l_users||','||l_user_id;
end if;
end loop;
end;
Help me to get the user email one by one and pass it in select query to get the groupid and user id.
Oh, boy. I presume that senior members / moderators won't be too happy with this "answer", but it is impossible to put everything into a 600-characters long comment. True, I could shorten the critic to "this is sh*t", but that won't help anyone. Though, "rules" would be obeyed. So, if it turns out that this message is deleted, sorry, everyone.
Here you go.
It is difficult to guess what you want to get as the result. You talk about Apex, but - what does this code have to do with it? DBMS_OUTPUT certainly won't work there.
Then, in DECLARE section, you use 3 variables that are never used - get rid of them.
Cursor FOR loop is the way to do that; however, remove INTO clause from SELECT, it doesn't belong here.
L_USER is concatenation of all REQUESTOR_NAMEs returned by cursor. It means that you shouldn't use it in SELECT GROUP_ID nor SELECT USER_ID statements as it certainly won't return anything (maybe something for the first loop iteration, but nothing for the rest of them). It looks as if you'd rather use I.REQUESTORS_NAME. Also, once you apply LOWER function to it, and then you don't - consider making it uniform.
Why do you select GROUP_ID at all? You never use it later.
L_NOT_PROVISIONED_USERS is a huge concatenation of duplicates, as you concatenate previous values with (yet another concatenation of) L_USER. Try to DBMS_OUTPUT it, you'll see.
You don't care about possible NO-DATA-FOUNDs which might well be raised by those SELECTs, as - as I've already said - they won't return anything in subsequent loop iterations.
Finally, even if that PL/SQL finishes successfully, it won't do anything. Nobody, including you, wont' benefit from it.
So, this is quite a mess ... try to follow what I've written, make a clear picture of what you want to get as a result, go step-by-step, test frequently and - hopefully - you'll get something useful.
Do it as below:
DECLARE
L_USERS VARCHAR2 (1000);
L_ORG_GROUP_ID VARCHAR2 (1000);
L_USER_ID VARCHAR2 (1000);
L_API_BODY VARCHAR2 (1000);
L_RETRY_AFTER NUMBER;
L_STATUS NUMBER;
L_NOT_PROVISIONED_USERS VARCHAR2 (1000);
L_SUCCESS BOOLEAN;
L_USER VARCHAR2 (1000);
CURSOR EMAIL_IDS
IS
SELECT REQUESTORS_NAME L_USER
FROM REQUEST
WHERE REQUEST_STATUS = 'Approved'
AND PROVISIONING_STATUS IS NULL;
BEGIN
FOR I IN EMAIL_IDS
LOOP
SELECT GROUP_ID
INTO L_ORG_GROUP_ID
FROM WORKSPACE_GROUP
WHERE LOWER (EMAIL) = LOWER (I.L_USER);
SELECT USER_ID
INTO L_USER_ID
FROM SLACKDATAWAREHOUSE.USERS
WHERE LOWER (EMAIL) = LOWER (I.L_USER);
DBMS_OUTPUT.PUT_LINE (L_USER_ID);
IF L_USER_ID IS NULL THEN
L_NOT_PROVISIONED_USERS :=
L_NOT_PROVISIONED_USERS || ',' || I.L_USER;
ELSE
L_API_BODY :=
L_API_BODY || '{"value" :"' || L_USER_ID || '"},';
L_USERS := L_USERS || ',' || L_USER_ID;
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE (SQLERRM);
END;
I've come up with the following trigger to extract all the column names which are updated when a table row update statement is executed...
but the problem is if there are more columns(atleast 100 cols), the performance/efficiency comes into concern
sample trigger code:
set define off;
create or replace TRIGGER TEST_TRIGG
AFTER UPDATE ON A_AAA
FOR EACH ROW
DECLARE
mytable varchar2(32) := 'A_AAA';
mycolumn varchar2(32);
updatedcols varchar2(3000);
cursor s1 (mytable varchar2) is
select column_name from user_tab_columns where table_name = mytable;
begin
open s1 (mytable);
loop
fetch s1 into mycolumn;
exit when s1%NOTFOUND;
IF UPDATING( mycolumn ) THEN
updatedcols := updatedcols || ',' || mycolumn;
END IF;
end loop;
close s1;
--do a few things with the list of updated columns
dbms_output.put_line('updated cols ' || updatedcols);
end;
/
Is there any alternative way to get the list?
Maybe with v$ tables (v$transaction or anything similar)?
No its the best way to get UPDATED column by UPDATING()
and you can change your code using implicit cursor like this, it will be a little bit faster
set define off;
create or replace TRIGGER TEST_TRIGG
AFTER UPDATE ON A_AAA
FOR EACH ROW
DECLARE
updatedcols varchar2(3000);
begin
for r in (select column_name from user_tab_columns where table_name ='A_AAA')
loop
IF UPDATING(r.column_name) THEN
updatedcols := updatedcols || ',' || r.column_name;
END IF;
end loop;
dbms_output.put_line('updated cols ' || updatedcols);
end;
/
Faced with a similar task, we ended up writing a pl/sql procedure which lists the columns of the table and generates the full trigger body for us, with static code referencing :new.col and :old.col. The execution of such trigger should probably be faster (though we didn't compare).
However, the downside is that when you later add a new column to the table, it's easy to forget to update the trigger body. It probably can be managed somehow with a monitoring job or elsehow, but for now it works for us.
P.S. I became curious what that updating('COL') feature does, and checked it now. I found out that it returns true if the column is present in the update statement, even if the value of the column actually didn't change (:old.col is equal to :new:col). This might generate unneeded history records, if the table is being updated by something like Java Hibernate library, which (by default) always specifies all columns in the update statements it generates. In such a case you might want to actually compare the values from inside the trigger body and insert the history record only in case the new value differs from the old value.
I'm fetching value using cursor:
CURSOR Colcond
IS
SELECT CONDITION
FROM CONDITION_TAB
WHERE PROCEDURE_NAME = 'CALL_VOL';
In first iteration it would fetch "SUM(CASE WHEN CALL_REF=0 THEN 1 ELSE 0 END)".
In my program:
OPEN Colcond;
FETCH Colcond INTO cond_val;
SELECT Appnum, customer_num,'"cond_val"'
INTO iappnum, icustnum,icond_val
FROM CALL_DETAILS WHERE APPNUM = val_appl
AND customer_num = val_cust
Group by APPLICATION_NUM,CUST_SGMT_NUM,DNIS;
INSERT INTO S_CALL_VOLUME VALUES (iappnum, icustnum, SYSDATE, icond_val);
The record thRough the variable "icond_val" inserted is SUM(CASE WHEN CALL_REF=0 THEN 1 ELSE 0 END) instead of the value (10 or 20 or 50).
How to get the value instead of that Sum case statement?
You need to use dynamic SQL to incorporate the value you selected from the condition_tab table into the next query. Here's an example in an anonymous block rather than a procedure:
declare
val_appl number; -- procedure argument in your version?
val_cust number; -- procedure argument in your version?
query_string varchar2(2000);
cond_val condition_tab.condition%type;
iappnum call_details.appnum%type;
icustnum call_details.customer_num%type;
icond_val number;
cursordyn sys_refcursor;
cursor colcond is
select condition
from condition_tab
where procedure_name = 'CALL_VOL';
begin
open colcond;
fetch colcond into cond_val;
close colcond;
query_string:='select appnum, customer_num, ' || cond_val || ' from call_details '
|| 'where appnum = :val_appl and customer_num = :val_cust '
|| 'group by application_num,cust_sgmt_num,dnis';
open cursordyn for query_string using val_appl, val_cust;
fetch cursordyn into iappnum, icustnum, icond_val;
close cursordyn;
insert into s_call_volume values (iappnum, icustnum, sysdate, icond_val);
end;
/
Your column names seem to be a bit inconsistent so it probably needs some tweaking.
For both cursors you're only selecting one row, so (a) they don't really need to be cursors, they can just be select into statements; and (b) the second one is selecting the two columns from the where clause which seems a bit pointless - when you use iappnum in the insert, you could just use val_app, etc. So I think you could simplify this to:
declare
val_appl number; -- procedure argument in your version?
val_cust number; -- procedure argument in your version?
query_string varchar2(2000);
cond_val condition_tab.condition%type;
icond_val number;
begin
select condition
into cond_val
from condition_tab
where procedure_name = 'CALL_VOL';
query_string:='select ' || cond_val || ' from call_details '
|| 'where appnum = :val_appl and customer_num = :val_cust '
|| 'group by application_num,cust_sgmt_num,dnis';
execute immediate query_string into icond_val using val_appl, val_cust;
insert into s_call_volume values (val_appl, val_cust, sysdate, icond_val);
end;
/
This will error if either query doesn't return exactly one row. Your cursor version will error if the condition_tab query finds no data, and will only use one 'condition' if there are multiples; and will only use the first result from the second query if there are multiples. If you're expecting multiples from either (not sure what your actual grouping is supposed to be, it looks inconsistent too) then you need to loop over the cursor, fetching repeatedly.
Hopefully this will get you started though.
Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 10 years ago.
Improve this question
I want to use single cursor to fetch single record or all records using where condition, e.g. : student is table and sid is an attribute.
I have two cursors,
DECLARE S1 CURSOR FOR SELECT * FROM Student;
and
Declare S2 Cursor for select * from Student where sid=11
My query is on how to combine these two conditions to use only one cursor. I need this as I have two functions m_viewStudent and m_viewallStudents, for this I would like to use only one cursor to display the requested details from table.
so how can I achieve this ?
Try this:
select *
from student
where sid = 11
or not exists (select 1 from student where sid = 11)
Here is a sqlfiddle demo
UPDATE
If you want to use the same cursor for different functions then you can do it like this:
create package p is
procedure one_sid(in_sid number);
procedure all_sid;
end p;
/
create package body p is
cursor c(p_sid number) is
select *
from student
where sid = p_sid or p_sid is null;
procedure one_sid(in_sid number) is
begin
open c(in_sid);
close c;
end;
procedure all_sid is
begin
open c(null);
close c;
end;
end p;
/
You can change the cursor to
SELECT * FROM students WHERE sid like '%' || variable_from_function || '%';
This will ensure that when function m_viewStudent calls this, only the sid (value sent to this function variable_from_function) record is picked. When m_viewallStudents calls this, all records get picked as variable_from_function would be null.
Assuming your procedure variable is $var, try this:
Declare S2 Cursor for select * from Student
where sid=$var
or $var is null
And pass in a null when you want them all, or make the default value of the parameter null and pass in no parameter
The best way to do this is with a Cursor Variable, more commonly called a Ref Cursor. This is basically a pointer to a result set. The advantage of a Ref Cursor is that we can vary the select statement, like this:
create or replace package student_utils is
-- a hard-types ref cursor
type stud_cur is ref cursor return students%rowtype;
function get_students
( p_sid in students.sid%type := null )
return stud_cur;
end;
Note that I have made a couple of presumptions about how you are intending to use the code. Using a package allows us to define a hard-typed ref cursor, which means that it can only be used for queries which match the projection of the STUDENTS table. (If you don't have an actual STUDENTS table you can use of of the views or define a Pl/SQL Record instead.)
create or replace package body student_utils is
function get_students
( p_sid in students.sid%type := null )
return stud_cur
is
return_value stud_cur;
begin
if p_sid is null
then
open return_value for
select * from m_viewallStudents;
else
open return_value for
select * from m_viewStudent
where sid = p_sid;
end if;
return return_value;
end;
end;
These queries are hard-coded but we can also open Ref Cursors with dynamic SQL, which is a powerful technique.
Read the documentation to find out more.
W.r.t code below I can not declare the type of fetch-into-variable as the underlying table's %ROWTYPE because the SYS_REFCURSOR is on a select that joins two tables and also selects a few functions called on the attributes of the underlying two tables; i.e I can't declare as L_RECORD T%ROWTYPE
---
DECLARE
P_RS SYS_REFCURSOR;
L_RECORD P_RS%ROWTYPE;
BEGIN
CAPITALEXTRACT(
P_RS => P_RS
);
OPEN P_RS;
LOOP
BEGIN
FETCH P_RS INTO L_RECORD;
EXIT WHEN P_RS%NOTFOUND;
...
EXCEPTION
WHEN OTHERS THEN
...
END;
END LOOP;
CLOSE P_RS;
END;
--------
CREATE or REPLACE PROCEDURE CAPITALEXTRACT
(
p_rs OUT SYS_REFCURSOR
) AS
BEGIN
OPEN p_rs for
select t.*,tminusone.*, f(t.cash), g(t.cash) FROM T t, TMINUSONE tminusone
where t.ticket=tminusone.ticket;
END CAPITALEXTRACT;
Of course I don't want to define a static table R with columns as returned in the SYS_REFCURSOR and then declare as L_RECORD R%ROWTYPE.
And hence the question:
how to declare %ROWTYPE of a variable that is a weakly typed SYS_REFCURSOR ?
The short answer is, you can't. You'd need to define a variable for each column that wil be returned.
DECLARE
P_RS SYS_REFCURSOR;
L_T_COL1 T.COL1%TYPE;
L_T_COL1 T.COL2%TYPE;
...
And then fetch into the list of columns:
FETCH P_RS INTO L_T_COL1, L_T_COL2, ... ;
This is painful but manageable as long as you know what you're expecting in the ref cursor. Using T.* in your procedure makes this fragile though, as adding a column to the table would break the code that thinks it knows what columns there are and what order they're in. (You can also break it between environments if the tables aren't built consistently - I've seen places where column ordering is different in different environments). You'll probably want to make sure you're only selecting the columns you really care about anyway, to avoid having to define variables for things you'll never read.
From 11g you can use the DBMS_SQL package to convert your sys_refcursor into a DBMS_SQL cursor, and you can interrogate that to determine the columns. Just as an example of what you can do, this will print out the value of every column in every row, with the column name:
DECLARE
P_RS SYS_REFCURSOR;
L_COLS NUMBER;
L_DESC DBMS_SQL.DESC_TAB;
L_CURS INTEGER;
L_VARCHAR VARCHAR2(4000);
BEGIN
CAPITALEXTRACT(P_RS => P_RS);
L_CURS := DBMS_SQL.TO_CURSOR_NUMBER(P_RS);
DBMS_SQL.DESCRIBE_COLUMNS(C => L_CURS, COL_CNT => L_COLS,
DESC_T => L_DESC);
FOR i IN 1..L_COLS LOOP
DBMS_SQL.DEFINE_COLUMN(L_CURS, i, L_VARCHAR, 4000);
END LOOP;
WHILE DBMS_SQL.FETCH_ROWS(L_CURS) > 0 LOOP
FOR i IN 1..L_COLS LOOP
DBMS_SQL.COLUMN_VALUE(L_CURS, i, L_VARCHAR);
DBMS_OUTPUT.PUT_LINE('Row ' || DBMS_SQL.LAST_ROW_COUNT
|| ': ' || l_desc(i).col_name
|| ' = ' || L_VARCHAR);
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(L_CURS);
END;
/
That's not of much practical use, and for brevity I'm treating every value as a string since I just want to print it anyway. Look at the docs and search for examples for more practical applications.
If you only want a few columns from your ref cursor you could, I suppose, loop around l_desc and record the position where column_name is whatever you're interested in, as a numeric variable; you could then refer to the column by that variable later where you would normally use the name in a cursor loop. Depends what you're doing with the data.
But unless you're expecting to not know the column order you're getting back, which is unlikely since you seem to control the procedure - and assuming you get rid of the .*s - you're probably much better off reducing the returned columns to the minimum you need and just declaring them all individually.