Will using a conditional variable negate execution plan retention and reuse - stored-procedures

I want to write a stored procedure in SQL Server that utilizes "conditional variables". By conditional, I am referring to something along these lines:
CREATE PROCEDURE [dbo].[Order_SEL]
#BeginDate DATETIME = NULL
, #ENDDATE = NULL = NULL
BEGIN
SELECT
ID
, CustomerID
, DateOrdered
FROM
Orders
WHERE
(
#BeginDate IS NULL
OR
DateOrdered BETWEEN #BeginDate AND #EndDate
)
END
Now, I know one of the benefits of using stored procedures is execution plan retention and reuse. Will the methodology used above negate this advantage?

Related

Can I get an Example of a Snowflake Stored Procedure that takes in StartDate and ReturnDate then executes 2 Select statements and returns a table()?

Can I get an Example of a Snowflake Stored Procedure that takes in StartDate and ReturnDate then executes 2 Select statements and returns a table()? It needs to be a WORKING example. Just Table1 and Table2 for table names, etc. I am just looking for a good example syntax wise.
If you want to return a table, you must use a SQL Script stored procedure. I'm not sure what you want the second select to do, so here's a sample with a single select. Are you looking for the second one to key off of something it finds after running the first statement?
create or replace procedure test (start_date date, end_date date)
returns table()
language sql
as
$$
declare
res resultset default (
select *
from "SNOWFLAKE_SAMPLE_DATA"."TPCH_SF1"."ORDERS"
where O_ORDERDATE >= :start_date
and O_ORDERDATE <= :end_date
);
begin
return table(res);
end;
$$;
call test('1994-01-01', '1994-01-02');

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.

Volatile Table in Stored Procedure - Teradata

I create a stored procedure as follows:
replace PROCEDURE mydb.sp_Insert_Values (
IN lastExecDate timestamp
)
SQL SECURITY CREATOR
BEGIN
CREATE MULTISET VOLATILE TABLE vt_ref_table_1
(
Ref_Id integer,
Ref_Unit_Type varchar(50)
) ON COMMIT PRESERVE ROWS;
insert into mydb.vt_ref_table_1
select Ref_Id, Ref_Unit_Type
from mydb.ref_table_1;
INSERT INTO mydb.Time_Series_Table
select
t1.TD_TIMECODE
, t1.Time_Series_Meas
, t1.Time_Series_Curve_Type_CD
, t1.Ref_Unit_Type
, t1.Ref_Id
, t1.created_on
from
(
select
meas_ts as TD_TIMECODE
, ref_table_1.Ref_Id as Ref_Id
, meas as Time_Series_Meas,
, Time_Series_Curve_Type_CD
, Ref_Unit_Type
, current_timestamp as created_on
from mydb_stg.Time_Series_Table_Stg as stg
left join mydb.ref_table_1 as ref_table_1
on ref_table_1.Ref_Id = stg.Ref_Id
where stg.created_on >= :lastExecDate
) as t1
left join mydb.Time_Series_Table as t2
on t1.TD_TIMECODE=t2.TD_TIMECODE
and t1.Time_Series_Curve_Type_CD = t2.Time_Series_Curve_Type_CD
and t1.Ref_Id=t2.Ref_Id
where t2.Ref_Id is null
;
END;
It compiles but when I call it this error is thrown:
Only a COMMIT WORK or null statement is legal after a DDL Statement.
I know the error is related to the volatile table but I don't know how to correct it.
Why I need the volatile table:
The reference table has a row-level-security constraint. If I use it directly I get another error:
A multi-table operation is executed and the tables do not have the
same security constraints.
Teradata Version: 16.20
Mode: ANSI
workaround:
create VOLATILE table first in your session
create procedure in the same session

Join procedures only once on Firebird

I'm trying to left join two stored procedures in a Firebird query.
In my example data the first returns 70 records, the second just 1 record.
select
--...
from MYSP1('ABC', 123) s1
left join MYSP2('DEF', 456) s2
on s1.FIELDA = s2.FIELDA
and s1.FIELDB = s2.FIELDB
The problem is performances: it takes 10 seconds, while each procedure takes less than 1 second. I suspect that procedures are run multiple times instead of just once. It would make sense to execute them just once, because I pass fixed parameters to them.
Is there a way to oblige Firebird to simply execute once each procedure and then join their results?
Since it seems there is no way, I solved this issue running this query inside a new stored procedure, where I cache all results from MYSP2 into a global temporary table and make the join between MYSP1 and the temporary table.
This is temporary table definition:
create global temporary table MY_TEMP_TABLE
(
FIELDA varchar(3) not null,
FIELDB smallint not null,
FIELDC varchar(10) not null
);
This is stored procedure body:
--cache MYSP2 results
delete from MY_TEMP_TABLE;
insert into MY_TEMP_TABLE
select *
from MYSP2('DEF', 456)
;
--join data
for
select
--...
from MYSP1('ABC', 123) s1
left join MY_TEMP_TABLE s2
on s1.FIELDA = s2.FIELDA
and s1.FIELDB = s2.FIELDB
into
--...
do
suspend;
But if there is another solution without temporary tables it would be great!
Maybe this can help:
with MYSP2W as (MYSP2('DEF', 456))
select
--...
from MYSP1('ABC', 123) s1
left join MYSP2W s2
on s1.FIELDA = s2.FIELDA
and s1.FIELDB = s2.FIELDB

