How to validate a query without executing in PowerBuilder - validation

wondering if there is way to validate a query before executing
Is there way to check/validate Query without executing it?

One way that we validate SQL is to add a condition to the SQL that could never be true.
Example:
long ll_rc
long ll_result
string ls_sql, ls_test
string ls_message
//Arbitrary SQL
ls_sql = "SELECT * FROM DUAL"
//This SQL when executed will always return 0 if successful.
ls_test = "select count(*) from ( " + ls_sql + " WHERE 1 = 2 )"
DECLARE l_cursor DYNAMIC CURSOR FOR SQLSA ;
PREPARE SQLSA FROM :ls_test;
OPEN DYNAMIC l_cursor;
ll_rc = SQLCA.SQLCODE
choose case ll_rc
case 0
//Success
ls_message = "SQL is properly formed"
case 100
//Fetched row not found. This should not be the case since we only opened the cursor
ls_message = SQLCA.SQLERRTEXT
case -1
//Error; the statement failed. Use SQLErrText or SQLDBCode to obtain the detail.
ls_message = SQLCA.SQLERRTEXT
end choose
CLOSE l_cursor ; //This will fail if open cursor failed.
messagebox( "Result", ls_message )
Note: If your SQL is VERY complicated, which I suspect it isn't, the database optimizer may take several seconds to prepare your SQL. It will be significantly less time than if you run the entire query.

Since the database is the final arbitrator for what is "valid" (table and column names and such) the general answer is no. Now you could come up with a class in PB which checks statement syntax, object names, etc. so you wouldn't have to touch the db but it would be obsolete as soon as any changes were made to the db.

Put the select statement in any script and compile it. Part of the work will be to check the SQL syntax against the database you are connected to.
Watch out: you need at least one bound variable in the column list of your SQL statement. This is not the case for other DML statements.
Example:
in my case:
select noms into :ls_ttt from contacts;
results in a message Unknown columns 'noms' in 'field list'.
However,
select nom into :ls_ttt from contacts;
does not show any error.
Hope this helps.

Related

Retrieving data from oracle table using scala jdbc giving wrong results

I am using scala jdbc to check whether a partition exists for an oracle table. It is returning wrong results when an aggregate function like count(*) is used.
I have checked the DB connectivity and other queries are working fine. I have tried to extract the value of count(*) using an alias, But it failed. Also tried using getString. But it failed.
Class.forName(jdbcDriver)
var connection = DriverManager.getConnection(jdbcUrl,dbUser,pswd)
val statement = connection.createStatement()
try{
val sqlQuery = s""" SELECT COUNT(*) FROM USER_TAB_PARTITIONS WHERE
TABLE_NAME = \'$tableName\' AND PARTITION_NAME = \'$partitionName\' """
val resultSet1 = statement.executeQuery(sqlQuery)
while(resultSet1.next())
{
var cnt=resultSet1.getInt(1)
println("Count="+cnt)
if(cnt==0)
// Code to add partition and insert data
else
//code to insert data in existing partition
}
}catch(Exception e) { ... }
The value of cnt always prints as 0 even though the oracle partition already exists. Can you please let me know what is the error in the code? Is this giving wrong results because I am using scala jdbc to get the result of an aggregate function like count(*)? If yes, then what would be the correct code? I need to use scala jdbc to check whether the partition already exists in oracle and then insert data accordingly.
This is just a suggestion or might be the solution in your case.
Whenever you search the metadata tables of the oracle always use UPPER or LOWER on both side of equal sign.
Oracle converts every object name in to the upper case and store it in the metadata unless you have specifically provided the lower case object name in double quotes while creating it.
So take an following example:
-- 1
CREATE TABLE "My_table_name1" ... -- CASE SENSISTIVE
-- 2
CREATE TABLE My_table_name2 ... -- CASE INSENSITIVE
In first query, we used double quotes so it will be stored in the metadata of the oracle as case sensitive name.
In second query, We have not used double quotes so the table name will be converted into the upper case and stored in the metadata of the oracle.
So If you want to create a query against any metadata in the oracle which include both of the above cases then you can use UPPER or LOWER against the column name and value as following:
SELECT * FROM USER_TABLES WHERE UPPER(TABLE_NAME) = UPPER('<YOUR TABLE NAME>');
Hope, this will help you in solving the issue.
Cheers!!

