Received: from localhost (unknown [200.46.208.211]) by mail.postgresql.org (Postfix) with ESMTP id 22CBB632599 for ; Fri, 17 Jul 2009 12:33:08 -0300 (ADT) Received: from mail.postgresql.org ([200.46.204.86]) by localhost (mx1.hub.org [200.46.208.211]) (amavisd-maia, port 10024) with ESMTP id 72281-06 for ; Fri, 17 Jul 2009 12:32:57 -0300 (ADT) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mail-ew0-f223.google.com (mail-ew0-f223.google.com [209.85.219.223]) by mail.postgresql.org (Postfix) with ESMTP id F1D9C633953 for ; Fri, 17 Jul 2009 12:32:56 -0300 (ADT) Received: by ewy23 with SMTP id 23so967585ewy.19 for ; Fri, 17 Jul 2009 08:32:55 -0700 (PDT) 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=YK8E66oVVi6wOYR2w4gmyPI8JmOjx73lqExPmar3hkw=; b=nRDwqIVKuIHvG7wAc9eqn8/dreOtt/sk+CusuhH7oY8ak1fRRUmJpDmvECtABQ0XFa fz24d/Cg2GOJY8lrVRYPtvPs8mYfJ06GZYwjK1Daidky0mDlYZvzjF1NFPtxVvrdh91B R97CHQpa4orgFTdYz458SRGURxq6uwAgzcEDU= 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=PKAQCGRXUiscL3sJZH7000Jb6HOHvYsvEKnqth4NCV3TTFdd0SgSFJypJBVrW5kQOp W4VuoUhL1PRL4Jt6tnsLtn1RO0ylHjzJcinlMYOntM4li/jKFDatOlOug4FHmp8UwGwD g4jAFD1a05K/f1cnZ5n3AuVx9Cf33hZtFyAP4= MIME-Version: 1.0 Received: by 10.210.81.9 with SMTP id e9mr1521900ebb.42.1247844775524; Fri, 17 Jul 2009 08:32:55 -0700 (PDT) In-Reply-To: <87E8E647-4CAD-4491-B687-FA763E0EBE7E@thebuild.com> References: <87E8E647-4CAD-4491-B687-FA763E0EBE7E@thebuild.com> Date: Fri, 17 Jul 2009 09:32:55 -0600 Message-ID: Subject: Re: De-duplicating rows From: Scott Marlowe To: Christophe Cc: pgsql-sql@postgresql.org 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=0.031 tagged_above=0 required=5 tests=AWL=0.031 X-Spam-Level: X-Archive-Number: 200907/78 X-Sequence-Number: 32819 On Thu, Jul 16, 2009 at 9:07 PM, Christophe wrote: > The Subject: is somewhat imprecise, but here's what I'm trying to do. =A0= For > some reason, my brain is locking up over it. > > I'm moving a 7.2 (yes) database to 8.4. =A0In the table in question, the > structure is along the lines of: > > =A0 =A0 =A0 =A0serial_number =A0 SERIAL, PRIMARY KEY > =A0 =A0 =A0 =A0email =A0 =A0 =A0 =A0 =A0 TEXT > =A0 =A0 =A0 =A0create_date =A0 =A0 TIMESTAMP > =A0 =A0 =A0 =A0attr1 =A0 =A0 =A0 =A0 =A0 type > =A0 =A0 =A0 =A0attr2 =A0 =A0 =A0 =A0 =A0 type > =A0 =A0 =A0 =A0attr3 =A0 =A0 =A0 =A0 =A0 type > =A0 =A0 =A0 =A0... > > (The point of the "attr" fields is that there are many more columns for e= ach > row.) > > The new structure removes the "serial_number" field, and uses "email" as = the > primary key, but is otherwise unchanged: > > =A0 =A0 =A0 =A0email =A0 =A0 =A0 =A0 =A0 TEXT, PRIMARY KEY > =A0 =A0 =A0 =A0create_date =A0 =A0 TIMESTAMP > =A0 =A0 =A0 =A0attr1 =A0 =A0 =A0 =A0 =A0 type > =A0 =A0 =A0 =A0attr2 =A0 =A0 =A0 =A0 =A0 type > =A0 =A0 =A0 =A0attr3 =A0 =A0 =A0 =A0 =A0 type > =A0 =A0 =A0 =A0... > > Now, since this database has been production since 7.2 days, cruft has cr= ept > in: in particular, there are duplicate email addresses, some with mismatc= hed > attributes. =A0The policy decision by the client is that the correct row = is > the one with the earliest timestamp. =A0(The timestamps are widely > distributed; it's not the case that there is a single timestamp above whi= ch > all the duplicates live.) =A0Thus, ideally, I want to select exactly one = row > per "email", picking the row with the earliest timestamp in the case that > there is more than one row with that email. > > Any suggestions on how to write such a SELECT? =A0Of course, I could do t= his > with an application against the db, but a single SELECT would be great if > possible. OK, assuming we can keep the serial number during the conversion, we could use something like this: select distinct a.serial_number from table a join table b on (a.email=3Db.email and a.serial_number>b.serial_number) Now assuming that the serial numbers and the timestamps are in order together, that'll give us all the serial numbers for all the matching email addresses EXCEPT the first one. If the serial numbers are not in order with the timestamps, then create a sequence, and update them in order, then the query will work. Once you've confirmed by hand that the first hundred or so serial_numbers you're getting back ARE in fact all n+1 for the same email address, use the select in a subselect to delete: delete from table x where serial_number in (select distinct....)