PL/SQL Procedure - ignore argument if null - sql

I have a procedure that takes three arguments and then queries a table based on those arguments. Now one of them could be null and if it is, I'd like it to be ignored in the WHERE clause of the selection.
create or replace PROCEDURE Procedure1
(
COUNTRY IN VARCHAR2, MAKE IN VARCHAR2, SERIAL IN number
) AS
BEGIN
DECLARE
CURSOR c1 IS select v.ID from vehicle v
where v.country = COUNTRY AND
v.make = MAKE AND
((SERIAL IS NOT NULL AND v.serial = SERIAL) OR 1);
BEGIN
FOR e_rec IN c1 LOOP
DBMS_OUTPUT.PUT_LINE(e_rec.id);
END LOOP;
END;
END Procedure1;
I tried something like this but it doesn't work.

you can use the condition like
WHERE v.country = COUNTRY AND
v.make = MAKE AND
(SERIAL IS NULL or v.serial = SERIAL)
This will also work
WHERE v.country = COUNTRY AND
v.make = MAKE AND
((SERIAL IS NOT NULL AND v.Serial = SERIAL) OR SERIAL IS NULL)

try this - this is same as #Miller suggested just using NVL function
WHERE v.country = nvl(COUNTRY,v.country)
AND v.make = nvl(MAKE,v.make)
AND v.serial = nvl(SERIAL,v.serial)

Related

select statements and loops

I am trying to execute a program with the following flow. I wondered how I could write this properly with oracle pl/sql?
paymentIds is an array of values, originally initialized as a VARRAY.
I'd like to pass variable1 into the "update" statement.
procedure reset_finance_interface(paymentIds MyType)
is
BEGIN
FOR i IN 1..paymentIds.count LOOP
variable1 =
select id_finance_interface
from finance_interface fi
where fi.interface_id in (paymentIds(i))
and fi.id_interface_type = 'DC'
update finance_interface fi
set processed = 0
,id_interface_file = null
,interfaced=0
,interface_date = null
where
id_interface_type in ('DC')
and id_finance_interface = variable1
and processed = 2 --not in (0,1)
and ok_to_process = 1;
END LOOP;
END;
Thanks,
Robert
You can use a MERGE statement to get rid of the loops and combine the SELECT and UPDATE statements:
procedure reset_finance_interface(paymentIds MyType)
IS
BEGIN
MERGE INTO finance_interface dst
USING (
SELECT column_value AS id
FROM TABLE(paymentIDs)
) src
ON (
src.id = dst.interface_id
AND dst.id_interface_type = 'DC'
AND dst.processed = 2 --not in (0,1)
AND dst.ok_to_process = 1
)
WHEN MATCHED THEN
UPDATE
SET processed = 0
, id_interface_file = null
, interfaced = 0
, interface_date = null;
END;
/
db<>fiddle here

sql filter only if parameter is not 0

Hello I have the following procedure:
CREATE PROCEDURE searchMembers(IN param1 INT, IN param2 INT, IN age INT)
searchMembers:BEGIN
SELECT * FROM members
WHERE param1=param1
AND param2=param2
AND age=age;
END;
But I want each filter to exist only if params are not equal to zero. For example, if I run CALL searchMembers(12, 24, 0); the executed query should be
SELECT * FROM members
WHERE param1=param1
AND param2=param2;
END;
Any ideas how to do that?
First, name your parameters so they do not conflict with column names.
Then, I would expect something like:
CREATE PROCEDURE searchMembers (
IN in_param1 INT,
IN in_param2 INT,
IN in_age INT
) AS
BEGIN
SELECT m.*
FROM members m
WHERE (m.param1 = in_param1 OR in_param1 IS NULL) AND
(m.param2 = in_param2 OR in_param2 IS NULL) AND
(m.age = in_age OR in_age IS NULL);
END;
Note that this uses NULL to denote all. This is a much more typical choice, but you can use similar logic for 0 if that is what you really want.
SELECT *
FROM members
WHERE (param1 = 0 or col1 = param1)
AND (param2 = 0 or col2 = param2)
AND (age_param = 0 or age = age_param)

Update a column in table with the calculations mede with other column in same table