Most Efficient Version of PLSQL Stored Procedure

I am writing a PL/SQL stored procedure which will be called from within a .NET application.
My stored procedure must return
the count of values in a table of part revisions, based on an input part number,
the name of the lowest revision level currently captured in this table for the input part number
the name of the revision level for a particular unit in the database associated with this part number and an input unit ID.
The unit's revision level name is captured within a separate table with no direct relationship to the part revision table.
Relevant data structure:
Table Part has columns:
Part_ID int PK
Part_Number varchar2(30)
Table Part_Revisions:
Revision_ID int PK
Revision_Name varchar2(100)
Revision_Level int
Part_ID int FK
Table Unit:
Unit_ID int PK
Part_ID int FK
Table Unit_Revision:
Unit_ID int PK
Revision_Name varchar2(100)
With that said, what is the most efficient way for me to query these three data elements into a ref cursor for output? I am considering the following option 1:
OPEN cursor o_Return_Cursor FOR
SELECT (SELECT COUNT (*)
FROM Part_Revisions pr
inner join PART pa on pa.part_id = pr.part_id
WHERE PA.PART_NO = :1 )
AS "Cnt_PN_Revisions",
(select pr1.Revision_Name from Part_Revisions pr1
inner join PART pa1 on pa1.part_id = pr1.part_id
WHERE PA.PART_NO = :1 and pr1.Revision_Level = 0)
AS "Input_Revison_Level",
(select ur.Revision_Name from Unit_Revision ur
WHERE ur.Unit_ID = :2) as "Unit_Revision"
FROM DUAL;
However, Toad's Explain Plan returns Cost:2 Cardinality: 1, which I suspect is due to me using DUAL in my main query. Comparing that to option 2:
select pr.Revision_Name, (select count(*)
from Part_Revisions pr1
where pr1.part_id = pr.part_id) as "Count",
(select ur.Revision_Name
from Unit_Revision ur
where ur.Unit_ID = :2) as "Unit_Revision"
from Part_Revisions pr
inner join PART pa on pa.part_id = pr.part_id
WHERE PA.PART_NO = :1 and pr.Revision_Level = 0
Essentially I don't really know how to compare the results from my execution plans, to chose the best design. I have also considered a version of option 1, where instead of joining twice to the Part table, I select the Part_ID into a local variable, and simply query the Part_Revisions table based on that value. However, this is not something I can use the Explain Plan to analyze.
Your description and select statements look different... I based the procedure on the SQL statements.
PROCEDURE the_proc
(
part_no_in IN NUMBER
, revision_level_in IN NUMBER
, unit_id_in IN NUMBER
, part_rev_count_out OUT NUMBER
, part_rev_name_out OUT VARCHAR2
, unit_rev_name_out OUT VARCHAR2
)
AS
BEGIN
SELECT COUNT(*)
INTO part_rev_count_out
FROM part pa
WHERE pa.part_no = part_no_in
AND EXISTS
(
SELECT 1
FROM part_revisions pr
WHERE pa.part_id = pr.part_id
);
SELECT pr1.revision_name
INTO part_rev_name_out
FROM part_revisions pr1
WHERE pr1.revision_level = revision_level_in
AND EXISTS
(
SELECT 1
FROM part pa1
WHERE pa1.part_id = pr1.part_id
AND pa.part_no = part_no_in
);
SELECT ur.revision_name
INTO unit_rev_name_out
FROM unit_revision ur
WHERE ur.unit_id = unit_id_in;
END the_proc;
It looks like you are obtaining scalar values. Rather than return a cursor, just return the values using clean sql statements. I have done this numerous times from .net, it works fine.
Procedure get_part_info(p_partnum in part.part_number%type
, ret_count out integer
, ret_revision_level out part_revisions.revision_level%type
, ret_revision_name out part_revisions.revision_name%type) as
begin
select count(*) into ret_count from ....;
select min(revision_level) into ret_revision_level from ...;
select revision_name in ret_revision_name...;
return;
end;

Resources