Searching data from table by passing table name as a parameter in PL/SQL - oracle

Is this stored procedure in oracle is correct for searching data from table by passing table name as a parameter
CREATE OR REPLACE PROCEDURE bank_search_sp
(
p_tablename IN VARCHAR2,
p_searchname IN VARCHAR2,
p_bankcode OUT VARCHAR2,
p_bankname OUT VARCHAR2,
p_dist_code OUT NUMBER
)
AS
v_tem VARCHAR2(5000);
BEGIN
v_tem := 'SELECT bankcode,bankname,dist_code FROM ' || UPPER (p_tablename) || '
WHERE bankname LIKE '''|| p_searchname||'''';
EXECUTE IMMEDIATE v_tem
INTO p_bankcode,p_bankname,p_dist_code
USING p_searchname ;
END bank_search_sp;

If you need this procedure, then I guess that you have several tables with the columns bankcode, bankname and dist_code. If this is true, then try to normalize your model if possible.
The USING term is the correct approach, but you have to use the parameter in your query.
To avoid SQL injection, you could use dbms_assert.sql_object_name.
This should work for you:
v_tem := 'SELECT bankcode, bankname, dist_code FROM '
|| dbms_assert.sql_object_name(p_tablename)
|| ' WHERE bankname LIKE :1';
Your EXECUTE IMMEDIATE will throw an exception when finding no row or more than one row, so using LIKE might not be a good idea.
Questions that you should ask yourself:
Is the model properly normalized?
Do you really need to use LIKE, or is = what you want?
If you want to use LIKE, how should the program deal with NO_DATA_FOUND / TOO_MANY_ROWS exceptions?

Related

ORA-01403 when referencing page item in PL/SQL

