X-Original-To: pgsql-sql-postgresql.org@localhost.postgresql.org Received: from localhost (unknown [200.46.204.144]) by svr1.postgresql.org (Postfix) with ESMTP id 75DE85E470E for ; Sat, 28 Aug 2004 05:36:07 -0300 (ADT) Received: from svr1.postgresql.org ([200.46.204.71]) by localhost (av.hub.org [200.46.204.144]) (amavisd-new, port 10024) with ESMTP id 22702-04 for ; Sat, 28 Aug 2004 08:36:06 +0000 (GMT) Received: from stark.xeocode.com (gsstark.mtl.istop.com [66.11.160.162]) by svr1.postgresql.org (Postfix) with ESMTP id DC80F5E46F4 for ; Sat, 28 Aug 2004 05:36:03 -0300 (ADT) Received: from localhost ([127.0.0.1] helo=stark.xeocode.com) by stark.xeocode.com with smtp (Exim 3.36 #1 (Debian)) id 1C0ygp-0003s4-00; Sat, 28 Aug 2004 04:35:59 -0400 To: Josh Berkus Cc: Scott Gerhardt , pgsql-sql@postgresql.org Subject: Re: Aggregate query for multiple records References: <641FEEDD-F728-11D8-A763-000393801C60@g-it.ca> <200408270948.00680.josh@agliodbs.com> <200408271159.26445.josh@agliodbs.com> In-Reply-To: <200408271159.26445.josh@agliodbs.com> From: Greg Stark Organization: The Emacs Conspiracy; member since 1992 Date: 28 Aug 2004 04:35:58 -0400 Message-ID: <87eklrlkwh.fsf@stark.xeocode.com> Lines: 75 User-Agent: Gnus/5.09 (Gnus v5.9.0) Emacs/21.3 MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Virus-Scanned: by amavisd-new at hub.org X-Spam-Status: No, hits=0.0 tagged_above=0.0 required=5.0 tests= X-Spam-Level: X-Archive-Number: 200408/313 X-Sequence-Number: 18810 Scott Gerhardt writes: > Hello, I am new to the list, my apology if this question is beyond the scope or > charter of this list. > > My questions is: > What is the best method to perform an aggregate query to calculate sum() values > for each distinct wid as in the example below, but except for all wid's (not > just WHERE wid='01/1-6-1-30w1/0'). > > Also, performance wise, would it be better to build a function for this query. > The table has 9 million records and these aggregate queries take hours. The "top n" type query (or in this case "first n" or "last n" but it's the same thing) is actually very tricky to do in standard SQL. The best solution seen here for postgres is to use arrays and custom aggregate functions. The following is based on a previous answer from Tom Lane to a similar question. (I thought I already posted this for you on pgsql-general but the list archives are down and you don't seem to have seen it, so I'm resending it) It allows you to do the whole query with a single sort for the grouping and the ordering by date together. You would have to use it with something like: SELECT sum_first_6(oil) FROM (SELECT oil from prd_data ORDER BY wid, "date") GROUP BY wid If you pump up sort_mem enough -- you can do it within the session for the single connection using "SET sort_mem" it should be pretty fast. I think it's the best you're going to get. If you're absolutely sure the data is physically stored in chronological order -- which I would only feel comfortable with if you've never done any updates or deletes, only inserts and perhaps occasional truncates, then you might be able to get by without ordering and convince it to do a hash aggregate. That would be the optimal result, no sorts at all. But it would be hard to make sure it would always work. test=> create or replace function first_6_accum (integer[], integer) returns integer[] language sql immutable as 'select case when array_upper($1,1)>=6 then $1 else $1||$2 end'; CREATE FUNCTION test=> create function sum_6(integer[]) returns integer immutable language sql as 'select $1[1]+$1[2]+$1[3]+$1[4]+$1[5]+$1[6]'; CREATE FUNCTION test=> create aggregate sum_first_6 (basetype=integer, sfunc=first_6_accum, stype=integer[],initcond='{}',finalfunc=sum_6); CREATE AGGREGATE test=> select sum_first_6(i) from (select i from (select 1 as i union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8) as x order by i desc) as x; sum_first_6 ------------- 33 (1 row) test=> select sum_first_6(i) from (select i from (select 1 as i union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8) as x order by i asc) as x; sum_first_6 ------------- 21 (1 row) This can easily be switched around to make it "last_6" and you can write functions to handle 6 records or 9 records. And all of these could be combined in a single query, so you only have to do the sort once. Unfortunately you cannot make aggregate functions that take multiple parameters, nor can you pass extra parameters to the state function. So you'll have to create a separate set of functions for each variant. Also, you'll have to change it to use reals. -- greg