[SQL] Creating an aggregate function

2000-09-09 Thread Roberto Mello

Hi all, 

This e-mail is long because I am providing lots of details on the
problem. It's an OpenACS module that we are porting so it'll have lots
of users, you can be sure.

I have this view that I am porting that is a UNION of two self OUTER
JOINed views. PG does not support UNIONs in views, so we thought of
creating our own aggregate functions to do the job. 

There are no examples for CREATE AGGREGATE in the docs, but we made it
work, at least partially, but it explodes a little after the first row
column. 

Here's the original Oracle query: 

   create or replace view cs_n_sessions_day_user as
   select b.date_id, b.n_sessions_day, nvl(a.n_users, 0) as members,
 b.n_users as non_members
   from cs_historical_visits_grouped a,cs_historical_visits_grouped b
   where  b.n_sessions_day = a.n_sessions_day(+)
 and b.date_id = a.date_id(+)
 and 1 = a.member_p(+)
 and b.member_p = 0
   UNION
   select a.date_id, a.n_sessions_day, a.n_users as members, 
 nvl(b.n_users, 0) as non_members
   from cs_historical_visits_grouped a, cs_historical_visits_grouped b
   where  a.n_sessions_day = b.n_sessions_day(+)
 and a.date_id = b.date_id(+)
 and a.member_p = 1
 and 0 = b.member_p(+);
Here is our port of the cs_historical_visits_grouped view: 

   create view cs_historical_visits_grouped as 
   select date_id, n_sessions_day, not_null_integer_p(user_id) as
   member_p, count(browser_id) as n_users 
   from cs_historical_visits 
   group by date_id, n_sessions_day;

not_null_integer_p is a function we created that basically does case
when user_id is null then 1 else 0 end, but for some reason PG wouldn't
take it in a view. 

Here's a helper view for our aggregate function: 

   create view cs_hist_visits_grouped_txt as 
   select date_id, n_sessions_day, member_p, n_users,
  member_p::varchar || ' ' || n_users::varchar as
   member_p_n_users
   from cs_historical_visits_grouped;

And here are the aggregate functions: 

   create function agg_if_member (integer, varchar)
   returns integer as '
   declare
   a_int alias for $1;
   a_varchar alias for $2;
   member_p integer;
   num_members integer;
   begin
   member_p := substr(a_varchar, 1, 1)::integer;
   num_members := substr(a_varchar, 3)::integer;
   if member_p = 1 then
   return num_members;
   else
   return a_int;
   end if;
   end;
   ' language 'plpgsql';

   create function agg_if_not_member (integer, varchar)
   returns integer as '
   declare
   a_int alias for $1;
   a_varchar alias for $2;
   return_val integer;
   member_p integer;
   num_members integer;
   begin
   member_p := substr(a_varchar, 1, 1)::integer;
   num_members := substr(a_varchar, 3)::integer;
   return_val := 0;
   if a_int is not null then
  return_val := a_int;
   end if;
   if member_p = 0 and num_members > a_int then
   return_val := num_members;
   end if;
   return return_val;
   end;
   ' language 'plpgsql';

   create aggregate num_members_or_zero (
 basetype = varchar,
 stype1 = integer,
 sfunc1 = agg_if_member,
 initcond1 = 0);

   create aggregate num_non_members_or_zero (
  basetype = varchar,
  stype1 = integer,
  sfunc1 = agg_if_not_member,
  initcond1 = 0);

and our query was: 

 select date_id, n_sessions_day,
 num_members_or_zero(member_p_n_users) as members,
 num_non_members_or_zero(member_p_n_users) as non_members 
 from foobar 
 group by date_id, n_sessions_day;

Our fake table foobar had the same structure as our cs_hist_grouped_txt
view has, and this was the dummy data we had inserted to
play with: 


 date_id | n_sessions_day | member_p | n_users | member_p_n_users
 -++--+-+--
1 |500 |0 |  50 | 0 50
1 |500 |1 |  30 | 1 30
2 |   2000 |1 | 210 | 1 210
2 |   2000 |0 | 999 | 0 999   

