Question

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;
Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top