Re: join and dynamic view

From: Gary Stainburn <gary(dot)stainburn(at)ringways(dot)co(dot)uk>
To: Christoph Haller <ch(at)rodos(dot)fzk(dot)de>
Cc: pgsql-sql(at)postgresql(dot)org
Subject: Re: join and dynamic view
Date: 2002-12-17 13:09:34
Message-ID: 200212171309.34174.gary.stainburn@ringways.co.uk
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-sql

Thanks for that Christoph.

I've got the view I need :

create view users as
select s.*, o.rrank as ops, m.rrank as mpd from staff s
left outer join ranks o on o.rsid = s.sid and o.rdid = 'O'
left outer join ranks m on m.rsid = s.sid and m.rdid = 'M';

which provides:

garytest=# select * from users;
sid | sname | ops | mpd
-----+---------+-----+-----
1 | Rod | | 3
2 | Jayne | 5 | 2
3 | Freddie | 3 |
(3 rows)

garytest=#

I've now started amending your plpgsql script to create this, but as you can
see I've cocked up somewhere. I wonder if you could have a peek at it for
me.

create_users_view() returns integer as '
DECLARE
pg_views_rtype pg_views%ROWTYPE;
vname_param TEXT;
ranks_record RECORD;
create_view TEXT;
join_text TEXT;
BEGIN

vname_param:=''users'';

SELECT INTO pg_views_rtype * FROM pg_views WHERE viewname = vname_param;

IF FOUND THEN
EXECUTE ''DROP VIEW '' || quote_ident(vname_param) ;
END IF;
create_view :=
''CREATE VIEW '' || quote_ident(vname_param) ||
'' AS SELECT s.* '';
join_text:='' from staff s '';
FOR ranks_record IN
EXECUTE ''SELECT did, dsdesc from depts ORDER BY did;''
LOOP
create_view :=
create_view || '', '' || ranks_record.dsdesc ||
'' AS '' || ranks_record.did);
join_text :=
join_text || '' left outer join ranks '' || ranks_record.did ||
'' ON '' || ranks_record.did || ''.rsid = s.sid and '' ||
ranks_record.did || ''.rdid = '''' || ranks_record.did '''' ;
END LOOP;
create_view :=
create_view || join_text || '';'';
EXECUTE create_view ;

RETURN 0;
END;
' LANGUAGE 'plpgsql' ;
ERROR: parser: parse error at or near "or"