I am building an Oracle ApEx page to show charts of data. The data are entered on to a table as inputs, but the actual calculated metric can be different depending on what KPI is being viewed (AVG(VALUE_1); SUM(VALUE_1)/SUM(VALUE_2); etc.); these calculations are stored on a separate table. Since I want everything to be dynamic for use interactions, I am using a source of PL/SQL Function Body returning SQL Query. Here is a simplified version
DECLARE
SQL_STMT VARCHAR2 (32767);
CALC_CLAUSE VARCHAR2 (4000);
BEGIN
SELECT CALCULATION
INTO CALC_CLAUSE
FROM KPI_TYPES
WHERE ID = 101;
--when the value itself is entered, the results display as intended
SQL_STMT := 'SELECT DATE, ' || CALC_CLAUSE || ' KPI_VALUE
FROM DATA_VALUES
WHERE TYPE_ID = :P_KPI
GROUP BY DATE';
RETURN SQL_STMT;
END;
As noted above, this functions as expected when the KPI's ID is manually entered, but I want this to be dynamic. I have a select list page item (P_KPI) for the user to choose the viewed KPI. Its value is actually set from a previous page with validation, so it should never be null.
When I update the CALC_CLAUSE condition:
WHERE ID = :P_KPI
I receive the following error:
ORA-01403: no data found
How can I reference this page item in the function?
Code you posted is invalid (wouldn't compile) and explanation you gave is wrong. Although it is a simplified version, I'd prefer if you posted accurate information.
You said that SQL statement is
SELECT DATE, '||CALC_CLAUSE||' KPI_VALUE
FROM DATA_VALUES ...
while CALC_CLAUSE looks like
WHERE ID = :P_KPI
so the whole query looks like
SELECT DATE, WHERE ID = :P_KPI KPI_VALUE
FROM DATA_VALUES ...
which just doesn't make sense. What is that CALC_CLAUSE, after all? Shouldn't it be AVG(VALUE_1) or something like that?
As of :P_KPI item whose value seems to be NULL: it doesn't matter that you see it on the screen - it should be in the session state. The simplest way to do that is to submit the page, so - did you do it?
Try to create a stored function (in the database, not in Apex) and pass it P_KPI value as a parameter. Then test what you get, as well as the result. When working with dynamic SQL, it is a good idea to DBMS_OUTPUT.PUT_LINE the resulting statement (that would be SQL_STMT in your example), copy/paste it into SQL*Plus (or any other tool you use, such as SQL Developer) and see whether it works correctly.
For example:
CREATE OR REPLACE FUNCTION f_stmt (par_kpi IN NUMBER)
RETURN VARCHAR2
IS
sql_stmt VARCHAR2 (32767);
calc_clause VARCHAR2 (4000);
BEGIN
SELECT calculation
INTO calc_clause
FROM kpi_types
WHERE id = par_kpi;
sql_stmt :=
'SELECT DATE, '
|| calc_clause
|| ' KPI_VALUE '
|| ' FROM DATA_VALUES '
|| ' WHERE TYPE_ID = '
|| par_kpi
|| ' GROUP BY DATE';
DBMS_OUTPUT.put_line (sql_stmt);
RETURN sql_stmt;
END;
Starting with the stored function suggested by Littlefoot, I was able to use this solution:
http://vincentdeelen.blogspot.com/2014/02/interactive-report-based-on-dynamic-sql.html
I have a page process that creates a collection with the dynamic query string. The charts region SQL now queries from the collection, and I can use all of my page items to filter the data appropriately.

Trying to use a FORALL to insert data dynamically to a table specified to the procedure

I have the need to dynamic know the name of the table that has the same data structure as many others and I can pass in a generic associative array that is of the same structure. Here is the proc
PROCEDURE INSRT_INTER_TBL(P_TABLE_NAME IN VARCHAR2, P_DATA IN tt_type)
IS
BEGIN
FORALL i IN P_DATA.FIRST .. P_DATA.LAST
EXECUTE IMMEDIATE
'INSERT INTO ' || P_TABLE_NAME ||
' VALUES :1'
USING P_DATA(i);
END INSRT_INTER_TBL;
I am getting the following error
ORA-01006: bind variable does not exist
What am I missing here?
So I had to specify all the columns necessary to insert to the table out in the insert statement like:
PROCEDURE INSRT_INTER_TBL(P_TABLE_NAME IN VARCHAR2, P_DATA IN inter_invc_ln_item_type)
IS
BEGIN
FORALL i IN P_DATA.FIRST .. P_DATA.LAST
EXECUTE IMMEDIATE
'INSERT INTO ' || P_TABLE_NAME || ' (ITEM_PK, pk, units, amt) ' ||
' VALUES (:P_INVC_LN_ITEM_PK, :PK, :UNITS, :AMT)'
USING IN P_DATA(i).item_pk, P_DATA(i).pk, P_DATA(i).units, P_DATA(i).amt;
END INSRT_INTER_TBL;
The TABLE operator works better than a FORALL here. It uses less code and probably skips some SQL-to-PL/SQL context switches.
--Simple record and table type.
create or replace type tt_rec is object
(
a number,
b number
);
create or replace type tt_type is table of tt_rec;
--Sample schema that will hold results.
create table test1(a number, b number);
--PL/SQL block that inserts TT_TYPE into a table.
declare
p_table_name varchar2(100) := 'test1';
p_data tt_type := tt_type(tt_rec(1,1), tt_rec(2,2));
begin
execute immediate
'
insert into '||p_table_name||'
select * from table(:p_data)
'
using p_data;
commit;
end;
/
You can run the above code in this SQL Fiddle.
Try VALUES (:1) i.e. have brackets around :1

Best practice for formatting/building queries to execute in a pl/sql stored procedure

Which method is preferred/faster/best practice? Does the second example execute slower? I assume it may, because the query has not been "compiled" yet since it is passed as a concatenated string before it is executed.
CREATE OR REPLACE PROCEDURE SP_FAST(
VI_OBID IN NUMBER,
VO_NAME OUT VARCHAR)
AS
BEGIN
SELECT NAME
INTO VO_NAME
FROM BILLING.CUSTOMER
WHERE OBJECTID = VI_OBID;
-- dbms_output.put_line('VI_OBID: '||VI_OBID||']');
-- dbms_output.put_line('VO_NAME: ['||VO_NAME||']');
END SP_FAST;
Or this:
CREATE OR REPLACE PROCEDURE SP_NOTASFAST(
VI_OBID IN NUMBER,
VO_NAME OUT VARCHAR)
AS
qstring VARCHAR2(500);
BEGIN
qstring:= 'SELECT NAME ' ||
'FROM BILLING.CUSTOMER ' ||
'WHERE OBJECTID = :1';
execute immediate qstring into VO_NAME using VI_OBID;
-- dbms_output.put_line('VI_OBID: '||VI_OBID||']');
-- dbms_output.put_line('VO_NAME: ['||VO_NAME||']');
END SP_NOTASFAST;
Each method has advantages and disadvantages.
The following link will be very useful for you. This chapter address how coding dynamic SQL in Oracle database.
http://docs.oracle.com/cd/B19306_01/appdev.102/b14251/adfns_dynamic_sql.htm

Use execute immediate in procedure for DML and passing character value in parameter

I have a delete procedure which is taking table name and some values to delete record from that table, hence I have created a procedure with execute immediate which is forming the delete query by taking the parameter and delete.
But when ever I am passing the char value in the parameter it is getting error :
invalid identifier
as query formed with out single quote for the character value. Please let me know how can I pass char value in the procedure to form a string correctly.
Below is the procedure:
CREATE OR replace PROCEDURE Prd_delete(p_tbl_name IN VARCHAR2,
p_sys VARCHAR2,
p_b_id VARCHAR2,
p_c_date NUMBER)
IS
dlt_query VARCHAR2(200);
BEGIN
dlt_query := 'delete from '
||p_tbl_name
||' where system='
||p_sys
|| ' And batch_id='
||p_b_id
|| ' And cobdate='
||p_c_date;
--dbms_output.put_line(dlt_query);
EXECUTE IMMEDIATE dlt_query;
END;
/
Below is the running command :
exec prd_delete ('TBL_HIST_DATA','M','N1',20141205);
Below is the error :
ORA-00904:"N1" invalid identifier.
How to pass this value correctly ? please suggest.
At first place, why do you need PL/SQL for the DELETE. You could do it in plain SQL.
Why is P_C_DATE a NUMBER, What data type is cobdate COLUMN. A date should always be a DATE. If the column data type is DATE, then you will run into more errors. Always pay attention to declaring correct data types.
With dynamic SQL, before directly executing, it is always a good practice to see whether the query is formed correctly using DBMS_OUTPUT. I would also suggest to use quoting string literal technique to make it even easier.
DBMS_OUTPUT.PUT_LINE(dlt_query);
The issue with the query is that you are missing the single-quotation marks around the VARCHAR2 type.
Modify the query to -
dlt_query := 'delete from '||P_TBL_NAME||' where system='||P_SYS||
' And batch_id='||''''||P_B_ID|| '''' ||
' And cobdate='||P_C_DATE;
you are losing the quotes around N1 during concatination
you can fix by adding quotes before and after , eg.
dlt_query := 'delete from '||P_TBL_NAME||' where system='||P_SYS||
' And batch_id='||''''||P_B_ID|| '''' ||
' And cobdate='||P_C_DATE;
If you have to use the EXECUTE IMMEDIATE statement, you should use bind variables:
CREATE OR REPLACE PROCEDURE prd_delete (P_TBL_NAME IN VARCHAR2,
P_SYS VARCHAR2,
P_B_ID VARCHAR2,
P_C_DATE NUMBER) IS
dlt_query VARCHAR2 (200);
BEGIN
dlt_query := 'delete from ' || P_TBL_NAME || ' where system=:1 and batch_id=:2 and cobdate=:3';
BEGIN
EXECUTE IMMEDIATE dlt_query USING P_SYS, P_B_ID, P_C_DATE;
EXCEPTION
WHEN OTHERS THEN
-- catch exception !!
END;
END;
/

Oracle dynamic parameters

I'm struggling to create a dynamic sql parametrized query. It involves using 'IS NULL' or 'IS NOT NULL'
Here's a simple pl/sql query:
CREATE OR REPLACE PROCEDURE GET_ALL_INFORMATION
(
"PARAM_START_DATE" IN DATE,
"PARAM_END_DATE" IN DATE,
"PARAM_IS_SUBMITTED" IN NUMBER,
"EXTRACT_SUBMITTED_CONTACTS" OUT sys_refcursor
) IS
sql_stmt VARCHAR2(3000);
PARAM_CONDITION VARCHAR2(20);
BEGIN
IF PARAM_IS_SUBMITTED = 1 THEN
PARAM_CONDITION := 'NOT NULL';
ELSE
PARAM_CONDITION := 'NULL';
END IF;
sql_stmt := ' SELECT
REGISTRATION_NUMBER,
NAME PROVIDER_TYPE,
ORGANIZATION
FROM TABLE_A
WHERE
P.DATE_FINALIZED IS :A;
OPEN EXTRACT_SUBMITTED_CONTACTS FOR sql_stmt USING PARAM_CONDITION;
Whereas the parameter (:A) in (USING PARAM_CONDITION) should have 'NULL' or 'NOT NULL'. It does not seem to work the way I envisioned.
Am I missing something?
As explained by GriffeyDog in a comment above, bind parameters could only be used as place holder for values. Not to replace keywords or identifiers.
However, this is not really an issue here, as you are using dynamic SQL. The key idea ifs that you build your query as a string -- and it will be parsed at run-time by the PL/SQL engine when you invoke EXECUTE or OPEN .. FOR.
Simply said, you need a concatenation -- not a bound parameter:
...
sql_stmt := ' SELECT
REGISTRATION_NUMBER,
NAME PROVIDER_TYPE,
ORGANIZATION
FROM TABLE_A
WHERE
P.DATE_FINALIZED IS ' || PARAM_CONDITION;
-- ^^
OPEN EXTRACT_SUBMITTED_CONTACTS FOR sql_stmt;

Resources