Thread: Query performance problem

Query performance problem

From
Phil Daintree
Date:

Dear psqlers,

I need your help!

I administer/develop an open source PHP accounting software project (webERP)
that was originally mysql only. Since Christmas I and another member of the
team lower cased all the sql and changed some elements of the SQL to allow it
to use postgres as well. All appears to work beautifully with just a single
but important exception.....

I am using PostgreSQL 7.4.6 running on :5432 on Mandrake 10.1

We wish to avoid lock in to a single database and have a single code base. We
elected to commonise the sql to ansi standard so far as possible in
preference to using stored procs. Whilst this will mean some compromises to
the performance, the sql is quite simple througout as this is a design goal
and I like having the sql inside the code for readability purposes ie its all
in one place. (I know I am wrong from a computer scientist's perspective!)

There are 2 tables used in the sql we need to optimise .....

CREATE TABLE chartdetails (   accountcode integer DEFAULT 0 NOT NULL,   period integer DEFAULT 0 NOT NULL,   budget
doubleprecision DEFAULT (0)::double precision NOT NULL,   actual double precision DEFAULT (0)::double precision NOT
NULL,  bfwd double precision DEFAULT (0)::double precision NOT NULL,   bfwdbudget double precision DEFAULT (0)::double
precisionNOT NULL
 
);


CREATE INDEX idxperiod ON chartdetails USING btree (period);
ALTER TABLE ONLY chartdetails   ADD CONSTRAINT chartdetails_pkey PRIMARY KEY (accountcode, period);
ALTER TABLE ONLY chartdetails   ADD CONSTRAINT cnt001251 FOREIGN KEY (accountcode) REFERENCES
chartmaster(accountcode);
ALTER TABLE ONLY chartdetails   ADD CONSTRAINT cnt001252 FOREIGN KEY (period) REFERENCES
periods(periodno);



AND the second table:


CREATE TABLE gltrans (   counterindex serial NOT NULL,   "type" integer DEFAULT 0 NOT NULL,   typeno bigint DEFAULT
(1)::bigintNOT NULL,   chequeno integer DEFAULT 0 NOT NULL,   trandate date,   periodno integer DEFAULT 0 NOT NULL,
accountinteger DEFAULT 0 NOT NULL,   narrative text DEFAULT ''::text NOT NULL,   amount double precision DEFAULT
(0)::doubleprecision NOT NULL,   posted integer DEFAULT 0 NOT NULL,   jobref text DEFAULT ''::text NOT NULL
 
);


CREATE INDEX idxaccount ON gltrans USING btree (account);
CREATE INDEX idxchequeno ON gltrans USING btree (chequeno);
CREATE INDEX idxgtperiodno ON gltrans USING btree (periodno);
CREATE INDEX idxposted ON gltrans USING btree (posted);
CREATE INDEX idxgttrandate ON gltrans USING btree (trandate);
CREATE INDEX idxgttypeno ON gltrans USING btree (typeno);
CREATE INDEX idxtype_and_number ON gltrans USING btree ("type", typeno);
CREATE INDEX idxgtjobref ON gltrans USING btree (jobref);
ALTER TABLE ONLY gltrans   ADD CONSTRAINT gltrans_pkey PRIMARY KEY (counterindex);
ALTER TABLE ONLY gltrans   ADD CONSTRAINT cnt001296 FOREIGN KEY (account) REFERENCES
chartmaster(accountcode);
ALTER TABLE ONLY gltrans   ADD CONSTRAINT cnt001297 FOREIGN KEY ("type") REFERENCESsystypes(typeid); ALTER TABLE ONLY
gltrans  ADD CONSTRAINT cnt001298 FOREIGN KEY (periodno) REFERENCES
 
periods(periodno);


So there is a chartdetail record for every period for every general ledger
account.  So if there are 5 years x 12 periods (months) and 200 general
ledger accounts this table will be 12,000 records.

There is a gltrans record for every side of a journal entry. This can get to
be quite a significant table - easily more than 200,000 per annum - depending
on the size of the business obviously.


Can anyone tell me why does the following code chokes ... literally - this
works almost invisbly under mysql - pg takes more than an hour even on a very
small 30 record database.

(The table chartmaster is just a list of general ledger accounts accountcode
and accountdescription. PK = accountcode)

$ChartAccounts = DB_query('SELECT accountcode FROM chartmaster',$db);
While ($AccountRow = DB_fetch_array($ChartAccounts)){
    for ($PeriodNo=$CreateFrom;$PeriodNo <= $CreateTo;$PeriodNo++) {
        echo '<LI>' . _('Period Number') . ' ' . $PeriodNo . '</LI>';
        // Check if there is an chart details record set up        $sql = 'SELECT count(*) FROM chartdetails
   WHERE accountcode='.$AccountRow['accountcode'].'                AND period=' . $PeriodNo;        $InsChartDetails =
DB_query($sql,$db,'','','',false);       $CountRows = DB_fetch_row($InsChartDetails);        $AccountExistsAlready =
$CountRows[0];       DB_free_result($InsChartDetails);        if(! $AccountExistsAlready) {            $sql = 'INSERT
INTOchartdetails (accountcode,                        period)                VALUES (' . $AccountRow['accountcode'] .
',                   ' . $PeriodNo . ')';            $InsChartDetails = DB_query($sql,$db);
DB_free_result($InsChartDetails);       }
 
    }
/*Now run through each of the new chartdetail records created for each
account and update them with the B/Fwd and B/Fwd budget no updates would be
required where there were previously no chart details set up ie
FirstPeriodPostedTo > 0 */
    for ($PeriodNo=$CreateFrom;$PeriodNo<=$CreateTo; $PeriodNo++) {
        $sql = 'SELECT accountcode,                period,                actual + bfwd AS cfwd,                budget
+bfwdbudget AS cfwdbudget            FROM chartdetails WHERE period =' . ($PeriodNo  - 1);        $ChartDetailsCFwd =
DB_query($sql,$db);
        while ($myrow = DB_fetch_array($ChartDetailsCFwd)){
            $sql = 'UPDATE chartdetails SET bfwd =' . $myrow['cfwd'] . ',                            bfwdbudget =' .
$myrow['cfwdbudget']. '                    WHERE accountcode = ' . $myrow['accountcode'] . '                    AND
period>=' . $PeriodNo;            $UpdChartDetails = DB_query($sql,$db, '', '', '', false);
DB_free_result($UpdChartDetails);       }        DB_free_result($ChartDetailsCFwd);    }
 
}


function DB_query ($SQL,    &$Conn,    $ErrorMessage='',    $DebugMessage= '',    $Transaction=false,
$TrapErrors=true){
global $debug;
$result = pg_query($Conn, $SQL);if ($DebugMessage == '') {    $DebugMessage = _('The SQL that failed was:');}//if
(DB_error_no($Conn)!= 0){if ( !$result AND $TrapErrors){    prnMsg($ErrorMessage.'<BR>' . DB_error_msg($Conn),'error',
_('DBERROR:'));    if ($debug==1){        echo '<BR>' . $DebugMessage. "<BR>$SQL<BR>";    }    if ($Transaction){
$SQL = 'rollback';        $Result = DB_query($SQL,$Conn);        if (DB_error_no($Conn) !=0){            prnMsg('<br
/>'. _('Error Rolling Back Transaction!!'), '', _('DB
 
DEBUG:') );        }    }    if ($TrapErrors){        include('includes/footer.inc');        exit;    }}return
$result;

}


I am hoping that someone will be able to see an alternative simpler method or
suggest a method of indexing the pg tables to optmise the required queries. I
would appreciate any help here men.

Many thanks in advance ....

--
Phil Daintree
webERP Project Admin

-------------------------------------------------------

-- 
Phil Daintree
webERP Project Admin


Re: Query performance problem

From
PFC
Date:
> Can anyone tell me why does the following code chokes ... literally -  
> this
> works almost invisbly under mysql - pg takes more than an hour even on a  
> very
> small 30 record database.
- You should really use 8.0- How much time toes it takes without the INSERT/UPDATES ?- Please post EXPLAIN ANALYZE of
allthe queries- You could do all that with only two queries
 


> (The table chartmaster is just a list of general ledger accounts  
> accountcode
> and accountdescription. PK = accountcode)
>
>
>     $ChartAccounts = DB_query('SELECT accountcode FROM chartmaster',$db);
>
>     While ($AccountRow = DB_fetch_array($ChartAccounts)){
>
>         for ($PeriodNo=$CreateFrom;$PeriodNo <= $CreateTo;$PeriodNo++) {
>
>             echo '<LI>' . _('Period Number') . ' ' . $PeriodNo . '</LI>';
>
>             // Check if there is an chart details record set up
>             $sql = 'SELECT count(*) FROM chartdetails
>                     WHERE accountcode='.$AccountRow['accountcode'].'
>                     AND period=' . $PeriodNo;
>             $InsChartDetails = DB_query($sql,$db,'','','',false);
>             $CountRows = DB_fetch_row($InsChartDetails);
>             $AccountExistsAlready = $CountRows[0];
>             DB_free_result($InsChartDetails);
>             if(! $AccountExistsAlready) {
>                 $sql = 'INSERT INTO chartdetails (accountcode,
>                             period)
>                     VALUES (' . $AccountRow['accountcode'] . ',
>                         ' . $PeriodNo . ')';
>                 $InsChartDetails = DB_query($sql,$db);
>                 DB_free_result($InsChartDetails);
>             }
>
>         }
>
>     /*Now run through each of the new chartdetail records created for each
> account and update them with the B/Fwd and B/Fwd budget no updates would  
> be
> required where there were previously no chart details set up ie
> FirstPeriodPostedTo > 0 */
>
>         for ($PeriodNo=$CreateFrom;$PeriodNo<=$CreateTo; $PeriodNo++) {
>
>             $sql = 'SELECT accountcode,
>                     period,
>                     actual + bfwd AS cfwd,
>                     budget + bfwdbudget AS cfwdbudget
>                 FROM chartdetails WHERE period =' . ($PeriodNo  - 1);
>             $ChartDetailsCFwd = DB_query($sql,$db);
>
>             while ($myrow = DB_fetch_array($ChartDetailsCFwd)){
>
>                 $sql = 'UPDATE chartdetails SET bfwd =' . $myrow['cfwd'] . ',
>                                 bfwdbudget =' . $myrow['cfwdbudget'] . '
>                         WHERE accountcode = ' . $myrow['accountcode'] . '
>                         AND period >=' . $PeriodNo;
>                 $UpdChartDetails = DB_query($sql,$db, '', '', '', false);
>                 DB_free_result($UpdChartDetails);
>             }
>             DB_free_result($ChartDetailsCFwd);
>         }
>
>     }
>
>
> function DB_query ($SQL,
>         &$Conn,
>         $ErrorMessage='',
>         $DebugMessage= '',
>         $Transaction=false,
>         $TrapErrors=true){
>
>     global $debug;
>
>     $result = pg_query($Conn, $SQL);
>     if ($DebugMessage == '') {
>         $DebugMessage = _('The SQL that failed was:');
>     }
>     //if (DB_error_no($Conn) != 0){
>     if ( !$result AND $TrapErrors){
>         prnMsg($ErrorMessage.'<BR>' . DB_error_msg($Conn),'error', _('DB  
> ERROR:'));
>         if ($debug==1){
>             echo '<BR>' . $DebugMessage. "<BR>$SQL<BR>";
>         }
>         if ($Transaction){
>             $SQL = 'rollback';
>             $Result = DB_query($SQL,$Conn);
>             if (DB_error_no($Conn) !=0){
>                 prnMsg('<br />'.  _('Error Rolling Back Transaction!!'), '', _('DB
> DEBUG:') );
>             }
>         }
>         if ($TrapErrors){
>             include('includes/footer.inc');
>             exit;
>         }
>     }
>     return $result;
>
> }
>
>
> I am hoping that someone will be able to see an alternative simpler  
> method or
> suggest a method of indexing the pg tables to optmise the required  
> queries. I
> would appreciate any help here men.
>
> Many thanks in advance ....
>
> --
> Phil Daintree
> webERP Project Admin
>
> -------------------------------------------------------
>




Re: Query performance problem

From
Kenneth Gonsalves
Date:
On Thursday 17 Mar 2005 1:50 pm, Phil Daintree wrote:

> CREATE TABLE chartdetails (
>     accountcode integer DEFAULT 0 NOT NULL,
>     period integer DEFAULT 0 NOT NULL,
>     budget double precision DEFAULT (0)::double precision NOT NULL,
>     actual double precision DEFAULT (0)::double precision NOT NULL,
>     bfwd double precision DEFAULT (0)::double precision NOT NULL,
>     bfwdbudget double precision DEFAULT (0)::double precision NOT
> NULL );

although may be not relevant to your question, as i have noticed this
before with mysql 'sql', what is the point of having a NOT NULL field
that defaults to 0? the whole idea of a NOT NULL field is to have the
value filled in compulsorily and having a default of 0 or '' defeats
the purpose
--
--
regards
kg

http://www.livejournal.com/users/lawgon
tally ho! http://avsap.sourceforge.net
ಇಂಡ್ಲಿನಕ್ಸ வாழ்க!


Re: Query performance problem

From
PFC
Date:
> although may be not relevant to your question, as i have noticed this
> before with mysql 'sql', what is the point of having a NOT NULL field
> that defaults to 0? the whole idea of a NOT NULL field is to have the
> value filled in compulsorily and having a default of 0 or '' defeats
> the purpose
Well if you define your field as NOT NULL mysql will automatically set it  
to 0 if you store a NULL anyway, so you might as well specify it in your  
table definitions so it looks like you wanted it...


Re: Query performance problem

From
Richard Huxton
Date:
Kenneth Gonsalves wrote:
> On Thursday 17 Mar 2005 1:50 pm, Phil Daintree wrote:
> 
> 
>>CREATE TABLE chartdetails (
>>    accountcode integer DEFAULT 0 NOT NULL,
>>    period integer DEFAULT 0 NOT NULL,
>>    budget double precision DEFAULT (0)::double precision NOT NULL,
>>    actual double precision DEFAULT (0)::double precision NOT NULL,
>>    bfwd double precision DEFAULT (0)::double precision NOT NULL,
>>    bfwdbudget double precision DEFAULT (0)::double precision NOT
>>NULL );
> 
> 
> although may be not relevant to your question, as i have noticed this 
> before with mysql 'sql', what is the point of having a NOT NULL field 
> that defaults to 0? the whole idea of a NOT NULL field is to have the 
> value filled in compulsorily and having a default of 0 or '' defeats 
> the purpose

Not necessarily. NOT NULL here helps to ensure you can add values 
together without the risk of a null result. There are plenty of "amount" 
columns that should be not-null (total spent, total ordered etc).

--   Richard Huxton  Archonet Ltd


Re: Query performance problem

From
Kenneth Gonsalves
Date:
On Thursday 17 Mar 2005 7:35 pm, Richard Huxton wrote:

> Not necessarily. NOT NULL here helps to ensure you can add values
> together without the risk of a null result. There are plenty of
> "amount" columns that should be not-null (total spent, total
> ordered etc).

that makes sense - but is it necessary to have a not null constraint
when there is a default value?

--
regards
kg

http://www.livejournal.com/users/lawgon
tally ho! http://avsap.sourceforge.net
ಇಂಡ್ಲಿನಕ್ಸ வாழ்க!


Re: Query performance problem

From
Ragnar Hafstað
Date:
On Fri, 2005-03-18 at 10:49 +0530, Kenneth Gonsalves wrote:
> On Thursday 17 Mar 2005 7:35 pm, Richard Huxton wrote:
> 
> > Not necessarily. NOT NULL here helps to ensure you can add values
> > together without the risk of a null result. There are plenty of
> > "amount" columns that should be not-null (total spent, total
> > ordered etc).
> 
> that makes sense - but is it necessary to have a not null constraint 
> when there is a default value?

DEFAULT applies to INSERTs, NOT NULL applies to UPDATEs too.

gnari




Re: Query performance problem

From
PFC
Date:
> DEFAULT applies to INSERTs, NOT NULL applies to UPDATEs too.
In MySQL it applies to both (ie. if you UPDATE to an invalid value, it  
sets it to 'something').
NOT NULL without default is useful when you want to be sure you'll never  
forget to put a value in that column, when there is no meaningful default.  
Also for foreign keys : what would be the default value of a foreign key ?

>
> gnari
>
>
>
> ---------------------------(end of broadcast)---------------------------
> TIP 3: if posting/reading through Usenet, please send an appropriate
>       subscribe-nomail command to majordomo@postgresql.org so that your
>       message can get through to the mailing list cleanly
>




Re: Query performance problem

From
Stephan Szabo
Date:
On Fri, 18 Mar 2005, Kenneth Gonsalves wrote:

> On Thursday 17 Mar 2005 7:35 pm, Richard Huxton wrote:
>
> > Not necessarily. NOT NULL here helps to ensure you can add values
> > together without the risk of a null result. There are plenty of
> > "amount" columns that should be not-null (total spent, total
> > ordered etc).
>
> that makes sense - but is it necessary to have a not null constraint
> when there is a default value?

It's also an added check which prevents you from explicitly setting the
value to NULL in an insert or update, since
"insert into foo(col1) values (NULL);" shouldn't insert the default value
into col1.  This is relatively minor generally, but if you have queries
whose behavior is broken by NULLs (things using IN/NOT IN for example)
it's better to be safe.



Re: Query performance problem

From
George Weaver
Date:
Only specifying a default value does not prevent a NULL from being entered 
either through accident or ignorance:

jan28-05=# create table test (foo text, foo1 int4 default(0));
CREATE TABLE
jan28-05=# insert into test values('a',1);
INSERT 98685 1
jan28-05=# insert into test values('b',4);
INSERT 98686 1
jan28-05=# insert into test values('c',NULL);
INSERT 98687 1
jan28-05=# insert into test values('d');
INSERT 98688 1
jan28-05=# select * from test;foo | foo1
-----+------a   |    1b   |    4c   |d   |    0
(4 rows)

George

----- Original Message ----- 
From: "Kenneth Gonsalves" <lawgon@thenilgiris.com>
To: "Richard Huxton" <dev@archonet.com>
Cc: <weberp@paradise.net.nz>; <pgsql-sql@postgresql.org>
Sent: Thursday, March 17, 2005 11:19 PM
Subject: Re: [SQL] Query performance problem


On Thursday 17 Mar 2005 7:35 pm, Richard Huxton wrote:

> Not necessarily. NOT NULL here helps to ensure you can add values
> together without the risk of a null result. There are plenty of
> "amount" columns that should be not-null (total spent, total
> ordered etc).

that makes sense - but is it necessary to have a not null constraint
when there is a default value?

-- 
regards
kg

http://www.livejournal.com/users/lawgon
tally ho! http://avsap.sourceforge.net
ಇಂಡ್ಲಿನಕ್ಸ வாழ்க!

---------------------------(end of broadcast)---------------------------
TIP 2: you can get off all lists at once with the unregister command   (send "unregister YourEmailAddressHere" to
majordomo@postgresql.org)