Procedure with Implicit cursor is compiling but not printing - loops

i am currently stuck on this pl/sql problem, i am trying to gather all the information of a APPLICANT who APPLIES to a certain POSITION (3 different tables) into a stored procedure.
Unfortunately i am very new to oracle and pl/sql so i think my joins may be sloppy aswell as my main problem of dbms_output.put_line is not printing out the data that i need. I figure maybe it is in the wrong place in the code block or there is a problem coming all the way down from my join statements.
enter code here
SET ECHO ON
SET FEEDBACK ON
SET LINESIZE 100
SET PAGESIZE 100
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE APPLICANTS IS
first_name APPLICANT.FNAME%TYPE;
last_name APPLICANT.LNAME%TYPE;
position_number APPLIES.PNUMBER%TYPE;
position_title POSITION.TITLE%TYPE;
str VARCHAR(300);
CURSOR fnameCursor IS
SELECT FNAME
FROM APPLICANT;
BEGIN
FOR fnameCursor IN (SELECT APPLICANT.LNAME, APPLIES.PNUMBER,
POSITION.TITLE INTO last_name, position_number, position_title
FROM APPLICANT JOIN APPLIES ON APPLICANT.ANUMBER =
APPLIES.ANUMBER
JOIN POSITION ON POSITION.PNUMBER = APPLIES.PNUMBER
WHERE FNAME = first_name
ORDER BY LNAME DESC)
LOOP
str := position_number || '' || first_name || '' || last_name || ': ' ||
position_title;
dbms_output.put_line(str);
--EXIT WHEN fnameCursor%NOTFOUND;
END LOOP;
END APPLICANTS;
/
EXECUTE APPLICANTS;

It is surprising to know that the procedure is compiling. You are using an INTO clause inside an implicit cursor query. Also, I believe first_name should come as an argument to your procedure but you have not mentioned it.
More importantly, the columns selected/aliased within the cursor should be referred within the loop using cursor's record variable fnamecursor
CREATE OR REPLACE PROCEDURE APPLICANTS(first_name APPLICANT.FNAME%TYPE)
IS
str VARCHAR(300);
BEGIN
FOR fnamecursor IN (
SELECT applicant.lname as last_name,
applies.pnumber as position_number,
position.title as position_title
FROM applicant
JOIN applies ON applicant.anumber = applies.anumber
JOIN position ON position.pnumber = applies.pnumber
WHERE fname = first_name
ORDER BY lname DESC
) LOOP
str := fnamecursor.position_number || ' ' || first_name || ' ' ||
fnamecursor.last_name || ': ' || fnamecursor.position_title;
dbms_output.put_line(str);
END LOOP;
END applicants;
/

Related

Write a pl sql block to fetch distinct department ids