I have a situation where I need to update a row in a temp table with calculations made over some other columns data from same table.
I was able to do this for division and sum but not the complicated one like below
What I should do is
UPDATE t
SET t.Value_Numeric = ((t.row9-((1-t.row28)*t.row26))/((1-t.row91))/(t.row33)-t.row85)
FROM #tempTable t
where t.order = 93
** t.row9 means t.Value_Numeric where t.order = 9
I was able to do a division like this
UPDATE #tempTable
SET #tempTable.Value_Numeric = reim.cmi
FROM #tempTable t
INNER JOIN ( SELECT t1.Value_Numeric AS IPAllw ,
t2.Value_Numeric AS IPElig ,
t1.Value_Numeric / NULLIF(t2.Value_Numeric,0) AS cmi ,
t1.WorkingID
FROM #tempTable t1
JOIN #tempTable t2 ON t1.WorkingID = t2.WorkingID
AND t1.order = 78
AND t2.order = 74
GROUP BY t1.WorkingID ,
t1.Value_Numeric ,
t2.Value_Numeric
) reim ON reim.WorkingID = t.WorkingID
WHERE t.order = 79
help me to figure out the query to update the complex calculation.
Thanks
I assume Oracle in my answer based on the plsql tag given to the question.
One possible solution would be to create a helper function as follows:
create or replace function get_temptable_value(p_row in number) return temptable.value_numeric%type
is
pragma AUTONOMOUS_TRANSACTION;
v_val temptable.value_numeric%type;
begin
begin
select value_numeric
into v_val
from temptable
where row_order = p_row;
exception
when no_data_found then
v_val := null;
end;
return v_val;
end;
The function returns null if the given row is not found, but it might be better to return 0 depending on your requirements. The autonomous pragma enables it to be called from an update statement (otherwise ORA-04091 is given):
update temptable t
SET t.Value_Numeric = ((get_temptable_value(9)-((1-get_temptable_value(28))* get_temptable_value(26)))/((1- get_temptable_value(91)))/( get_temptable_value(33))- get_temptable_value(85))
where t.row_order = 93;
2. Another solution would be the use of model cluase:
create or replace view temptable_upd
as
select *
from temptable t
model dimension by (row_order)
measures (value_numeric, value_numeric as new_value_numeric)
rules (new_value_numeric[93] = ((value_numeric[9]-((1-value_numeric[28])* value_numeric[26]))/((1- value_numeric[91]))/( value_numeric[33])- value_numeric[85])
);
In the rules section you can define any inter-row calculation.
Using the view, you can update the table:
update temptable t
set t.value_numeric = (select new_value_numeric from temptable_upd tupd where tupd.row_order = t.row_order)
where t.row_order = 93;
Create or replace procedure abc
Is
V_count number;
Begin
Select ur_calculations o/p into v_count from table ;Update
Ur_table set col1 = v_count
Where col2 =value;
Commit;End;

Using a Stored Procedure in a View with Postgresql

