|
1 | 1 | <!--
|
2 |
| -$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.55 2005/01/08 22:13:34 tgl Exp $ |
| 2 | +$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.56 2005/01/14 01:16:22 tgl Exp $ |
3 | 3 | -->
|
4 | 4 |
|
5 | 5 | <chapter id="plpgsql">
|
@@ -2304,6 +2304,32 @@ SELECT reffunc2();
|
2304 | 2304 |
|
2305 | 2305 | FETCH ALL IN "<unnamed cursor 1>";
|
2306 | 2306 | COMMIT;
|
| 2307 | +</programlisting> |
| 2308 | + </para> |
| 2309 | + |
| 2310 | + <para> |
| 2311 | + The following example shows one way to return multiple cursors |
| 2312 | + from a single function: |
| 2313 | + |
| 2314 | +<programlisting> |
| 2315 | +CREATE FUNCTION myfunc(refcursor, refcursor) RETURNS SETOF refcursor AS $$ |
| 2316 | +BEGIN |
| 2317 | + OPEN $1 FOR SELECT * FROM table_1; |
| 2318 | + RETURN NEXT $1; |
| 2319 | + OPEN $2 FOR SELECT * FROM table_2; |
| 2320 | + RETURN NEXT $2; |
| 2321 | + RETURN; |
| 2322 | +END; |
| 2323 | +$$ LANGUAGE plpgsql; |
| 2324 | + |
| 2325 | +-- need to be in a transaction to use cursors. |
| 2326 | +BEGIN; |
| 2327 | + |
| 2328 | +SELECT * FROM myfunc('a', 'b'); |
| 2329 | + |
| 2330 | +FETCH ALL FROM a; |
| 2331 | +FETCH ALL FROM b; |
| 2332 | +COMMIT; |
2307 | 2333 | </programlisting>
|
2308 | 2334 | </para>
|
2309 | 2335 | </sect3>
|
@@ -2585,8 +2611,6 @@ $emp_stamp$ LANGUAGE plpgsql;
|
2585 | 2611 | CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
|
2586 | 2612 | FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
|
2587 | 2613 | </programlisting>
|
2588 |
| - |
2589 |
| - |
2590 | 2614 | </example>
|
2591 | 2615 |
|
2592 | 2616 | <para>
|
@@ -2646,6 +2670,159 @@ AFTER INSERT OR UPDATE OR DELETE ON emp
|
2646 | 2670 | FOR EACH ROW EXECUTE PROCEDURE process_emp_audit();
|
2647 | 2671 | </programlisting>
|
2648 | 2672 | </example>
|
| 2673 | + |
| 2674 | + <para> |
| 2675 | + One use of triggers is to maintain a summary table |
| 2676 | + of another table. The resulting summary can be used in place of the |
| 2677 | + original table for certain queries — often with vastly reduced run |
| 2678 | + times. |
| 2679 | + This technique is commonly used in Data Warehousing, where the tables |
| 2680 | + of measured or observed data (called fact tables) can be extremely large. |
| 2681 | + <xref linkend="plpgsql-trigger-summary-example"> shows an example of a |
| 2682 | + trigger procedure in <application>PL/pgSQL</application> that maintains |
| 2683 | + a summary table for a fact table in a data warehouse. |
| 2684 | + </para> |
| 2685 | + |
| 2686 | + |
| 2687 | + <example id="plpgsql-trigger-summary-example"> |
| 2688 | + <title>A <application>PL/pgSQL</application> Trigger Procedure For Maintaining A Summary Table</title> |
| 2689 | + |
| 2690 | + <para> |
| 2691 | + The schema detailed here is partly based on the <emphasis>Grocery Store |
| 2692 | + </emphasis> example from <emphasis>The Data Warehouse Toolkit</emphasis> |
| 2693 | + by Ralph Kimball. |
| 2694 | + </para> |
| 2695 | + |
| 2696 | +<programlisting> |
| 2697 | +-- |
| 2698 | +-- Main tables - time dimension and sales fact. |
| 2699 | +-- |
| 2700 | +CREATE TABLE time_dimension ( |
| 2701 | + time_key integer NOT NULL, |
| 2702 | + day_of_week integer NOT NULL, |
| 2703 | + day_of_month integer NOT NULL, |
| 2704 | + month integer NOT NULL, |
| 2705 | + quarter integer NOT NULL, |
| 2706 | + year integer NOT NULL |
| 2707 | +); |
| 2708 | +CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key); |
| 2709 | + |
| 2710 | +CREATE TABLE sales_fact ( |
| 2711 | + time_key integer NOT NULL, |
| 2712 | + product_key integer NOT NULL, |
| 2713 | + store_key integer NOT NULL, |
| 2714 | + amount_sold numeric(12,2) NOT NULL, |
| 2715 | + units_sold integer NOT NULL, |
| 2716 | + amount_cost numeric(12,2) NOT NULL |
| 2717 | +); |
| 2718 | +CREATE INDEX sales_fact_time ON sales_fact(time_key); |
| 2719 | + |
| 2720 | +-- |
| 2721 | +-- Summary table - sales by time. |
| 2722 | +-- |
| 2723 | +CREATE TABLE sales_summary_bytime ( |
| 2724 | + time_key integer NOT NULL, |
| 2725 | + amount_sold numeric(15,2) NOT NULL, |
| 2726 | + units_sold numeric(12) NOT NULL, |
| 2727 | + amount_cost numeric(15,2) NOT NULL |
| 2728 | +); |
| 2729 | +CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key); |
| 2730 | + |
| 2731 | +-- |
| 2732 | +-- Function and trigger to amend summarized column(s) on UPDATE, INSERT, DELETE. |
| 2733 | +-- |
| 2734 | +CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER AS $maint_sales_summary_bytime$ |
| 2735 | + DECLARE |
| 2736 | + delta_time_key integer; |
| 2737 | + delta_amount_sold numeric(15,2); |
| 2738 | + delta_units_sold numeric(12); |
| 2739 | + delta_amount_cost numeric(15,2); |
| 2740 | + BEGIN |
| 2741 | + |
| 2742 | + -- Work out the increment/decrement amount(s). |
| 2743 | + IF (TG_OP = 'DELETE') THEN |
| 2744 | + |
| 2745 | + delta_time_key = OLD.time_key; |
| 2746 | + delta_amount_sold = -1 * OLD.amount_sold; |
| 2747 | + delta_units_sold = -1 * OLD.units_sold; |
| 2748 | + delta_amount_cost = -1 * OLD.amount_cost; |
| 2749 | + |
| 2750 | + ELSIF (TG_OP = 'UPDATE') THEN |
| 2751 | + |
| 2752 | + -- forbid updates that change the time_key - |
| 2753 | + -- (probably not too onerous, as DELETE + INSERT is how most |
| 2754 | + -- changes will be made). |
| 2755 | + IF ( OLD.time_key != NEW.time_key) THEN |
| 2756 | + RAISE EXCEPTION 'Update of time_key : % -> % not allowed', OLD.time_key, NEW.time_key; |
| 2757 | + END IF; |
| 2758 | + |
| 2759 | + delta_time_key = OLD.time_key; |
| 2760 | + delta_amount_sold = NEW.amount_sold - OLD.amount_sold; |
| 2761 | + delta_units_sold = NEW.units_sold - OLD.units_sold; |
| 2762 | + delta_amount_cost = NEW.amount_cost - OLD.amount_cost; |
| 2763 | + |
| 2764 | + ELSIF (TG_OP = 'INSERT') THEN |
| 2765 | + |
| 2766 | + delta_time_key = NEW.time_key; |
| 2767 | + delta_amount_sold = NEW.amount_sold; |
| 2768 | + delta_units_sold = NEW.units_sold; |
| 2769 | + delta_amount_cost = NEW.amount_cost; |
| 2770 | + |
| 2771 | + END IF; |
| 2772 | + |
| 2773 | + |
| 2774 | + -- Update the summary row with the new values. |
| 2775 | + UPDATE sales_summary_bytime |
| 2776 | + SET amount_sold = amount_sold + delta_amount_sold, |
| 2777 | + units_sold = units_sold + delta_units_sold, |
| 2778 | + amount_cost = amount_cost + delta_amount_cost |
| 2779 | + WHERE time_key = delta_time_key; |
| 2780 | + |
| 2781 | + |
| 2782 | + -- There might have been no row with this time_key (e.g new data!). |
| 2783 | + IF (NOT FOUND) THEN |
| 2784 | + BEGIN |
| 2785 | + INSERT INTO sales_summary_bytime ( |
| 2786 | + time_key, |
| 2787 | + amount_sold, |
| 2788 | + units_sold, |
| 2789 | + amount_cost) |
| 2790 | + SELECT f.time_key, |
| 2791 | + sum(f.amount_sold), |
| 2792 | + sum(f.units_sold), |
| 2793 | + sum(f.amount_cost) |
| 2794 | + FROM sales_fact f |
| 2795 | + WHERE f.time_key = delta_time_key |
| 2796 | + GROUP BY f.time_key; |
| 2797 | + -- This query can potentially be very expensive if the trigger |
| 2798 | + -- is created on sales_fact without the time_key indexes. |
| 2799 | + -- Some care is needed to ensure that this situation does |
| 2800 | + -- *not* occur. |
| 2801 | + EXCEPTION |
| 2802 | + -- |
| 2803 | + -- Catch race condition when two transactions are adding data |
| 2804 | + -- for a new time_key. |
| 2805 | + -- |
| 2806 | + WHEN UNIQUE_VIOLATION THEN |
| 2807 | + UPDATE sales_summary_bytime |
| 2808 | + SET amount_sold = amount_sold + delta_amount_sold, |
| 2809 | + units_sold = units_sold + delta_units_sold, |
| 2810 | + amount_cost = amount_cost + delta_amount_cost |
| 2811 | + WHERE time_key = delta_time_key; |
| 2812 | + |
| 2813 | + END; |
| 2814 | + END IF; |
| 2815 | + RETURN NULL; |
| 2816 | + |
| 2817 | + END; |
| 2818 | +$maint_sales_summary_bytime$ LANGUAGE plpgsql; |
| 2819 | + |
| 2820 | +CREATE TRIGGER maint_sales_summary_bytime |
| 2821 | +AFTER INSERT OR UPDATE OR DELETE ON sales_fact |
| 2822 | + FOR EACH ROW EXECUTE PROCEDURE maint_sales_summary_bytime(); |
| 2823 | +</programlisting> |
| 2824 | + </example> |
| 2825 | + |
2649 | 2826 | </sect1>
|
2650 | 2827 |
|
2651 | 2828 | <!-- **** Porting from Oracle PL/SQL **** -->
|
|
0 commit comments