Thread: Plpgsql function to compute "every other Friday"

Plpgsql function to compute "every other Friday"

From
"C. Bensend"
Date:
Hey folks,

   So, I'm working on a little application to help me with my
budget.  Yeah, there are apps out there to do it, but I'm having
a good time learning some more too.  :)

   I get paid every other Friday.  I thought, for scheduling
purposes in this app, that I would take a stab at writing a plpgsql
function to determine if a given date is a payday.  Here is what I
have so far:


CREATE OR REPLACE FUNCTION is_payday( d DATE ) RETURNS BOOLEAN AS $$

        DECLARE epoch DATE;
                days_since_epoch INTEGER;
                mult FLOAT8;
                ret BOOLEAN := FALSE;

BEGIN

        SELECT INTO epoch option_value
                FROM options WHERE option_name = 'payroll_epoch';

        SELECT INTO days_since_epoch ( SELECT CURRENT_DATE - d);

        *** here's where I'm stuck ***

        RETURN ret;

END;
$$ LANGUAGE plpgsql;


   OK.  So, I have a "starting" payday (payroll_epoch) in an options
table.  That is the first payday of the year.  I then calculate the
number of days between that value and the date I pass to the function.
Now I need to calculate whether this delta (how many days since
epoch) is an even multiple of 14 days (the two weeks).

   I have no idea how to do that in plpgsql.  Basically, I need to
figure out if the date I pass to the function is a payday, and if
it is, return TRUE.

   I would very much appreciate any help with this last bit of math
and syntax, as well as any advice on whether this is a reasonable
way to attack the problem.  And no - this isn't a homework
assignment.  :)

Thanks folks!

Benny


--
"Hairy ape nads."        -- Colleen, playing Neverwinter Nights





Re: Plpgsql function to compute "every other Friday"

From
"David Johnston"
Date:
It is a very simplistic approach since you do not take into account
holidays.  But if it meets your needs what you want is the modulo operator (
"%"; "mod(x,y)" is the equivalent function ) which performs division but
returns only the remainder.

N % 14 = [a number between 0 and (14 - 1)]

N = 7; 7 % 14 = 7 (0, 7 remainder)
N = 14; 14 % 14 = 0 (1, 0 remainder)
N = 28; 28 % 14 = 0  (2, 0 remainder)
N = 31; 31 % 14 = 3 (2, 3 remainder)

If you KNOW the epoch date you are using is a Friday then you have no need
for CURRENT_DATE since you are passing in a date to check as a function
parameter.

I'll have to leave it to you or others to address the specific way to
integrate the modulo operator/function into the algorithm.

David J.

-----Original Message-----
From: pgsql-general-owner@postgresql.org
[mailto:pgsql-general-owner@postgresql.org] On Behalf Of C. Bensend
Sent: Monday, April 04, 2011 8:12 PM
To: pgsql-general@postgresql.org
Subject: [GENERAL] Plpgsql function to compute "every other Friday"


Hey folks,

   So, I'm working on a little application to help me with my budget.  Yeah,
there are apps out there to do it, but I'm having a good time learning some
more too.  :)

   I get paid every other Friday.  I thought, for scheduling purposes in
this app, that I would take a stab at writing a plpgsql function to
determine if a given date is a payday.  Here is what I have so far:


CREATE OR REPLACE FUNCTION is_payday( d DATE ) RETURNS BOOLEAN AS $$

        DECLARE epoch DATE;
                days_since_epoch INTEGER;
                mult FLOAT8;
                ret BOOLEAN := FALSE;

BEGIN

        SELECT INTO epoch option_value
                FROM options WHERE option_name = 'payroll_epoch';

        SELECT INTO days_since_epoch ( SELECT CURRENT_DATE - d);

        *** here's where I'm stuck ***

        RETURN ret;

END;
$$ LANGUAGE plpgsql;


   OK.  So, I have a "starting" payday (payroll_epoch) in an options table.
That is the first payday of the year.  I then calculate the number of days
between that value and the date I pass to the function.
Now I need to calculate whether this delta (how many days since
epoch) is an even multiple of 14 days (the two weeks).

   I have no idea how to do that in plpgsql.  Basically, I need to figure
out if the date I pass to the function is a payday, and if it is, return
TRUE.

   I would very much appreciate any help with this last bit of math and
syntax, as well as any advice on whether this is a reasonable way to attack
the problem.  And no - this isn't a homework assignment.  :)

Thanks folks!

Benny


--
"Hairy ape nads."        -- Colleen, playing Neverwinter Nights





--
Sent via pgsql-general mailing list (pgsql-general@postgresql.org) To make
changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-general