Getting Unknown Command error on IF-THEN-ELSE

I have the following query that I am using in Oracle 11g
IF EXISTS (SELECT * FROM EMPLOYEE_MASTER WHERE EMPID='ABCD32643')
THEN
update EMPLOYEE_MASTER set EMPID='A62352',EMPNAME='JOHN DOE',EMPTYPE='1' where EMPID='ABCD32643' ;
ELSE
insert into EMPLOYEE_MASTER(EMPID,EMPNAME,EMPTYPE) values('A62352','JOHN DOE','1') ;
END IF;
On running the statement I get the following output:
Error starting at line : 4 in command -
ELSE
Error report -
Unknown Command
1 row inserted.
Error starting at line : 6 in command -
END IF
Error report -
Unknown Command
The values get inserted with error when I run it directly. But when I try to execute this query through my application I get an oracle exception because of the error generated :
ORA-00900: invalid SQL statement
And hence the values are not inserted.
I am relatively new to Oracle. Please advise on what's wrong with the above query so that I could run this query error free.
If MERGE doesn't work for you, try the following:
begin
update EMPLOYEE_MASTER set EMPID='A62352',EMPNAME='JOHN DOE',EMPTYPE='1'
where EMPID='ABCD32643' ;
if SQL%ROWCOUNT=0 then
insert into EMPLOYEE_MASTER(EMPID,EMPNAME,EMPTYPE)
values('A62352','JOHN DOE','1') ;
end if;
end;
Here you you the update on spec, then check whether or not you found a matching row, and insert in case you didn't.
"what's wrong with the above query "
What's wrong with the query is that it is not a query (SQL). It should be a program snippet (PL/SQL) but it isn't written as PL/SQL block, framed by BEGIN and END; keywords.
But turning it into an anonymous PL/SQL block won't help. Oracle PL/SQL does not support IF EXISTS (select ... syntax.
Fortunately Oracle SQL does support MERGE statement which does the same thing as your code, with less typing.
merge into EMPLOYEE_MASTER em
using ( select 'A62352' as empid,
'JOHN DOE' as empname,
'1' as emptype
from dual ) q
on (q.empid = em.empid)
when not matched then
insert (EMPID,EMPNAME,EMPTYPE)
values (q.empid, q.empname, q.emptype)
when matched then
update
set em.empname = q.empname, em.emptype = q.emptype
/
Except that you're trying to update empid as well. That's not supported in MERGE. Why would you want to change the primary key?
"Does this query need me to add values to all columns in the table? "
The INSERT can have all the columns in the table. The UPDATE cannot change the columns used in the ON clause (usually the primary key) because that's a limitation of the way MERGE works. I think it's the same key preservation mechanism we see when updating views. Find out more.

Re-use a cursor instead re-open

