Insert Stored Procedure with WHERE clause - stored-procedures

I have a stored procedure for Oracle 10g that needs to create a new row in the table and not create duplicates.
The table allows duplicates, so long as all columns are not the same. This is because the last two columns can differ in values.
With that being said, when I try to store my procedure I get the following flags:
Line # = 10 Column # = 1 Error Text = PL/SQL: SQL Statement ignored
Line # = 13 Column # = 3 Error Text = PL/SQL: ORA-00933: SQL command not properly ended
The procedure looks fine [given I haven't added a WHERE clause for an insert before like this].
So either my format isn't what it should be or my logic is off.
Whatever the case may be, I have tried finding examples online and on stackoverflow and have fallen short.
Any suggestions on how I should tweak this?
(val_ID tablename.column1%type,
val_cat tablename.column2%type,
val_sub tablename.column3%type
)
AS
BEGIN
INSERT INTO tablename (column1, column2, column3)
VALUES (val_ID, val_cat, val_sub)
WHERE ((column1 != val_ID) and (column2 != val_cat) and (column3 != val_sub));
COMMIT;
END;
I have even removed the "(" in WHERE clause and nothing changed.
UPDATE:
tried the suggestion and all errors are gone [however the record didn't create]
(val_ID tablename.column1%type,
val_cat tablename.column2%type,
val_sub tablename.column3%type
)
AS
BEGIN
INSERT INTO tablename (column1, column2, column3)
SELECT val_ID, val_cat, val_sub
FROM dual
MINUS
SELECT val_ID, val_cat, val_sub
FROM tablename;

The insert statement doesn't have a where clause. You could emulate it, though, by using an insert-select statement:
INSERT INTO tablename (column1, column2, column3)
SELECT val_ID, val_cat, val_sub
FROM dual
MINUS
SELECT column1, column2, column3
FROM tablename;

#Mureinik 's example did negate all my errors; however, when tested it didn't create the new row.
So my current work around will be a query in VB.net checking if the value exists and then implementing a simple insert stored procedure:
//Make select statement and look at table for whether more than 0 rows shows up. If 0 rows, then execute stored procedure
If DsAds1.Tables(0).Rows.Count = 0 Then
...do stored procedure
End If
Stored Procedure
(val_ID tablename.column1%type,
val_cat tablename.column2%type,
val_sub tablename.column3%type
)
AS
BEGIN
INSERT INTO tablename (column1, column2, column3)
VALUES( val_ID, val_cat, val_sub);
COMMIT;
END;

Related

create columns in existing table and populate data using procedure

I will try to keep the query as short as possible. This involves 2 tables - lets call them staging_data and audit_data. STAGING_DATA has 3 columns:
user_no with data type number,
update_date_time with data type as date in DD-MON-YYYY HH24:MI:SS format
status_code which is varchar(1).
audit_data table also has the same 3 columns. The ask is to add 3 columns to audit_data table
seq_no (which will be unique to every user),
active_from (date type without the time format)
active_to (date type without the time format).
There is a procedure that inserts data from staging_data to audit_data.
Sample of the table audit_data
That data in audit table should look like :
For the next record for user_no 523(lets assume update_date_time is '23-Nov-2020 10:20') seq_no becomes 3, active_from_date becomes '23-Nov-2020', active_to becomes 31-Dec-99 and the active_to of user_no 523 with seq_no 2 becomes '22-Nov-2020'. So the data should look like this :
Highlighted the 3rd record which will be added later in light green.
So here goes my solution : I suggested to use row_number() over(partition by user_no) analytical function to get seq_no for each user. I wanted to create a view based on that but Boss doesn't want a view. He strictly wants to use a procedure. Procedure should check if the user_no exists (in this example 523). If exists then seq_no increases and active_to of the previous record for 523 changes to latest active_from - 1 date. I will be honest - I have no clue how to achieve this in Procedure. I understand I can create a cursor with the query I had in my mind for the view. But to add seq_no and change active_to date is something that has puzzled me. Can anyone please guide me in right direction/s? Also I apologise in advance if I have left out any other details. Its midnight here now and after 8 hours of racking my brain on this I am very hungry!
edit 11th Mar : here is the code for the procedure I wrote to insert data into the audit table for situation when a particular user_no has no record in audit table :
create or replace procedure test_aud IS
user_found_audit number;
lv_user_no AUDIT_DATA.user_no%TYPE;
cursor member_no is select distinct user_no from STAGING_DATA;
begin
open member_no;
loop
fetch member_no into lv_user_no;
exit when member_no%notfound;
select count(*) into user_found_audit from AUDIT_DATA where user_no = lv_user_no;
if user_found_audit = 0 then
insert into AUDIT_DATA(user_no, update_date_time,status_code, seq_no, last_update_date, active_from, active_to)
select user_no, update_date_time,status_code,row_number() over(partition by user_no order by UPDATE_DATE_TIME) as seqno,
to_char(trunc(update_date_time),'DD-MON-YYYY'),
to_char(trunc(update_date_time),'DD-MON-YYYY'),
lead(to_char(trunc(update_date_time)-1,'DD-MON-YYYY'),1,'31-DEC-99') over(PARTITION BY user_no ORDER BY UPDATE_DATE_TIME) from STAGING_DATA where user_no = lv_user_no;
commit;
else
dbms_output.put_line(lv_user_no||' exists in audit table');
-- to code the block when user_no exists, involves an update and insert
end if;
end loop;
close member_no;
end;
/
Well you need to collect a couple things. The latest stage row and the latest audit row. Then it is just a matter of generating the new audit information and updating the previous latest one. The following makes a couple simplifying assumptions:
Only the latest stage data for a given user_no needs processed as
all prior have been processed, However it does not assume the stage
table has been cleared.
The sequencing of 'Y' and 'N' status_codes are properly order in
that manner. In fact it does not even check the value.
It need not concern itself with the inherent race condition. The
condition is derives from seq_no being generated as Max()+1. This
structure virtually guarantees a duplicate will eventually be
created.
The nested procedure "establish_audit" does all the actual work. The rest are just supporting characters, including a couple just for debug purpose. See fiddle.
create or replace
procedure generate_stage_audit(user_no_in staging_data.user_no%type)
as
k_end_of_time constant date := date '9999-12-31';
l_latest_user_stage staging_data%rowtype;
l_latest_user_audit audit_data%rowtype;
procedure establish_audit
is
begin
insert into audit_data(user_no, update_date_time, status_code
,seq_no, active_from, active_to)
select l_latest_user_stage.user_no
, l_latest_user_stage.update_date_time
, l_latest_user_stage.status_code
, coalesce(l_latest_user_audit.seq_no,0) + 1
, trunc(l_latest_user_stage.update_date_time)
, k_end_of_time
from dual;
update audit_data
set active_to = trunc(l_latest_user_stage.update_date_time - 1)
where user_no = l_latest_user_audit.user_no
and seq_no = l_latest_user_audit.seq_no;
end establish_audit;
procedure retrieve_latest_stage
is
begin
select *
into l_latest_user_stage
from staging_data
where (user_no, update_date_time) =
( select user_no, max(update_date_time)
from staging_data
where user_no = user_no_in
group by user_no
);
end retrieve_latest_stage;
procedure retrieve_latest_audit
is
begin
select *
into l_latest_user_audit
from audit_data
where (user_no, seq_no) =
( select user_no, max(seq_no)
from audit_data
where user_no = user_no_in
group by user_no
);
exception
when no_data_found then
null;
end retrieve_latest_audit;
---- for debugging ---
procedure show_stage
is
begin
dbms_output.put_line('-------- Stage Row -------');
dbms_output.put_line(' user_no==>' || to_char(l_latest_user_stage.user_no));
dbms_output.put_line('update_date_time==>' || to_char(l_latest_user_stage.update_date_time));
dbms_output.put_line(' status_code==>' || to_char(l_latest_user_stage.status_code));
end show_stage;
procedure show_audit
is
begin
dbms_output.put_line('-------- Audit Row -------');
dbms_output.put_line(' user_no==>' || to_char(l_latest_user_audit.user_no));
dbms_output.put_line('update_date_time==>' || to_char(l_latest_user_audit.update_date_time));
dbms_output.put_line(' status_code==>' || to_char(l_latest_user_audit.status_code));
dbms_output.put_line(' seq_no==>' || to_char(l_latest_user_audit.seq_no));
dbms_output.put_line(' active_from==>' || to_char(l_latest_user_audit.active_from));
dbms_output.put_line(' active_to==>' || to_char(l_latest_user_audit.active_to));
end show_audit;
begin -- the main event
retrieve_latest_stage;
show_stage;
retrieve_latest_audit;
show_audit;
establish_audit;
end generate_stage_audit;
A couple warnings:
It seems you may be tempted to use string data type for the audit
columns Active_Form and Active_to as you are trying to declare then
"date type without the time". However there is no such data type in
Oracle; time is part of all dates. Do not do so, store them as
standard dates. (Note Dates are not stored in any format, but an
internal structure. Formats are strictly a visual representation).
Just throwaway the time with the format on the query or by setting
nls_date_format.
You may be tempted to convert call this through a trigger. Do not,
it will likely result in an "ORA-04091: Table is mutating"
exception.

Oracle - Dynamic select procedure

I need a stored procedure with dynamic select statement, in my case only adding desired column names in select. This is what I created, but I'm not sure If It's safe for SQL injections:
CREATE OR REPLACE PROCEDURE MySchema.Search(
columns IN VARCHAR2,
res_out OUT SYS_REFCURSOR)
IS
BEGIN
OPEN res_out FOR
'SELECT ' || columns ||' FROM MySchema.Table1';
END Search;
Is this fine or is It not safe ? When reading all examples I haven't noticed anything easy as this, but It works. If It's not safe for SQL injections, please show me how I should do It. Thanks for help in advance !
I will suggest to you use your PL/SQL like this: in the below PL/SQL it ensures that, if any of the SQL Injection statement is trying to invoke it will stop.
CREATE OR REPLACE PROCEDURE MySchema.Search(
columns IN VARCHAR2,
res_out OUT SYS_REFCURSOR)
IS
v_columns VARCHAR2(4000);
BEGIN
select listagg(column_name,',') within group(order by 1)
INTO v_columns
from all_tab_columns
where owner = 'MYSCHEMA'
and table_name = 'TABLE1'
and column_name in (select regexp_substr(columns,'[^,]+', 1, level)
from dual
connect by regexp_substr(columns, '[^,]+', 1, level) is not null
);
OPEN res_out FOR
'SELECT ' || v_columns ||' FROM MySchema.Table1';
END Search;

Create table with stored procedure in Teradata

I want to create a stored procedure where I can pass in variable to the WHERE clause below.
DROP TABLE fan0ia_mstr.Store_List;
CREATE TABLE fan0ia_mstr.Store_List AS(
SELECT
a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = 'VARIABLE' ) WITH DATA
PRIMARY INDEX (ANA_Code)
The VARIABLE will be a character string. I don't need to display the results, I just want the table to be created.
Also how do I trap any errors e.g. if the table doesn't exist for some reason I still want it to be created
thanks
As you don't have variable database/table/column names you simply need to wrap your existing code (slightly modified) into a Stored Procedure:
replace procedure myproc(IN variable varchar(100))
begin
BEGIN
-- simply try dropping the table and ignore the "table doesn't exist error"
DECLARE exit HANDLER FOR SQLEXCEPTION
BEGIN -- 3807 = table doesn't exist
IF SQLCODE <> 3807 THEN RESIGNAL; END IF;
END;
DROP TABLE fan0ia_mstr.Store_List;
END;
CREATE TABLE fan0ia_mstr.Store_List AS(
SELECT
a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = :variable ) WITH DATA
PRIMARY INDEX (ANA_Code);
end;
Of course a DELETE/INSERT or a Temporary table might be more efficient.
Edited... second option needs a execute immediate too...
CREATE PROCEDURE PROCEDURE1(
V_AREA_DESC IN VARCHAR2 )
AS
BEGIN
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE fan0ia_mstr.Store_List';
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
EXECUTE IMMEDIATE 'CREATE TABLE fan0ia_mstr.Store_List AS
(SELECT a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = ''' || v_area_desc || '''
) WITH DATA PRIMARY INDEX (ANA_Code)';
END PROCEDURE1;
but you can avoid drop / create with truncate / insert
CREATE PROCEDURE PROCEDURE1(
V_AREA_DESC IN VARCHAR2 )
AS
BEGIN
execute immediate 'truncate TABLE fan0ia_mstr.Store_List';
insert into fan0ia_mstr.Store_List (SELECT a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = v_area_desc
);
commit;
END PROCEDURE1;

PL/SQL:, How to pass variable into SELECT statent and return all rows of results

I am using an oracle database. I am used to SQL server but not familiar with PL/SQL for the Oracle database.
How do I Set a variable that returns all the rows that contain the value of that variable: I am lost, I tried to understand, but it is not making sense to me. This si a recent attempt I made to to this.
DECLARE date1 varchar(40);
Begin
Select '''07/31/2013_09%''' into :date1 from dual;
End;
/
print date1
Begin
Select * from TABLE1 where start_time LIKE date1;
End;
/
I should get all the rows returned from this.
Thank you for your help.
This might help you get started:
create table table1 (
start_time varchar2(10),
foo number
);
insert into table1 values ('xyz', 1);
insert into table1 values ('wxy', 2);
insert into table1 values ('abc', 3);
create type table1_obj as object (
start_time varchar2(10),
foo number
);
/
create type table1_tab as table of table1_obj;
/
declare
v table1_tab;
begin
select table1_obj(start_time, foo) bulk collect into v
from table1 where start_time like '%x%';
end;
/
You have to create a parametrized cursor and pass that date as parameter to that cursor as below.
CREATE or REPLACE procedure proc1(p_date date)
as
CURSOR C1 (date1 date)is
SELECT * from TABLE1 where start_time LIKE date1;
BEGIN
FOR i in c1(p_dat)
LOOP
.......
END LOOP;
END;
It looks like you're missing the understanding of several basic building blocks:
You need a PL/SQL data structure where you'll save the data set queried from database table: PL/SQL Collections and Records. See especially nested tables and record variables.
You query the database with PL/SQL Static SQL. See especially SELECT INTO statements. In this case there is no need for dynamic SQL.
But of course it is bit harder to get a set of rows out of the database than only one row. Here the keywords are: SELECT INTO Statement with BULK COLLECT Clause. Note that depending on your query and table size bulk collection will potentially exhaust your server's memory (by loading millions of rows).
Here is an example that should give you a kickstart:
create table so26 (
day date,
event varchar(10)
);
insert all
into so26 values(trunc(sysdate - 1), 'foo1')
into so26 values(trunc(sysdate - 1), 'foo2')
into so26 values(trunc(sysdate - 1), 'foo3')
into so26 values(trunc(sysdate ), 'bar')
into so26 values(trunc(sysdate + 1), 'zoo')
select 1 from dual;
select * from so26;
declare
type event_list_t is table of so26%rowtype;
v_events event_list_t := event_list_t();
function get_events(p_day in date default sysdate) return event_list_t as
v_events event_list_t := event_list_t();
begin
select *
bulk collect into v_events
from so26
where day = trunc(p_day);
return v_events;
end;
begin
v_events := get_events(sysdate + 1);
if v_events.first is null then
dbms_output.put_line('no events on that day');
return;
end if;
for i in v_events.first .. v_events.last loop
dbms_output.put_line(i || ': event = ' || v_events(i).event);
end loop;
end;
/
Example output when get_events(sysdate - 1):
1: event = foo1
2: event = foo2
3: event = foo3

How to use procedure parameters in merge statement

i'm creating a procedure to update/insert a table using merge statement(upsert).now i have a problem: using procedure parameters i have to do this upsert.
procedure xyz( a in table.a%type,b in table.b%type,....)
is
some local variables;
begin
merge into target_table
using source_table --instead of the source table, i have to use procedure parameters here
on (condition on primary key in the table)
when matched then
update the table
when not matched then
insert the table ;
end xyz;
so how to use procedure parameters instead of source table in merge statement?? or
suggest me a query to fetch the procedure parameters and use it as source table values.
help me please.
Thanks in advance.
I know that I'm eight years late to the party, but I think that I was trying to do something similar to what you were doing, but trying to Upsert based on parameters passed into a stored procedure that returns an empty string on success and an error on failure back to my VB Code. Below is all of my code along with comments explaining what I did, and why I did it. Let me know if this helps you or anyone else. This is my first time answering a post.
PROCEDURE UpsertTSJobData(ActivitySeq_in IN NUMBER,
Owner_in In VARCHAR2,
NumTrailers_in IN NUMBER,
ReleaseFormReceived_in IN NUMBER,
Response_out OUT VARCHAR2) AS
err_num NUMBER;
err_msg VARCHAR2(4000);
BEGIN
--This top line essentially does a "SELECT *" from the named table
--and looks for a match based on the "ON" statement below
MERGE INTO glob1app.GFS_TS_JOBDATA_TAB tsj
--This select statement is used for the INSERT when no match
--is found and the UPDATE when a match is found.
--It creates a "pseudo-table"
USING (
SELECT ActivitySeq_in AS ActSeq,
Owner_in As Owner,
NumTrailers_in As NumTrailers,
ReleaseFormReceived_in As ReleaseFormReceived
FROM DUAL) input
--This ON statement is what we're doing the match on to find
--matching records. This decides whether it will be an
--INSERT or an UPDATE
ON (tsj.Activity_seq = ActivitySeq_in)
WHEN MATCHED THEN
--Here we UPDATE based on the passed in input table
UPDATE
SET OWNER = input.owner,
NUMTRAILERS = input.NumTrailers,
RELEASEFORMRECEIVED = input.releaseformreceived
WHEN NOT MATCHED THEN
--Here we INSERT based on the passed in input table
INSERT (
ACTIVITY_SEQ,
OWNER,
NUMTRAILERS,
RELEASEFORMRECEIVED
)
VALUES (
input.actseq,
input.owner,
input.numtrailers,
input.releaseformreceived
);
Response_out := '';
EXCEPTION
WHEN OTHERS THEN
err_num := SQLCODE;
err_msg := SUBSTR(SQLERRM, 1, 3900);
Response_out := TO_CHAR (err_num) || ': ' || err_msg;
END;
Maby something like
DECLARE V_EXISTS NUMBER;
BEGIN SELECT COUNT(*) INTO V_EXISTS FROM TARGET_TABLE WHERE PK_ID = :ID;
IF V_EXISTS > 0 THEN
-- UPDATE
ELSE
-- INSERT
END IF;
END;
Also, you may try to use so-called tempotary table (select from DUAL)
CREATE TABLE TEST (N NUMBER(2), NAME VARCHAR2(20), ADRESS VARCHAR2(100));
INSERT INTO TEST VALUES(1, 'Name1', 'Adress1');
INSERT INTO TEST VALUES(2, 'Name2', 'Adress2');
INSERT INTO TEST VALUES(3, 'Name3', 'Adress3');
SELECT * FROM TEST;
-- test update
MERGE INTO TEST trg
USING (SELECT 1 AS N, 'NameUpdated' AS NAME,
'AdressUpdated' AS ADRESS FROM Dual ) src
ON ( src.N = trg.N )
WHEN MATCHED THEN
UPDATE
SET trg.NAME = src.NAME,
trg.ADRESS = src.ADRESS
WHEN NOT MATCHED THEN
INSERT VALUES (src.N, src.NAME, src.ADRESS);
SELECT * FROM TEST;
-- test insert
MERGE INTO TEST trg
USING (SELECT 34 AS N, 'NameInserted' AS NAME,
'AdressInserted' AS ADRESS FROM Dual ) src
ON ( src.N = trg.N )
WHEN MATCHED THEN
UPDATE
SET trg.NAME = src.NAME,
trg.ADRESS = src.ADRESS
WHEN NOT MATCHED THEN
INSERT VALUES (src.N, src.NAME, src.ADRESS);
SELECT * FROM TEST;
DROP TABLE TEST;
see here
Its very difficult to tell from you question exactly what you what, but I gather you want the table that you are merging into ( or on ) to be dynamic. In that case, what you should be using is the DBMS_SQL package to create dynamic SQL

Resources