Re: Plpgsql function to compute "every other Friday"

From
Andy Colson
Date:
On 04/04/2011 07:12 PM, C. Bensend wrote:
>
> Hey folks,
>
>     So, I'm working on a little application to help me with my
> budget.  Yeah, there are apps out there to do it, but I'm having
> a good time learning some more too.  :)
>
>     I get paid every other Friday.  I thought, for scheduling
> purposes in this app, that I would take a stab at writing a plpgsql
> function to determine if a given date is a payday.  Here is what I
> have so far:
>
>
> CREATE OR REPLACE FUNCTION is_payday( d DATE ) RETURNS BOOLEAN AS $$
>
>          DECLARE epoch DATE;
>                  days_since_epoch INTEGER;
>                  mult FLOAT8;
>                  ret BOOLEAN := FALSE;
>
> BEGIN
>
>          SELECT INTO epoch option_value
>                  FROM options WHERE option_name = 'payroll_epoch';
>
>          SELECT INTO days_since_epoch ( SELECT CURRENT_DATE - d);
>
>          *** here's where I'm stuck ***
>
>          RETURN ret;
>
> END;
> $$ LANGUAGE plpgsql;
>
>
>     OK.  So, I have a "starting" payday (payroll_epoch) in an options
> table.  That is the first payday of the year.  I then calculate the
> number of days between that value and the date I pass to the function.
> Now I need to calculate whether this delta (how many days since
> epoch) is an even multiple of 14 days (the two weeks).
>
>     I have no idea how to do that in plpgsql.  Basically, I need to
> figure out if the date I pass to the function is a payday, and if
> it is, return TRUE.
>
>     I would very much appreciate any help with this last bit of math
> and syntax, as well as any advice on whether this is a reasonable
> way to attack the problem.  And no - this isn't a homework
> assignment.  :)
>
> Thanks folks!
>
> Benny
>
>

