Received: from maia.hub.org (unknown [200.46.204.183]) by mail.postgresql.org (Postfix) with ESMTP id 1D79A632DE2 for ; Tue, 11 Aug 2009 08:45:54 -0300 (ADT) 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 28982-04 for ; Tue, 11 Aug 2009 11:45:50 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.7.6 Received: from titania.wxnz.net (titania.wxnz.net [58.28.4.13]) by mail.postgresql.org (Postfix) with ESMTP id 625AA6321A5 for ; Tue, 11 Aug 2009 08:45:50 -0300 (ADT) Received: from gonzo (ip-118-90-128-88.xdsl.xnet.co.nz [118.90.128.88]) by titania.wxnz.net (Postfix) with ESMTP id 787D71DB82C9 for ; Tue, 11 Aug 2009 23:45:46 +1200 (NZST) Received: from news by gonzo with local (Exim 4.69) (envelope-from ) id 1Mapn4-0003nW-4p for pgsql-sql@postgresql.org; Tue, 11 Aug 2009 23:45:18 +1200 To: pgsql-sql@postgresql.org Path: not-for-mail From: Jasen Betts Newsgroups: gmane.comp.db.postgresql.sql Subject: Re: Determining logically unique entities across many partially complete rows where at least one column matches Date: 11 Aug 2009 11:45:17 GMT Organization: Dis (not Dat) Organisation Lines: 176 Message-ID: References: NNTP-Posting-Host: localhost Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Trace: reversiblemaps.ath.cx 1249991117 14600 127.0.0.1 (11 Aug 2009 11:45:17 GMT) X-Face: ?)Aw4rXwN5u0~$nqKj`xPz>xHCwgi^q+^?Ri*+R(&uv2=E1Q0Zk(>h!~o2ID@6{uf8s; a +M[5[U[QT7xFN%^gR"=tuJw%TXXR'Fp~W; (T"1(739R%m0Yyyv*gkGoPA.$b,D.w:z+<'"=-lVT?6 {T?=R^:W5g|E2#EhjKCa+nt":4b}dU7GYB*HBxn&Td$@f%.kl^:7X8rQWd[NTc"P"u6nkisze/Q; 8 "9Z{peQF,w)7UjV$c|RO/mQW/NMgWfr5*$-Z%u46"/00mx-,\R'fLPe.)^ User-Agent: slrn/pre1.0.0-2 (Linux) X-Virus-Scanned: Maia Mailguard 1.0.1 X-Spam-Status: No, hits=-3.599 tagged_above=-10 required=5 tests=BAYES_00=-2.599, RCVD_IN_DNSWL_LOW=-1 X-Spam-Level: X-Archive-Number: 200908/43 X-Sequence-Number: 32952 On 2009-08-11, Jamie Tufnell wrote: > Hi, > > I am faced with a modeling problem and thought I'd see if anyone has run > into something similar and can offer some advice. > > Basically my problem domain is cataloguing "snippets of information" about > "entities" which are loosely identified. > > Entities can be identified up to 3 different methods (email, phone or > openid.) > > Entities can have zero or many emails, phone numbers and openids. The > only restriction is they must have at least one value in one of those three > columns. > > > Some sample data: > > snippet #1 > email: null > phone: +1234567890 > openid: john@myopenid.net > information: This is snippet #1 > > snippet #2 > email: foo@bar.com > phone: null > openid: johnny.name > information: This is snippet #2 > > At this point snippet #1 and #2 could refer to different entities. > > snippet #3 > email: bar@baz.com > phone: +1234567890 > openid: johnny.name > information: This is snippet #3 > > But now all three snippets definitely refer to the same entity, as far as > we're concerned: > > Entity: 1 > OpenIDs: johnny.name, john@myopenid.net > Phones: +1234567890 > Emails: foo@bar.com, bar@baz.com > > So as far as modeling this goes, I'm stuck between: > > 1. Normalizing as usual with some serious triggers to maintain the > relationships. > 2. Just having a snippets table with these fields inline and make these > inferences at query time. > 3. Something in between. > 4. Using a document store like CouchDB. I think three tables openid,email, phone (phone's a bad one for a unique id IME, especially POTS lines) create table (entid integer, phone text unique) etc,etc... then at insert time you use a rule that runs a function with exception handling when the unique rules detect a match with some existing data and then in the exception code you do updates to replace the higher entid with the lower one > The kinds of queries I need to do right now (which will no doubt change): > > * Return all snippets. > * Return all distinct entities. > * Find all id for a distinct entity given a single piece of id. > * Find all snippets for a distinct entity. > > To do it in one table, I am thinking something like this: > > create table snippets ( > id serial not null primary key, > email text, > phone_number text, > openid text, > information text not null, > check (email is not null or > phone_number is not null or openid is not null) > ); > > with queries like: > > * Find all snippets for one distinct entity, searching by openid: > > select * from snippets > where phone_number = > (select phone_number from snippets where openid = 'john@myopenid.net') > or email = > (select email from snippets where openid = 'john@myopenid.net') > or openid in > (select openid from snippets > where phone_number = > (select phone_number from snippets where openid = 'john@myopenid.net') > or email = > (select email from snippets where openid = 'john@myopenid.net')); but that won't get them all. > Or if I was to model as usual I am thinking something like this: > > create table entities ( > id serial not null primary key > ); > > create table entity_has_email ( > entity_id integer not null > references entities (id) on delete cascade on update cascade, > email text not null unique > ); > > create table entity_has_phone_number ( > entity_id integer not null > references entities (id) on delete cascade on update cascade, > phone_number text not null unique > ); > > create table entity_has_openid ( > entity_id integer not null > references entities (id) on delete cascade on update cascade, > openid text not null unique > ); > > create table snippets ( > id serial not null primary key, > entity_id integer not null > references entities (id) on delete cascade on update cascade, > information text not null > ); > > (followed by a mass of on insert/update/delete triggers) > > select s.* from snippets s > join entity_has_email e on s.entity_id = e.id > join entity_has_phone_number p on s.entity_id = p.id > join entity_has_openid o on s.entity_id = o.id > where o.openid = 'john@myopenid.net'; looks like the wrong query for snippets to me. select s.* from snippets s join entity_has_openid o on s.entity_id = o.id where o.openid = 'john@myopenid.net'; > Another option, sort of half way between the two could be: > > create table snippets ( > id serial not null primary key, > entity_id integer not null > references entities (id) on delete cascade on update cascade, > information text not null > ); > > create table entities ( > id serial not null primary key, > email text, > phone_number text, > openid text, > check (email is not null or > phone_number is not null or openid is not null) > ); that's not going to work with your example data. (subject has two different email addresses) I guess you could use arrays for email, openid, and phone.