I have been trying to create a View where one of the column pending_amount gets its value as a result of a stored procedure execution.
The stored procedure is pending_stock(int,int) and returns an integer. The view is created successfully but when i try to perform any query like select on the this view it takes ever to return a value.
CREATE OR REPLACE VIEW view_production_parts AS
SELECT p.part_id, p.min_amount, gp.part_num, gp.description
, p.quantity_available
, p.quantity_total - p.quantity_available AS quantity_alloc
, p.quantity_total
, (SELECT pending_stock(p.part_id, 0) AS pending_stock) AS pending_amount
, p.production_run
, CASE
WHEN ppur.purchased_part_id IS NOT NULL THEN true
ELSE false
END AS is_purchased_part, ppur.purchased_part_id, p.store_move_type_id
, gp.part_status_id, p.default_location
, COALESCE(pwoh.part_work_order_hold_id, 0) AS part_work_order_hold_id
FROM general_part gp
JOIN part p ON gp.part_id = p.part_id
LEFT JOIN purchased_part ppur ON ppur.part_id = p.part_id
LEFT JOIN part_work_order_hold pwoh ON pwoh.part_id = p.part_id
ORDER BY gp.part_num;
Can a stored procedure be used in a view? If used, Is my declaration correct?
Find the result from of this query at explain.depesz.com:
EXPLAIN ANALYZE SELECT count(*) FROM view_production_parts
I am using Postgres 8.4.
Function definition for pending_stock(int,int):
CREATE OR REPLACE FUNCTION pending_stock(var_part_id integer
, var_pattern_id integer)
RETURNS integer AS
$BODY$
declare
r record;
var_qty_expected integer;
var_qty_moved_to_stock integer;
var_total_stock_moved_out integer;
var_actual_qty integer;
begin
var_total_stock_moved_out := 0;
var_qty_expected := 0;
for r in
select work_order_id,quantity_expected
from view_work_orders
where part_id = var_part_id and open = 'TRUE'
and quantity_allocated is null and quantity_expected >= quantity_actual
loop
var_qty_expected = var_qty_expected + r.quantity_expected;
select sum(quantity) from view_work_order_move_parts_details
where source_work_order_id = r.work_order_id
and part_id = var_part_id into var_qty_moved_to_stock;
if var_qty_moved_to_stock is null then
var_qty_moved_to_stock = 0;
end if;
var_total_stock_moved_out = var_total_stock_moved_out
+ var_qty_moved_to_stock;
end loop;
var_actual_qty := var_qty_expected - var_total_stock_moved_out;
if var_actual_qty > 0 then
return var_actual_qty;
else
return 0;
end if;
end;
$BODY$
LANGUAGE 'plpgsql' VOLATILE STRICT
COST 100;
ALTER FUNCTION pending_stock(integer, integer) OWNER TO postgres;
View
You don't need a subquery for the function call. And you can simplify some other minor details:
CREATE OR REPLACE VIEW view_production_parts AS
SELECT p.part_id, p.min_amount
, gp.part_num, gp.description, p.quantity_available
, p.quantity_total - p.quantity_available AS quantity_alloc
, p.quantity_total
, pending_stock(gp.part_id, 0) AS pending_amount
, p.production_run
,(ppur.purchased_part_id IS NOT NULL) AS is_purchased_part
, ppur.purchased_part_id, p.store_move_type_id, gp.part_status_id
, p.default_location
, COALESCE(pwoh.part_work_order_hold_id, 0) AS part_work_order_hold_id
FROM general_part gp
JOIN part p USING (part_id)
LEFT JOIN purchased_part ppur USING (part_id)
LEFT JOIN part_work_order_hold pwoh USING (part_id)
ORDER BY gp.part_num;
Other than that the VIEW definition looks fine.
Function
Can be largely simplified:
CREATE OR REPLACE FUNCTION pending_stock(var_part_id integer
, var_pattern_id integer)
RETURNS integer AS
$func$
DECLARE
r record;
var_qty_expected integer := 0;
var_total_stock_moved_out integer := 0;
BEGIN
FOR r IN
SELECT work_order_id, quantity_expected
FROM view_work_orders
WHERE part_id = var_part_id
AND open = 'TRUE' -- A string instead of a boolean?
AND quantity_allocated IS NULL
AND quantity_expected >= quantity_actual
LOOP
var_qty_expected := var_qty_expected + r.quantity_expected;
SELECT var_total_stock_moved_out + COALESCE(sum(quantity), 0)
FROM view_work_order_move_parts_details
WHERE source_work_order_id = r.work_order_id
AND part_id = var_part_id
INTO var_total_stock_moved_out;
END LOOP;
RETURN GREATEST(var_qty_expected - var_total_stock_moved_out, 0);
END
$func$ LANGUAGE plpgsql
Major points
Generally, assignments are comparatively expensive in plpgsql. Every assignment is executed with a (very simple and fast) SELECT statement internally. Try to use fewer of them.
You can init variables at declaration time. No need for another statement.
The assignment operator in plpgsql is :=. = works, but is undocumented.
Use COALESCE() to catch NULL values.
The function parameter var_pattern_id is never used. This is probably not the full function definition.
The whole final part can be replaced with a single statement using GREATEST
Superior query
Now, this cleaned up function will be a bit faster, but not much. Your whole design of looping repeatedly is extremely inefficient. It results in correlated subqueries that loop through correlated subqueries yet again. Performance nightmare.
Recast the problem as set-based operation to make it faster. Well, a lot faster.
SELECT e.part_id
,GREATEST(COALESCE(sum(e.quantity_expected), 0)
- COALESCE(sum(m.total_stock_moved_out), 0), 0)
FROM view_work_orders e
LEFT JOIN (
SELECT source_work_order_id AS work_order_id
,COALESCE(sum(quantity), 0) AS total_stock_moved_out
FROM view_work_order_move_parts_details
WHERE part_id = var_part_id
GROUP BY 1
) m USING (work_order_id)
WHERE e.part_id = var_part_id
AND e.open = 'TRUE'
AND e.quantity_allocated IS NULL
AND e.quantity_expected >= e.quantity_actual
GROUP BY 1;
Superior view
Integrate this into the original query / view:
CREATE OR REPLACE VIEW view_production_parts AS
SELECT p.part_id, p.min_amount
,gp.part_num, gp.description, p.quantity_available
,p.quantity_total - p.quantity_available AS quantity_alloc
,p.quantity_total
,x.pending_amount
,p.production_run
,(ppur.purchased_part_id IS NOT NULL) AS is_purchased_part
,ppur.purchased_part_id, p.store_move_type_id, gp.part_status_id
,p.default_location
,COALESCE(pwoh.part_work_order_hold_id, 0) AS part_work_order_hold_id
FROM general_part gp
JOIN part p USING (part_id)
LEFT JOIN purchased_part ppur USING (part_id)
LEFT JOIN part_work_order_hold pwoh USING (part_id)
LEFT JOIN (
SELECT e.part_id
,GREATEST(COALESCE(sum(e.quantity_expected), 0)
- COALESCE(sum(m.total_stock_moved_out), 0)
, 0) AS pending_amount
FROM view_work_orders e
LEFT JOIN (
SELECT source_work_order_id AS work_order_id
,sum(quantity) AS total_stock_moved_out
FROM view_work_order_move_parts_details
WHERE part_id = var_part_id
GROUP BY 1
) m USING (work_order_id)
WHERE e.part_id = var_part_id
AND e.open = 'TRUE'
AND e.quantity_allocated IS NULL
AND e.quantity_expected >= e.quantity_actual
GROUP BY 1
) x USING (part_id)
ORDER BY gp.part_num;
Untested, obviously.