On Tuesday 17 Dec 2002 12:40 pm, Christoph Haller wrote:
> > What I mean here was that if I add another row to the depts table,
>
> e.g.
>
> > A ADM Administrative
> >
> > I would like the ADM column to automatically appear in the 'myview'
>
> view
>
> > without having to recreate the view - i.e. the rows in the 'depts'
>
> table
>
> > become columns in 'myview' view
>
> Yes, that's what I thought you intended.
>
> > Surely the problem with this is that I'd have to drop/amend/create
>
> the view
>
> > every time I add a row to 'depts'. Couldn't I just do that using an
>
> outer
>
> > join instead of a case?
>
> Possibly, but so far I've no idea how to achieve that.
>
> > How could a plpgsql dynamically create the view?
> > How about a trigger from the on-update of the depts table to drop the
>
> view and
>
> > then create a new one. Could it not do the same thing using outer
>
> joins.
>
> > (I've done VERY little plpgsql and even less with triggers.
>
> I've done VERY little with triggers, too.
> But, how to dynamically create a view, see for yourself:
>
>
> Based on the e-mails on "Generating a cross tab (pivot table)",
> I can give you a PLpgSQL procedure to automatically generate a
> cross tab from any relation now.
> It's my first steps in PLpgSQL. I am pretty sure this is not the
> best way to implement, but I wanted to get some experience, so I
> did it this way.
>
> For all, who missed it last week, again the objective:
> There is a relation "sales",
> holding the sales of different products of different vendors.
> The task is to generate a report which shows the sales
> of every vendor and every product.
>
> Consider the following table populated with some data:
> CREATE TABLE sales (
> product TEXT,
> vendor TEXT,
> sales INTEGER
> );
>
> INSERT INTO sales VALUES ( 'milk' , 'mr. pink' , 12 ) ;
> INSERT INTO sales VALUES ( 'milk' , 'mr. brown' , 8 ) ;
> INSERT INTO sales VALUES ( 'honey' , 'mr. green' , 2 ) ;
> INSERT INTO sales VALUES ( 'milk' , 'mr. green' , 34 ) ;
> INSERT INTO sales VALUES ( 'butter', 'mr. pink' , 17 ) ;
> INSERT INTO sales VALUES ( 'butter', 'mr. brown' , 2 ) ;
> INSERT INTO sales VALUES ( 'honey' , 'mr. pink' , 19 ) ;
> The following query generates the report:
> CREATE VIEW sales_report AS
> SELECT product,
> SUM(CASE vendor WHEN 'mr. pink' THEN sales ELSE 0 END) AS "mr.
> pink ",
> SUM(CASE vendor WHEN 'mr. brown' THEN sales ELSE 0 END) AS "mr.
> brown",
> SUM(CASE vendor WHEN 'mr. green' THEN sales ELSE 0 END) AS "mr.
> green",
> SUM(sales) AS "sum of sales"
> FROM sales GROUP BY product ;
> SELECT * FROM sales_report ;
>
> product | mr. pink | mr. brown | mr. green | sum of sales
> ---------+-----------+-----------+-----------+--------------
> butter | 17 | 2 | 0 | 19
> honey | 19 | 0 | 2 | 21
> milk | 12 | 8 | 34 | 54
> (3 rows)
> It's obvious this approach is most inflexible.
> As soon as there is a new vendor, one has to re-write the query and add
> SUM(CASE vendor WHEN 'mr. new' THEN ... ,
>
> So what we need is a tool to automatically adapt the view to new vendors
>
> resp. new products.
> Here it is (choosing good mnemonics is not my favourite discipline):
>
> CREATE OR REPLACE FUNCTION
> create_pivot_report(TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) RETURNS INTEGER AS '
> DECLARE
> pg_views_rtype pg_views%ROWTYPE;
> vname_param ALIAS FOR $1;
> pivot_column ALIAS FOR $2;
> select_column ALIAS FOR $3;
> pivot_table ALIAS FOR $4;
> aggregate_func ALIAS FOR $5;
> aggr_column ALIAS FOR $6;
> pivot_record RECORD;
> create_view TEXT;
> BEGIN
>
> SELECT INTO pg_views_rtype * FROM pg_views WHERE viewname = vname_param;
>
> IF FOUND THEN
> EXECUTE ''DROP VIEW '' || quote_ident(vname_param) ;
> END IF;
> create_view :=
> ''CREATE VIEW '' || quote_ident(vname_param) ||
> '' AS SELECT '' || quote_ident(select_column) ;
> FOR pivot_record IN
> EXECUTE ''SELECT DISTINCT CAST(''
>
> || quote_ident(pivot_column)
> || '' AS TEXT) AS col1 FROM ''
> || quote_ident(pivot_table)
> ||
> || '' ORDER BY '' || quote_ident(pivot_column)
>
> LOOP
> create_view :=
> create_view || '','' || aggregate_func ||
> ''(CASE '' || quote_ident(pivot_column) ||
> '' WHEN '' || quote_literal(pivot_record.col1) ||
> '' THEN '' || quote_ident(aggr_column) ||
> '' ELSE 0 END) AS "'' || pivot_record.col1 || ''"'' ;
> END LOOP;
> create_view :=
> create_view || '','' || aggregate_func ||
> ''('' || quote_ident(aggr_column) || '') AS "'' || aggregate_func ||
> '' of '' || aggr_column || ''" FROM '' || quote_ident(pivot_table) ||
> '' GROUP BY '' || quote_ident(select_column);
> EXECUTE create_view ;
>
> RETURN 0;
> END;
> ' LANGUAGE 'plpgsql' ;
>
> -- where
> -- vname_param ALIAS FOR $1; -- the view's name to create
> -- pivot_column ALIAS FOR $2; -- the pivot column (entries to be
> CASEd)
> -- select_column ALIAS FOR $3; -- the select column (entries to be
> grouped)
> -- pivot_table ALIAS FOR $4; -- the name of the table to work on
> -- aggregate_func ALIAS FOR $5; -- the name of the aggregate function
> -- aggr_column ALIAS FOR $6; -- the aggregate column (entries to be
> aggregated)
>
> First try:
> SELECT create_pivot_report
> ('sales_report2','vendor','product','sales','sum','sales');
> SELECT * FROM sales_report2 ;
> gives you 'sales_report2' as a copy of 'sales_report'.
>
> Now add another data set:
> INSERT INTO sales VALUES ( 'butter', 'mr. blue' , 11 ) ;
> Re-write the view by:
> SELECT create_pivot_report
> ('sales_report2','vendor','product','sales','sum','sales');
> And here we go
> SELECT * FROM sales_report2 ;
> product | mr. blue | mr. brown | mr. green | mr. pink | sum of sales
> ---------+----------+-----------+-----------+----------+--------------
> butter | 11 | 2 | 0 | 17 | 30
> honey | 0 | 0 | 2 | 19 | 21
> milk | 0 | 8 | 34 | 12 | 54
> (3 rows)
>
> More examples:
> SELECT create_pivot_report
> ('sales_report3','vendor','product','sales','avg','sales');
> SELECT create_pivot_report
> ('sales_report4','vendor','product','sales','stddev','sales');
> SELECT create_pivot_report
> ('sales_report5','product','vendor','sales','sum','sales');
> SELECT create_pivot_report
> ('sales_report6','product','vendor','sales','max','sales');
> SELECT create_pivot_report
> ('sales_report7','vendor','product','sales','max','sales');
>
> As you can see even interchanging the pivot column and the select column
>
> works. Feel free to use the code.
>
> Regards, Christoph

--
Gary Stainburn

This email does not contain private or confidential material as it
may be snooped on by interested government parties for unknown
and undisclosed purposes - Regulation of Investigatory Powers Act, 2000

Browse pgsql-sql by date

  From Date Subject
Next Message Tomasz Myrta 2002-12-17 13:36:53 Re: join and dynamic view
Previous Message Christoph Haller 2002-12-17 13:06:55 Re: join and dynamic view