I have a table with employees and for each employees have few bills.
I declare 2 cursors, one for all employees(distinct) and second cursor for all bills for one employees. Now, open 1st cursor with all employees, fetch one, open second cursor (based on employee from 1st cursor) with all bills for employee. To reuse a second cursor for all employees, I open and close second cursor for each employee. This thing spend a lot of time. How to reuse a second cursor instead reopen or any good idea?
Part of code in Pro*C:
struct sforc1 {
long nis_rad[ROWS_FETCHED_C1];
long sec_nis[ROWS_FETCHED_C1];
/*char f_fact[9];
long sec_rec;*/
}forc1;
struct sforc2 {
long nis_rad[ROWS_FETCHED_C2];
long sec_nis[ROWS_FETCHED_C2];
char f_fact[ROWS_FETCHED_C2][9];
long sec_rec[ROWS_FETCHED_C2];
char f_p_camb_est[ROWS_FETCHED_C2][9];
char op_cambest[ROWS_FETCHED_C2][9];
}forc2;
void main (void)
{
exec sql declare c1 cursor for
select distinct nis_rad, sec_nis
from recibos
where ((imp_tot_rec - imp_cta)>0) and f_p_camb_est = '29991231';
exec sql declare c2 cursor for
select nis_rad, sec_nis, f_fact, sec_rec, f_p_camb_est, op_cambest
from recibos
where ((imp_tot_rec - imp_cta)>0) and f_p_camb_est = '29991231' and nis_rad = :forc1.nis_rad[i] and sec_nis=:forc1.sec_nis[i];
exec sql open c1;
while(1){
exec sql fetch c1 into :forc1;
rows_this_time1 = sqlca.sqlerrd[2]-rows_before1;
rows_before1 = sqlca.sqlerrd[2];
if (rows_this_time1==0){
break;
}
for(i=0;i<rows_this_time1;++i){
exec sql open c2;
rows_before2 = 0;
while(1){
exec sql fetch c2 into :forc2;
rows_this_time2 = sqlca.sqlerrd[2]-rows_before2;
rows_before2=sqlca.sqlerrd[2];
if(rows_this_time2==0){
break;
}
for(j=0;j<rows_this_time2;++j){
strcpy(forc2.f_p_camb_est[j], "20161212");
strcpy(forc2.op_cambest[j], "SIMD0943");
}
EXEC SQL
update recibos
set f_p_camb_est = :forc2.f_p_camb_est,
op_cambest = :forc2.op_cambest
where nis_rad = :forc2.nis_rad
and sec_nis = :forc2.sec_nis
and f_fact = :forc2.f_fact
and sec_rec = :forc2.sec_rec;
}
exec sql close c2;
}
exec sql close c1;
exec sql commit;
exec sql open c1;
rows_before1 = 0;
}
exec sql close c1;
}
nis_rad and sec_nis is a employee_id(primary key). Each nis_rad have few bills f_fact(bills)
For processing 10000 nis_rad's spend 30 min, and 28-29 min is for re-open second cursor(c2)
UP. Deleted previously example
I would say inplace of opening explicit cursor, let oracle use implicit cursor and use simple loops to achieve your requirements:
declare
begin
for rec in ( select employee_id
from employees )
Loop
for rcrd in (Select bill from employee_bill
where employee_id = rec.employee_id)
loop
/***Process your bills***/
end loop;
end loop;
end;
I've edited an answer.
You can't reuse date from cursor without opening it again unless you cache all results in memory. Cursor is like pointer to data when you read record it already points to next so you can't get back.
Problem in your code is using SQL imperative way. You shouldn't pull all records to your application and then apply logic. Think about how to write a query that will return only records you need to process. It may be even faster to execute 2-3 queries that will return records for each section of your code than pull all data and then check logic in application.
The problem with your code is that it's doing an awful lot of context switching, so it's no wonder that it's slow.
From what I can tell, your code is doing something like:
In Pro*C, go to the database
In the database, open the first cursor
Back in Pro*C, ask the database for a row from the cursor
The database sends back a row
In Pro*C, go to the database
In the database, open the second cursor, passing in the values from the first cursor
In Pro*C, ask the database for a row from the second cursor
The database sends back a row
In Pro*C, ask the database to update the row(s) based on the results from the second cursor
The database updates the rows.
Repeat steps 7-10 until there are no more rows to fetch from cursor 2
Repeat steps 3.11 until there are no more rows to fetch from cursor 1
I think you can see that there's an awful lot of unnecessary to-ing and fro-ing going on there... and in fact, it's likely that that's where the majority of your time is being spent.
Were it not for your mentor's requirements (which I assume are for teaching purposes only), you could do this in a single update statement, like so:
update recibos
set f_p_camb_est = '20161212',
op_cambest = 'SIMD0943'
where (imp_tot_rec - imp_cta) > 0
and f_p_camb_est = '29991231';
(N.B. if f_p_camb_est is of DATE datatype, then the string literals need to be converted into DATEs using DATE or to_date(), e.g. DATE '29991231', to_date('20161212', 'yyyymmdd'), unless you're going to pass in a parameter that's already of DATE datatype.)
Doing it this way means that you can plug the update statement into your Pro*C app and it will do:
In Pro*C, ask the database to execute the update statement
The database executes the update statement
Control is returned back to Pro*C.
That's 2 context switches in total - a massive reduction than from your original method!
However, your mentor has asked for (IMHO) the daft way of updating the rows by doing it one nis_rad at a time.
In that case, you are still far better off doing the bulk of the work in the database - there is *no* point in sending rows back to your app from the database only for your app to do nothing with them except pass them back into the database. The way you would do this on the database side is in PL/SQL, like so:
begin
for rec in (select distinct nis_rad, sec_nis
from recibos
where (imp_tot_rec - imp_cta) > 0
and f_p_camb_est = '29991231')
loop
update recibos
set f_p_camb_est = '20161212',
op_cambest = 'SIMD0943'
where nis_rad = rec.nis_rad
and (imp_tot_rec - imp_cta) > 0
and f_p_camb_est = '29991231';
end loop;
end;
/
You could either call this directly from Pro*C as an anonymous block or you could create it as a procedure in the database (potentially with parameters), and then call the procedure from Pro*C.
Either way, the overall flow looks like:
In Pro*C, ask the database to execute the PL/SQL
The database executes the PL/SQL
Control is returned back to Pro*C.
I know this looks like 2 context switches, but you also need to take account of the context switching within the database, when it switches between the PL/SQL and SQL engines, which looks something like:
The PL/SQL engine requests a cursor
The SQL engine opens the cursor
The PL/SQL engine requests a row from the cursor
The SQL engine passes a row back
The PL/SQL engine stored the information in the rec record
The PL/SQL engine asks the SQL engine to execute the update statement, based on the nis_rad value from the rec record
The SQL engine runs the update statement
Repeat steps 1 - 7 until no more rows are returned
which, as I'm sure you can see is a lot more than the 2 context switches we had overall if you had just run the single update statement instead.
Hopefully that clears things up a bit for you?