how to make an outer join with the calling of other procedure

I have a stored procedure :
procedure qr_get_dep_boss(...,...)
takes two params(year, main_code) and returning one record the boss_num and his name .
I want to create another procedure and make a loop on the previous procedure calling , making an outer join with the result of another query
SELECT
year,
main_code,
name,
CASE
WHEN father_code in(-1) THEN NULL
ELSE father_code
END AS father_code,main_code || '_' || year AS main_id,
(SELECT COUNT(*)
FROM sm_r_build
WHERE father_code = sc.main_code
AND year = (SELECT MAX(year)
FROM st_quit_info)) childcount,
main_code||'_'|| father_code AS serial
FROM sm_r_build sc
WHERE year=(SELECT MAX(year)FROM st_quit_info)
So I want the result:
year,main_code,name,father_code,main_id,childcount,serial,boss_num,boss_name
I try this :
create procedure new_get_alldepwithboss()
returning int as year , int as main_code ,nvarchar(100) as name,int as father_code,nvarchar(255) as main_id,int as childcount,int as ll_boss_num,nvarchar(100) as ll_boss_name
define ll_year int;
define ll_main_code int;
define ll_name nvarchar(100);
define ll_father_code int;
define ll_main_id nvarchar(255) ;
define ll_childcount int;
define ll_boss_num int;
define ll_boss_name nvarchar(100);
foreach
SELECT year,main_code,name,CASE WHEN father_code in(-1) THEN NULL ELSE father_code END AS father_code,main_code || '_' || year AS main_id, (SELECT COUNT(*)
FROM sm_r_build
WHERE father_code=sc.main_code AND year= (SELECT MAX(year)
FROM st_quit_info)) childcount ,a.emp_num,a.emp_name
INTO ll_year ,ll_main_code,ll_name ,ll_father_code ,ll_main_id ,ll_childcount ,ll_boss_num,ll_boss_name
FROM sm_r_build sc , TABLE(FUNCTION qr_get_dep_boss(ll_main_code, ll_year))AS a(emp_num,emp_name)
WHERE year=(SELECT MAX(year)FROM st_quit_info)
return ll_year , ll_main_code, ll_name,ll_father_code,ll_main_id, ll_childcount , ll_boss_num,ll_boss_name with resume;
end foreach
end procedure
but in vain !
In case of SQL server I would recommend using rather function than store procedure. Header of function should look:
CREATE FUNCTION qr_get_dep_boss( #year DATE, #main_code INT)
RETURN #t TABLE ( boss_num INT, boss_name VARCHAR(50))
AS
... ( here boss_num and boss_name should be set )
In your main query you can use this function by:
SELECT year, main_code, ... , t.bos_num , t.boss_name
FROM ...
CROSS APPLY
qr_get_dep_boss(year, main_code) t
But I am not sure if it is possible in Informix, in case not you can always define two functions that returns single value. GL!
If I understand right, you don't need the second procedure.
What you need to do is use the RETURN xxx WITH RESUME into the procedure qr_get_dep_boss()
A example copied from the manual .
CREATE FUNCTION series (limit INT, backwards INT) RETURNING INT;
DEFINE i INT;
FOR i IN (1 TO limit)
RETURN i WITH RESUME;
END FOR;
IF backwards = 0 THEN
RETURN;
END IF;
FOR i IN (limit TO 1 STEP -1)
RETURN i WITH RESUME;
END FOR;
END FUNCTION; -- series