Received: from maia.hub.org (unknown [200.46.204.183]) by mail.postgresql.org (Postfix) with ESMTP id D6D3A632CC4 for ; Tue, 9 Mar 2010 08:46:41 -0400 (AST) Received: from mail.postgresql.org ([200.46.204.86]) by maia.hub.org (mx1.hub.org [200.46.204.183]) (amavisd-maia, port 10024) with ESMTP id 67885-03 for ; Tue, 9 Mar 2010 12:46:30 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mail-gw0-f46.google.com (mail-gw0-f46.google.com [74.125.83.46]) by mail.postgresql.org (Postfix) with ESMTP id 472C5632B43 for ; Tue, 9 Mar 2010 08:46:31 -0400 (AST) Received: by gwb15 with SMTP id 15so3495740gwb.19 for ; Tue, 09 Mar 2010 04:46:29 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:mime-version:received:in-reply-to:references :date:message-id:subject:from:to:cc:content-type :content-transfer-encoding; bh=CKSpV9VuTKp64szzIOON2tBVSb0gdMOoh+Gzh1W95Rs=; b=BLeFZL8/l6QUNPt/adqjzLQtYPM9Nm37SdgXS1YgtU74pcTg5AqUjf9gjjWw60MH7Y lHscG0/foy0R5uU28XnrB03IgmmTAiGbYZrrDH0nrmZVuVLvwhkapWfea1kGeNn5lKRh lCWkfPMI9ZInOJJ7wKcI4ROpjh4tiQLVFV82c= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=mime-version:in-reply-to:references:date:message-id:subject:from:to :cc:content-type:content-transfer-encoding; b=lbrxA6rgQvbkqmIm7Z1/bfpb0VcYXtL1l4I3TRXi1rVpcKQTsPSR3eatt67R7ZfNMW CepnnUSM//EEIpLWc+5R937i1sYYIaqH6ejYM4nBZ2pG4jhcL35s161xek09Y+durKE5 m+rSk9b2Jn2fl27bmAh54DG+teQU/EtShhibs= MIME-Version: 1.0 Received: by 10.90.133.11 with SMTP id g11mr6381810agd.121.1268138787314; Tue, 09 Mar 2010 04:46:27 -0800 (PST) In-Reply-To: References: Date: Tue, 9 Mar 2010 07:46:27 -0500 Message-ID: Subject: Re: Extracting superlatives - SQL design philosophy From: Merlin Moncure To: Dave Crooke Cc: pgsql-performance Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: Maia Mailguard 1.0.1 X-Spam-Status: No, hits=-2.599 tagged_above=-10 required=5 tests=BAYES_00=-2.599 X-Spam-Level: X-Archive-Number: 201003/87 X-Sequence-Number: 37912 On Wed, Feb 24, 2010 at 4:31 PM, Dave Crooke wrote: > This is a generic SQL issue and not PG specific, but I'd like to get > an opinion from this list. > > Consider the following data: > > # \d bar > =A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 Table "public.bar" > =A0Column |=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 Type=A0=A0=A0=A0=A0=A0=A0=A0= =A0=A0=A0=A0 | Modifiers > --------+-----------------------------+----------- > =A0city=A0=A0 | character varying(255)=A0=A0=A0=A0=A0 | > =A0temp=A0=A0 | integer=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0= =A0=A0=A0=A0 | > =A0date=A0=A0 | timestamp without time zone | > > # select * from bar order by city, date; > =A0 city =A0 =A0| temp | =A0 =A0 =A0 =A0date > -----------+------+--------------------- > =A0Austin =A0 =A0| =A0 75 | 2010-02-21 15:00:00 > =A0Austin =A0 =A0| =A0 35 | 2010-02-23 15:00:00 > =A0Edinburgh | =A0 42 | 2010-02-23 15:00:00 > =A0New York =A0| =A0 56 | 2010-02-23 15:00:00 > =A0New York =A0| =A0 78 | 2010-06-23 15:00:00 > (5 rows) > > If you want the highest recorded temperature for a city, that's easy > to do, since the selection criteria works on the same column that we > are extracing: > > # select city, max(temp) from bar group by city order by 1; > =A0 city =A0 =A0| max > -----------+----- > =A0Austin =A0 =A0| =A075 > =A0Edinburgh | =A042 > =A0New York =A0| =A078 > (3 rows) > > > However there is (AFAIK) no simple way in plain SQL to write a query > that performs such an aggregation where the aggregation criteria is on > one column and you want to return another, e.g. adding the the *date > of* that highest temperature to the output above, or doing a query to > get the most recent temperature reading for each city. > > What I'd like to do is something like the below (and I'm inventing > mock syntax here, the following is not valid SQL): > > -- Ugly implicit syntax but no worse than an Oracle outer join ;-) > select city, temp, date from bar where date=3Dmax(date) group by city, > temp order by city; > > or perhaps > > -- More explicit > select aggregate_using(max(date), city, temp, date) from bar group by > city, temp order by city; > > Both of the above, if they existed, would be a single data access > followed by and sort-merge. > > The only way I know how to do it involves doing two accesses to the data,= e.g. > > # select city, temp, date from bar a where date=3D(select max(b.date) > from bar b where a.city=3Db.city) order by 1; > =A0 city =A0 =A0| temp | =A0 =A0 =A0 =A0date > -----------+------+--------------------- > =A0Austin =A0 =A0| =A0 35 | 2010-02-23 15:00:00 > =A0Edinburgh | =A0 42 | 2010-02-23 15:00:00 > =A0New York =A0| =A0 78 | 2010-06-23 15:00:00 > (3 rows) > > > # explain select * from bar a where date=3D(select max(b.date) from bar > b where a.city=3Db.city) order by 1; > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0QUERY PLAN > -------------------------------------------------------------------------= - > =A0Sort =A0(cost=3D1658.86..1658.87 rows=3D1 width=3D528) > =A0 Sort Key: a.city > =A0 -> =A0Seq Scan on bar a =A0(cost=3D0.00..1658.85 rows=3D1 width=3D528= ) > =A0 =A0 =A0 =A0 Filter: (date =3D (subplan)) > =A0 =A0 =A0 =A0 SubPlan > =A0 =A0 =A0 =A0 =A0 -> =A0Aggregate =A0(cost=3D11.76..11.77 rows=3D1 widt= h=3D8) > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 -> =A0Seq Scan on bar b =A0(cost=3D0.00..= 11.75 rows=3D1 > width=3D8) =A0 =A0 -- would be an index lookup in a real scenario > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 Filter: (($0)::text =3D (city= )::text) Another cool way to do this is via a custom aggregate: create function maxdata(data, data) returns data as $$ select case when ($1).date > ($2).date then $1 else $2 end; $$ language sql; create aggregate maxdata(data) ( sfunc=3Dmaxdata, stype=3Ddata ); select (d).* from ( select maxdata(data) as d from data group by city ); It does it in a single pass. Where this approach can pay dividends is when you have a very complicated 'max'-ing criteria to justify the verbosity of creating the aggregate. If you are not doing the whole table, the self join is often faster. I'm surprised custom aggregates aren't used more...they seem very clean and neat to me. merlin