ORA-01403: no data found IN ORACLE PL/SQL

I have an oracle error executing this PL/SQL in the second line: SELECT ....
But for God's sake ! I've already check if there is some null values
IF zocRole IS NOT NULL and devices.unit_id IS NOT NULL THEN
SELECT unit_role_id INTO unitRoleId FROM T_UNIT_ROLE WHERE role_id = zocRole AND unit_id = devices.unit_id;
END IF;
As mentioned above, this exception is thrown because your implicit cursor returns no rows. You would also get an exception if more than one row is returned.
You could instead use an Explicit Cursor Oracle Documents This is really just a named SQL statement (into which you can pass parameters if you like).
You then open the cursor, fetch (each fetch will attempt to retrieve one row) and close. You can then check whether the fetch returned any data. It takes slightly longer to code but can look cleaner.
I remember that years ago there was some debate about the relative speed of implicit vs explicit cursors but I've not heard anyone talk about this for a long time so I assume they perform the same
The best way to control the execution in a procedure/function plsql is adding blocks: BEGIN/EXCEPTION.
IF zocRole IS NOT NULL AND devices.unit_id IS NOT NULL
THEN
BEGIN
SELECT unit_role_id
INTO unitRoleId
FROM T_UNIT_ROLE
WHERE role_id = zocRole
AND unit_id = devices.unit_id
;
EXCEPTION
WHEN OTHERS
THEN dbms_output.put_line(SQLCODE||'-'||SUBSTR(SQLERRM, 1, 200));
END
;
END IF
;

Ref Cursor Exceptions