Write a pl sql block to fetch distinct department idsenter image description here
That would be something like this (read comments within code).
Though, the exercise is just the opposite of what I'd expect because - normally - departments exist, but nobody works in them. With enforced referential integrity constraint, you couldn't have situation as described in the exercise. After all, that's what your homework text suggests.
set serveroutput on
declare
l_dept_name dept.dept_name%type;
begin
-- loop through all distinct DEPT_ID values in the EMP table
for cur_r in (select distinct e.dept_id
from emp e
)
loop
-- starting inner BEGIN-EXCEPTION-END block so that
-- loop goes on in case of an error
begin
-- retrieve department details
select d.dept_name
into l_dept_name
from dept d
where d.dept_id = cur_r.dept_id;
-- display department info (because SELECT returned something for CUR_R.DEPT_ID)
dbms_output.put_line('Dept ID: ' || cur_r.dept_id || ' is ' || l_dept_name);
-- handle errors (because SELECT didn't return anything for CUR_R.DEPT_ID)
exception
when no_data_found then
dbms_output.put_line('Dept ID: ' || cur_r.dept_id ||': no additional info');
end;
end loop;
end;
/

Oracle: update multiple columns with dynamic query

I am trying to update all the columns of type NVARCHAR2 to some random string in my database. I iterated through all the columns in the database of type nvarchar2 and executed an update statement for each column.
for i in (
select
table_name,
column_name
from
user_tab_columns
where
data_type = 'NVARCHAR2'
) loop
execute immediate
'update ' || i.table_name || 'set ' || i.column_name ||
' = DBMS_RANDOM.STRING(''X'', length('|| i.column_name ||'))
where ' || i.column_name || ' is not null';
Instead of running an update statement for every column of type nvarchar2, I want to update all the nvarchar columns of a particular table with a single update statement for efficiency(that is, one update statement per 1 table). For this, I tried to bulk collect all the nvarchar columns in a table, into a temporary storage. But, I am stuck at writing a dynamic update statement for this. Could you please help me with this? Thanks in advance!
You can try this one. However, depending on your table it could be not the fastest solution.
for aTable in (
select table_name,
listagg(column_name||' = nvl2('||column_name||', DBMS_RANDOM.STRING(''XX'', length('||column_name||')), NULL)') WITHIN GROUP (ORDER BY column_name) as upd,
listagg(column_name) WITHIN GROUP (ORDER BY column_name) as con
from user_tab_columns
where DATA_TYPE = 'NVARCHAR2'
group by table_name
) loop
execute immediate
'UPDATE '||aTable.table_name ||
' SET '||aTable.upd ||
' WHERE COALESCE('||aTable.con||') IS NOT NULL';
end loop;
Resulting UPDATE (verify with DBMS_OUTPUT.PUT_LINE(..)) should look like this:
UPDATE MY_TABLE SET
COL_A = nvl2(COL_A, DBMS_RANDOM.STRING('XX', length(COL_A)), NULL),
COL_B = nvl2(COL_B, DBMS_RANDOM.STRING('XX', length(COL_B)), NULL)
WHERE COALESCE(COL_A, COL_B) IS NOT NULL;
Please try this:
DECLARE
CURSOR CUR IS
SELECT
TABLE_NAME,
LISTAGG(COLUMN_NAME||' = DBMS_RANDOM.STRING(''X'', length(NVL('||
COLUMN_NAME ||',''A''))',', ')
WITHIN GROUP (ORDER BY COLUMN_ID) COLUMN_NAME
FROM DBA_TAB_COLUMNS
WHERE DATA_TYPE = 'NVARCHAR2'
GROUP BY TABLE_NAME;
TYPE TAB IS TABLE OF CUR%ROWTYPE INDEX BY PLS_INTEGER;
T TAB;
S VARCHAR2(4000);
BEGIN
OPEN CUR;
LOOP
FETCH CUR BULK COLLECT INTO T LIMIT 1000;
EXIT WHEN T.COUNT = 0;
FOR i IN 1..T.COUNT LOOP
S := 'UPDATE ' || T(i).TABLE_NAME || ' SET ' || T(i).COLUMN_NAME;
EXECUTE IMMEDIATE S;
END LOOP;
END LOOP;
COMMIT;
END;
/
I think that would do it. But as I said in the comments, you need to validate the syntax since I don't have an Oracle instance to test it.
for i in (
select table_name,
'update || i.table_name || set ' ||
listagg( column_name || '= NLV( ' || column_name || ', '
|| 'DBMS_RANDOM.STRING(''X'', length('|| column_name ||') ) )'
|| ';'
) WITHIN GROUP (ORDER BY column_name) as updCommand
from user_tab_columns
where DATA_TYPE = 'NVARCHAR2'
group by table_name
) loop
execute immediate i.updCommand;
end loop;
If you find any error, let me know in the comments so I can fix it.

Concatenated string as parameter argument value does not give output in stored procedure

I am using SQL SERVER 2008 management studio.
I have a stored procedure which accepts several parameters.
One parameter name is #recvemail with default value as 'B'.
If the value of #recvemail is 'Y' then all the users who opted for email news letter should be displayed in the output.
If #recvemail is 'N' then users who do not wish to receive email letter should be included in the output.
If the value is 'B' then all the users should be displayed in the output,
whether they who opted for news letter or not opted for news letter.
Now for default value 'B' i tried the set the parameter argument values as 'Y', 'N' but I am not getting any output.
My stored proc is
Create procedure [dbo].[proc_getMembershipEmailbackup]
-- exec proc_getMembershipEmail #From=N'1991-06-11',#To=N'2017-06-14',#DevoteeName=N'',#MembershipName=N'Membership Life',#log_cond=N'',#recvemail=N'N'
#From varchar(10),
#To varchar(10),
#DevoteeName varchar(60)='',
#MembershipName varchar(100)='ALL',
#Email varchar(50)= '',
#Phoneno varchar(50)= '',
#MinAmount decimal =Null,
#MaxAmount decimal =NULL,
#log_cond Varchar(5),
#recvemail char(61)='B'
As
Begin
if #recvemail = 'B'
Begin
Set #recvemail='Y' + ',' + 'N'
End
end
Begin
if #MembershipName = 'ALL'
Begin
Set #MembershipName=''
End
end
begin
select
ROW_NUMBER() over (order by mu.f_name) as [No],
mu.f_name +' '+mu.l_name as DevoteeName ,
mu.pers_email as Email,
mu.home_phone as PhoneNo,
mp.name as MembershipName,
mu.recv_email as optedforemail,
d1.memb_id ,
d1.amt_due as amount_due,
CONVERT(varchar(10),d1.nextduedate,101) as duedate,
d1.pay_term
from d_user_memb d1
left join m_user mu on d1.user_id = mu.user_id
left join m_memb_pledge mp on d1.memb_id = mp.memb_id
where
d1.status in ('A','D','C')
AND(Isnull(#From,'') = '' or CONVERT(date,d1.nextduedate ) >= CONVERT(date,#From) )
and (Isnull(#To,'') = '' or CONVERT(date,d1.nextduedate ) <= CONVERT(date,#To ))
and (Isnull(#DevoteeName,'') = '' or (f_name +' '+l_name) like '%' + #DevoteeName + '%')
and (recv_email in (#recvemail ) )
and (Isnull(#MembershipName,'') = '' or (mp.name) = #MembershipName)
and (ISNULL (#Email, '') = '' or mu.pers_email = #Email)
and (ISNULL (#Phoneno, '') = '' or mu.home_phone = #Phoneno)
--and (ISNULL (#MinAmount,0) = 0 or cast(d1.amt_due as decimal(10,2)) >=#MinAmount)
--and (ISNULL (#MaxAmount, 0) =0 or cast(d1.amt_due as decimal(10,2)) <= #MaxAmount)
and d1.amt_due<>0
and mu.status='A'
and (
(
#log_cond = 'b'
AND d1.amt_due BETWEEN #MinAmount AND #MaxAmount
)
OR
(
#log_cond <> 'b'
AND #log_cond <> '0'
AND
(
isnull(#log_cond,'')=''
OR (#log_cond = '>' AND d1.amt_due > #MinAmount)
OR (#log_cond = '<' AND d1.amt_due < #MinAmount)
OR (#log_cond = '>=' AND d1.amt_due >= #MinAmount)
OR (#log_cond = '<=' AND d1.amt_due <= #MinAmount)
OR (#log_cond='=' AND d1.amt_due =#MinAmount)
)
)
OR
(
ISNULL(#log_cond,'')='0'
)
)
End
I tried to execute the procedure with values like this but I am not getting any output
use CHTLIVE
GO
exec proc_getMembershipEmailbackup #From=N'1800-06-11',#To=N'2099-06-14',#DevoteeName=N'',#log_cond='', #recvemail='B'
If the change the parameter value #recvemail from 'B' to 'Y', I am getting output correctly and all users who opted for email letter is displayed correctly in the output.
Similarly I changed the value from 'B' to 'N' I am getting the output correctly and all users who have not opted for email letter is displayed in the output.
Only for value 'B' I am not getting output. I should get the output such that all users who have either opted or not opted for email letter should be present in the output.
As Mark Sinkinson wrote in his comment, the IN() operator expects a comma separated list of arguments, but you have supplied it with a single argument that contains a comma separated list of values.
Change this part of the stored procedure:
and (recv_email in (#recvemail ) )
to this:
and (recv_email = #recvemail OR #recvemail = 'B')
The line
and (recv_email in (#recvemail ) )
is equivalent to
and recv_email = #recvemail
IN works against a list of arguments where you only pass a single argument. It doesn't parse the arguments' contents to see if any of them could be interpreted as a comma-separated list.
If you want to check against a list of options, create a table valued parameter and use it in your IN clause:
declare #emailoptions table (Option varchar(2))
IF (#recvemail = 'B' OR #recvemail = 'Y')
begin
insert into #emailoptions VALUES('Y')
end if
IF (#recvemail = 'B' OR #recvemail = 'N')
begin
insert into #emailoptions VALUES('N')
end if
This would allow you to joint your tables with #emailOptions
from d_user_memb d1
left join m_user mu on d1.user_id = mu.user_id
left join m_memb_pledge mp on d1.memb_id = mp.memb_id
inner join #emailOptions on Option=#recvc_email
This allows you to use more than two options, and the optimizer will be able to use any indexes that include recv_email to create a faster execution plan.

Execute Immediate in cursor on ibm db2

I'm having difficulties creating a SP in which I pass in a name of a table and query the SYS2 library to find out if it has an auto-increment field. If it does I query for the max value of that field in the table and then alter the table so the next used value is that result plus 1. This is for use when migrating production data over to development.
I'm not sure if it is possible to use "Execute Immediate" as part of a cursor declaration. I'm still fairly new to db2 in general, never mind for IBM. So any assistance would be greatly appreciated. If "Execute Immediate" is not allowed in a cursor declaration, how would I go about doing this?
I'm getting the error on the Cursor declaration (line 10), but here is the exact error code I'm getting:
SQL State: 42601
Vendor Code: -199
Message: [SQL0199] Keyword IMMEDIATE not expected. Valid tokens: <END-OF-STATEMENT>. Cause . . . . . : The keyword IMMEDIATE was not expected here. A syntax error was detected at keyword IMMEDIATE. The partial list of valid tokens is <END-OF-STATEMENT>. This list assumes that the statement is correct up to the unexpected keyword. The error may be earlier in the statement but the syntax of the statement seems to be valid up to this point. Recovery . . . : Examine the SQL statement in the area of the specified keyword. A colon or SQL delimiter may be missing. SQL requires reserved words to be delimited when they are used as a name. Correct the SQL statement and try the request again.
And then finally here is my SP
/* Creating procedure DLLIB.SETNXTINC# */
CREATE OR REPLACE PROCEDURE DLLIB.SETNXTINC#(IN TABLE CHARACTER (10) ) LANGUAGE SQL CONTAINS SQL PROGRAM TYPE SUB CONCURRENT ACCESS RESOLUTION DEFAULT DYNAMIC RESULT SETS 0 OLD SAVEPOINT LEVEL COMMIT ON RETURN NO
SET #STMT1 = 'SELECT COLUMN_NAME ' ||
'FROM QSYS2.SYSCOLUMNS ' ||
'WHERE TABLE_SCHEMA =''DLLIB'' and table_name = ''' || TRIM(TABLE) || '''' ||
'AND HAS_DEFAULT = ''I'' ' ||
'OR HAS_DEFAULT = ''J'';';
DECLARE cursor1 CURSOR FOR
EXECUTE IMMEDIATE #STMT1;
OPEN cursor1;
WHILE (sqlcode == 0){
FETCH cursor1 INTO field;
SET #STMT2 = 'ALTER TABLE DLLIB.' || TRIM(TABLE) || ''' ' ||
'ALTER COLUMN ' || TRIM(field) || ' RESTART WITH ( ' ||
'SELECT MAX(' || TRIM(field) || ') ' ||
'FROM DLLIB.' || TRIM(TABLE) || ');';
EXECUTE IMMEDIATE #STMT2;
};;
/* Setting label text for DLLIB.SETNXTINC# */
LABEL ON ROUTINE DLLIB.SETNXTINC# ( CHAR() ) IS 'Set the next auto-increment';
/* Setting comment text for DLLIB.SETNXTINC# */
COMMENT ON PARAMETER ROUTINE DLLIB.SETNXTINC# ( CHAR() ) (TABLE IS 'Table from DLLIB' ) ;
First off, you don't need to dynamically prepare you first statement.
Secondly, you can't use a SELECT in the RESTART WITH, you'll have to use 2 statements
Thirdly, if you use VARCHAR instead of CHAR, you don't need to use any TRIM()s
Lastly, using TABLE as a parameter name is bad practice as it is a reserved word.
You want something like so
CREATE OR REPLACE PROCEDURE QGPL.SETNXTINC#(IN MYTABLE VARCHAR (128) )
LANGUAGE SQL
MODIFIES SQL DATA
PROGRAM TYPE SUB
CONCURRENT ACCESS RESOLUTION DEFAULT
DYNAMIC RESULT SETS 0
OLD SAVEPOINT LEVEL
COMMIT ON RETURN NO
BEGIN
declare mycolumn varchar(128);
declare stmt2 varchar(1000);
declare stmt3 varchar(1000);
declare mymaxvalue integer;
-- Table known at runtime, a static statement is all we need
SELECT COLUMN_NAME INTO mycolumn
FROM QSYS2.SYSCOLUMNS
WHERE TABLE_SCHEMA = 'DLLIB'
AND TABLE_NAME = mytable
AND HAS_DEFAULT = 'I'
OR HAS_DEFAULT = 'J';
-- Need to use a dynamic statement here
-- as the affected table is not known till runtime
-- need VALUES INTO as SELECT INTO can not be used dynamically
SET STMT2 = 'VALUES (SELECT MAX(' || mycolumn || ') ' ||
'FROM DLLIB.' || mytable || ')' || 'INTO ?';
PREPARE S2 from stmt2;
EXECUTE S2 using mymaxvalue;
-- we want to restart with a value 1 more than the current max
SET mymaxvalue = mymaxvalue + 1;
-- Need to use a dynamic statement here
-- as the affected table is not known till runtime
SET STMT3 = 'ALTER TABLE DLLIB.' || mytable || ' ALTER COLUMN '
|| mycolumn || ' RESTART WITH ' || char(mymaxvalue);
EXECUTE IMMEDIATE STMT3;
END;
One more thing to consider, you might want to LOCK the table in exclusive mode prior to running STMT2; otherwise there's a possibility that a record with a higher value got added between the execution of STMT2 and STMT3.

Adding Dyanmic In() Conditions in Sql Server

Facing problem for generating SQL Server Query
In the Following query dynamic conditions are added to check whether value is null or not
Select *
From tblEmployees
where EmployeeName = Case
When #EmployeeName Is Not Null
Then #EmployeeName
Else EmployeeName
End
But I need to add IN () Conditions and the parameter with in the IN () could be null or blank also ,if the parameter /string which is passed to the IN condition is blank then i donot want to add that condition in the query.
So how can i Achieve this.A helping hand will be very useful for me.
Thanks and Regards,
D.Mahesh
Depending on value of your parameter (blank of not), you can create SQL string accordingly.
DECLARE #sqlCommand VARCHAR(1000)
IF(ISNULL(#YourParameter,'')='')
#sqlCommand = 'your query goes here'
ELSE
#sqlCommand = 'your query goes here'
and then, run it using dynamic query execution
EXEC (#sqlCommand)
If not dynamic query then,
SELECT ....
FROM ....
WHERE CASE WHEN ISNULL(#YourParameter,'')='' THEN '' ELSE EmployeeName END IN (ISNULL(#YourParameter,''))
See if this works...
I think the Dynamic query is the best solution, however you could put the "IS NULL" and "IS BLANK" condition in OR with your IN clause.
Something like that
Select *
From tblEmployees
where #EmployeeName is null or EmployeeName in (#EmployeeName)
When #EmployeeName is null, your IN clause will be ignored
If i get this right you have #EmployeeName = 'Name1,Name2,Name3' and you want to get the employees that is named Name1 or Name2 or Name3, also the variable #EmployeeName can be null or contain an empty string.
Instead of using IN you can split the string #EmployeeName on , and store it in a table variable or temporary table. Then you can use that table in a join against tblEmployees to get the rows you need.
There are a lot of posts in S.O. about how to split a string. Here is one recent variant.
Group by sql query on comma joined column
This will work for SQL Server 2005 or later.
declare #EmployeeName varchar(100) = 'Name2,Name3,Name5'
-- Null or empty will have a comma
set #EmployeeName = coalesce(#EmployeeName, '') + ','
-- cteNames splits the string to rows
;with cteNames
as
(
select
left(#EmployeeName, charindex(',', #EmployeeName)-1) as Name,
right(#EmployeeName, len(#EmployeeName)-charindex(',', #EmployeeName)) as EmployeeName
union all
select
left(EmployeeName, charindex(',', EmployeeName)-1) as Name,
right(EmployeeName, len(EmployeeName)-charindex(',', EmployeeName)) as EmployeeName
from cteNames
where charindex(',', EmployeeName) > 1
)
select E.*
from tblEmployees as E
inner join cteNames as N
on E.Name = N.Name or
#EmployeeName = ','
-- #EmployeeName = ',' will give you all names when #EmployeeName is null of empty

Resources