Not sure if your needs are like mine, but here is the function I use.  It stores the date in a config table, and rolls
itforward when needed.  It also calculates it from some "know payroll date", which I'm guessing was near when I wrote
it? (I'm not sure why I choose Nov 16 2008.)  for me, this procedure is called a lot, and the things calling it expect
itto roll into the next pay period.  Not sure if it'll work for you, but might offer some ideas. 



CREATE OR REPLACE FUNCTION startpayperiod()
  RETURNS date
  LANGUAGE plpgsql
AS $function$
declare
         st date;
         last date;
         needins boolean;
begin
         select avalue::date into st from config where akey = 'startPayPeriod';
         if (st is null) then
                 st := '2008.11.16';
                 needins := true;
         else
                 needins := false;
         end if;
         -- find the end of the pp
         last := st + interval '13 days';
         if (current_date > last) then
                 -- raise notice 'need update';
                 loop
                         last := st;
                         st := st + interval '2 weeks';
                         if current_date < st then
                                 exit;
                         end if;
                 end loop;
                 st := last;
                 if needins then
                         insert into config(akey, avalue) values('startPayPeriod', st::text);
                 else
                         update config set avalue = st::text where akey = 'startPayPeriod';
                 end if;
         end if;
         return st;
end; $function$


-Andy

Re: Plpgsql function to compute "every other Friday"

From
John R Pierce
Date:
generate_series(date '2001-01-05', date '2020-12-31', interval '2 weeks')


will return every payday from jan 5 2001 to the end of 2020 (assuming
the 5th was payday, change the start to jan 12 if that was instead).



Re: Plpgsql function to compute "every other Friday"

From
"C. Bensend"
Date:
> It is a very simplistic approach since you do not take into account
> holidays.  But if it meets your needs what you want is the modulo operator
> (
> "%"; "mod(x,y)" is the equivalent function ) which performs division but
> returns only the remainder.
>
> N % 14 = [a number between 0 and (14 - 1)]
>
> N = 7; 7 % 14 = 7 (0, 7 remainder)
> N = 14; 14 % 14 = 0 (1, 0 remainder)
> N = 28; 28 % 14 = 0  (2, 0 remainder)
> N = 31; 31 % 14 = 3 (2, 3 remainder)

Ah, thank you, David.  This gives me some good knowledge that I
was missing!  I know about %, but I was fumbling a bit with it in
plpgsql, and your examples helped.

Thanks!

Benny


--
"Hairy ape nads."        -- Colleen, playing Neverwinter Nights



Re: Plpgsql function to compute "every other Friday"

From
"C. Bensend"
Date:
> Not sure if your needs are like mine, but here is the function I use.  It
> stores the date in a config table, and rolls it forward when needed.  It
> also calculates it from some "know payroll date", which I'm guessing was
> near when I wrote it?  (I'm not sure why I choose Nov 16 2008.)  for me,
> this procedure is called a lot, and the things calling it expect it to
> roll into the next pay period.  Not sure if it'll work for you, but might
> offer some ideas.

Great stuff, Andy!  Thank you for this - this function gives me a
lot of great hints about functions in plpgsql.  Very useful
indeed.

Benny


--
"Hairy ape nads."        -- Colleen, playing Neverwinter Nights



Re: Plpgsql function to compute "every other Friday"

From
"C. Bensend"
Date:
> generate_series(date '2001-01-05', date '2020-12-31', interval '2 weeks')
>
>
> will return every payday from jan 5 2001 to the end of 2020 (assuming
> the 5th was payday, change the start to jan 12 if that was instead).

And THERE is the winner.  I feel like an idiot for not even
considering generate_series().

Thanks a bunch, John!  This will do nicely!

Benny


--
"Hairy ape nads."        -- Colleen, playing Neverwinter Nights



Re: Plpgsql function to compute "every other Friday"

From
Merlin Moncure
Date:
On Mon, Apr 4, 2011 at 7:12 PM, C. Bensend <benny@bennyvision.com> wrote:
>
> Hey folks,
>
>   So, I'm working on a little application to help me with my
> budget.  Yeah, there are apps out there to do it, but I'm having
> a good time learning some more too.  :)
>
>   I get paid every other Friday.  I thought, for scheduling
> purposes in this app, that I would take a stab at writing a plpgsql
> function to determine if a given date is a payday.  Here is what I
> have so far:
>
>
> CREATE OR REPLACE FUNCTION is_payday( d DATE ) RETURNS BOOLEAN AS $$
>
>        DECLARE epoch DATE;
>                days_since_epoch INTEGER;
>                mult FLOAT8;
>                ret BOOLEAN := FALSE;
>
> BEGIN
>
>        SELECT INTO epoch option_value
>                FROM options WHERE option_name = 'payroll_epoch';
>
>        SELECT INTO days_since_epoch ( SELECT CURRENT_DATE - d);
>
>        *** here's where I'm stuck ***
>
>        RETURN ret;
>
> END;
> $$ LANGUAGE plpgsql;
>
>
>   OK.  So, I have a "starting" payday (payroll_epoch) in an options
> table.  That is the first payday of the year.  I then calculate the
> number of days between that value and the date I pass to the function.
> Now I need to calculate whether this delta (how many days since
> epoch) is an even multiple of 14 days (the two weeks).
>
>   I have no idea how to do that in plpgsql.  Basically, I need to
> figure out if the date I pass to the function is a payday, and if
> it is, return TRUE.
>
>   I would very much appreciate any help with this last bit of math
> and syntax, as well as any advice on whether this is a reasonable
> way to attack the problem.  And no - this isn't a homework
> assignment.  :)
>
> Thanks folks!
>
> Benny

first, let's fix your function definition.  I would advise you to take
in both the base pay date (so we know which 'every other' to use) and
the epoch so you don't have to read it from the database in the
function.  Why do that? you can make your function immutable.

CREATE OR REPLACE FUNCTION is_payday(
  d DATE,
  base_date DATE,
  payroll_epoch INT) RETURNS BOOLEAN AS $$
  SELECT (select extract('j' from $1)::int - select extract('j' from
$2)::int) % $3 = 0;
$$ LANGUAGE sql IMMUTABLE;

By making this function sql and immutable, you give the database more
ability to inline it into queries which can make a tremendous
performance difference in some cases.  You can also index based on it
which can be useful.

By pulling out julian days, we can do simple calculation based on days
(julian day, not to be confused with julian calendar, is kinda sorta
like epoch for days.  While it doesn't really apply to this toy
example, a key thing to remember if if trying to write high
performance pl/pgsql is to separate stable/immutable, and volatile
elements.  Also, use sql, not plpgsql in trivial functions.

If you don't want to select out your option in every query, I'd advise
making an option() function which wraps the trivial select:

select is_payday(some_date, option('base_date'), option('payroll_epoch');

The 'option' function should be stable.

merlin

Re: Plpgsql function to compute "every other Friday"

From
"C. Bensend"
Date:
> By making this function sql and immutable, you give the database more
> ability to inline it into queries which can make a tremendous
> performance difference in some cases.  You can also index based on it
> which can be useful.

Very nice, Merlin.  These aren't really a concern in my case as
I'm the only one accessing the app (and hence, performance isn't
an issue), but it's good to see a better way to do things.

The PostgreSQL community really is top notch.

Benny


--
"Hairy ape nads."        -- Colleen, playing Neverwinter Nights