I have a couple of questions arounbd ref_cursors. Below is a ref_cursor that returns a a single row to a Unix calling script based on what is passed in and although the select looks a little untidy, it works as expected.
My first question is that in the select I join to a lookup table to retrieve a single lookup value 'trigram' and on testing found that this join will occasionally fail as no value exists. I have tried to capture this with no_data_found and when others exception but this does not appear to be working.
Ideally if the join fails I would still like to return the values to the ref_cursor but add something like 'No Trigram' into the trigram field - primarily I want to capture exception.
My second question is more general about ref_cursors - While initially I have created this in its own procedure, it is likely to get called by the main processing procedure a number of times, one of the conditions requires a seperate select but the procedure would only ever return one ref_cur when called, can the procdure ref_cur out be associated with 2 queries.
CREATE OR REPLACE PROCEDURE OPC_OP.SiteZone_status
(in_site_id IN AW_ACTIVE_ALARMS.site_id%TYPE
,in_zone_id IN AW_ACTIVE_ALARMS.zone_id%TYPE
,in_mod IN AW_ACTIVE_ALARMS.module%TYPE
,p_ResultSet OUT TYPES.cursorType
)
AS
BEGIN
OPEN p_ResultSet FOR
SELECT a.site_id,'~',a.zone_id,'~',b.trigram,'~',a.module,'~',a.message_txt,'~',a.time_stamp
FROM AW_ACTIVE_ALARMS a, AW_TRIGRAM_LOCATION b
WHERE a.site_id = b.site_id
AND a.zone_id = b.zone_id
AND a.site_id = in_site_id
AND a.zone_id = in_zone_id
AND a.module LIKE substr(in_mod,1,3)||'%'
AND weight = (select max(weight) from AW_ACTIVE_ALARMS c
WHERE c.site_id = in_site_id
AND c.zone_id = in_zone_id
AND c.module LIKE substr(in_mod,1,3)||'%');
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE('No Data Found');
END SiteZone_status;
I have modified my code to adopt answers provided and this now works as expected as a standalone procedure within my package, which when called via a UNIX script using:
v_process_alarm=$(sqlplus -s user/pass <
set colsep ','
set linesize 500
set pages 0 feedback off;
set serveroutput on;
VARIABLE resultSet REFCURSOR
EXEC alarm_pkg.rtn_active_alarm($site,$zone,$module, :resultSet);
PRINT :resultSet
EOF
)
However the procedure returning the ref cursor is to be called from the main processing procedure as I only want to return values if certain criteria are met. I have add an out refcurosr to my main procedure and set a variable to match, I then call my ref cursor procedure from here but this fails to compile
with the message 'Wrong number or types of argument in call'
My question is what is the correct way to call a procedure that has out refcursor from within a procedure and then return these values from there back to the calling script.
Oracle doesn't know whether a query will return rows until you fetch from the cursor. And it is not an error for a query to return 0 rows. So you will never get a no_data_found exception from opening a cursor. You'll only get that if you do something like a select into a local variable in which case a query that returns either 0 or more than 1 row is an error.
It sounds like you want to do an outer join to the AW_TRIGRAM_LOCATION table rather than a current inner join. This will return data from the other tables even if there is no matching row in aw_trigram_location. That would look something like this (I have no idea why every other column is a hard-coded tilde character, that seems exceptionally odd)
SELECT a.site_id,'~',
a.zone_id,'~',
nvl(b.trigram, 'No Trigram Found'),'~',
a.module,'~',
a.message_txt,'~',
a.time_stamp
FROM AW_ACTIVE_ALARMS a
LEFT OUTER JOIN AW_TRIGRAM_LOCATION b
ON( a.site_id = b.site_id AND
a.zone_id = b.zone_id )
WHERE a.site_id = in_site_id
AND a.zone_id = in_zone_id
AND a.module LIKE substr(in_mod,1,3)||'%'
AND weight = (select max(weight)
from AW_ACTIVE_ALARMS c
WHERE c.site_id = in_site_id
AND c.zone_id = in_zone_id
AND c.module LIKE substr(in_mod,1,3)||'%');
I'm not quite sure that I understand your last question. You can certainly put logic in your procedure to run a different query depending on an input parameter. Something like
IF( <<some condition>> )
THEN
OPEN p_ResultSet FOR <<query 1>>
ELSE
OPEN p_ResultSet FOR <<query 2>>
END IF;
Whether it makes sense to do this rather than adding additional predicates or creating separate procedures is a question you'd have to answer.
You can use a left outer join to your look-up table, which is clearer if you use ANSI join syntax rather than Oracle's old syntax. If there is no record in AW_TRIGRAM_LOCATION then b.trigram will be null, and you can then use NVL to assign a dummy value:
OPEN p_ResultSet FOR
SELECT a.site_id,'~',a.zone_id,'~',NVL(b.trigram, 'No Trigram'),'~',
a.module,'~',a.message_txt,'~',a.time_stamp
FROM AW_ACTIVE_ALARMS a
LEFT JOIN AW_TRIGRAM_LOCATION b
ON b.site_id = a.site_id
AND b.zone_id = a.zone_id
WHERE a.zone_id = in_zone_id
AND a.module LIKE substr(in_mod,1,3)||'%'
AND weight = (select max(weight) from AW_ACTIVE_ALARMS c
WHERE c.site_id = in_site_id
AND c.zone_id = in_zone_id
AND c.module LIKE substr(in_mod,1,3)||'%');
You won't get NO_DATA_FOUND opening a cursor, only when you fetch from it (depending on what is actually consuming this). It's a bad idea to catch WHEN OTHERS anyway - you would want to catch WHEN NO_DATA_FOUND, though it wouldn't help here. And using dbms_output to report an error relies on the client enabling its display, which you can't generally assume.

Resources