and our output was: 


  date_id | n_sessions_day |  members  | non_members
 -++---+-
1 |500 |30 |   136394044
2 |   2000 | 136394612 |   136394612 

the values for members is what we expected for date_id == 1, but the
other ones are all goofy. Anybody has any ideas or worked with CREATE
AGGREGATE before? 

Sorry about the long email and TIA. 

-Roberto Mello
-- 
Roberto Mello, [EMAIL PROTECTED] - GNU/Linux Reg.User #96240 
 Computer Science - Utah State University
 USU Free Software and GNU/Linux Club, Pres

Re: [SQL] Creating an aggregate function

2000-09-09 Thread Tom Lane

Roberto Mello <[EMAIL PROTECTED]> writes:
> and our output was: 
>   date_id | n_sessions_day |  members  | non_members
>  -++---+-
> 1 |500 |30 |   136394044
> 2 |   2000 | 136394612 |   136394612 

In current sources I get the expected results if I write the
CREATE AGGREGATE commands with
initcond1 = '0');
Although the parser will accept unquoted integers as initcond
inputs, it looks like something downstream is mishandling them,
resulting in an invalid initial value entered into the pg_aggregate
entry for the aggregate.

I'll look into fixing that for 7.1, but in the meantime try putting
quotes around the initial values.

BTW, your agg_if_member function is not very good, because if it gets
more than one input row with member_p = 1 then the result is order-
dependent.  You don't want that.

regards, tom lane



Re: [SQL] Porting from mysql to psql (UNIX_TIMESTAMP()?)

2000-09-09 Thread Tom Lane

Zlatko Calusic <[EMAIL PROTECTED]> writes:
> Is there any similar functionality (returning unixish number of
> seconds since 1970 from the timestamp field) in PostgreSQL?

Sure.  You can use date_part, or cast to abstime and thence to integer:

regression=# select now();
  now

 2000-09-09 12:55:50-04
(1 row)

regression=# select date_part('epoch',now());
 date_part
---
 968518563
(1 row)

regression=# select now()::abstime::int4;
 ?column?
---
 968518585
(1 row)

To go the other way (integer seconds to timestamp), use the cast
method in reverse:

regression=# select 968518585 :: int4 :: abstime :: timestamp;
?column?

 2000-09-09 12:56:25-04
(1 row)

(there's probably a cleaner way to do this, but that works ...)

regards, tom lane



[SQL] Porting from mysql to psql (UNIX_TIMESTAMP()?)

2000-09-09 Thread Zlatko Calusic

Hi!

As subject says, we are currently porting all of our data, programs
and logic from mysql to postgresql. One of the things we have yet to
resolve is how to replace mysql's UNIX_TIMESTAMP() function we used
extensively in PosgreSQL?

Function works like this in mysql:

mysql> select start from connection limit 1;
+-+
| start   |
+-+
| 2000-07-03 20:12:37 |
+-+
1 row in set (0.01 sec)

mysql> select UNIX_TIMESTAMP(start) from connection limit 1;
+---+
| UNIX_TIMESTAMP(start) |
+---+
| 962647957 |
+---+
1 row in set (0.00 sec)

Is there any similar functionality (returning unixish number of
seconds since 1970 from the timestamp field) in PostgreSQL?

I tried all of the available date/time functions, type casting but all
to no avail.

TIA,
-- 
Zlatko

P.S Is it bad manners crossposting to two pgsql mailing list? Still
new to PostgreSQL, still learning...



[SQL] Re: [GENERAL] Porting from mysql to psql (UNIX_TIMESTAMP()?)

2000-09-09 Thread Zlatko Calusic

Zlatko Calusic <[EMAIL PROTECTED]> writes:

> Is there any similar functionality (returning unixish number of
> seconds since 1970 from the timestamp field) in PostgreSQL?

It's amazing that I spent something like an hour trying to find an
answer, and only ten minutes waiting for the answer to it. :)

Mario Weilguni came up with a date_part('epoch', start) solution, and
that is exactly what I was looking for.

Thanks Mario!
-- 
Zlatko