Received: from maia.hub.org (maia-5.hub.org [200.46.204.29]) by mail.postgresql.org (Postfix) with ESMTP id CF610B5DC61 for ; Wed, 31 Aug 2011 05:31:03 -0300 (ADT) Received: from mail.postgresql.org ([200.46.204.86]) by maia.hub.org (mx1.hub.org [200.46.204.29]) (amavisd-maia, port 10024) with ESMTP id 87131-07 for ; Wed, 31 Aug 2011 08:30:56 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.7.6 Received: from mail-vx0-f174.google.com (mail-vx0-f174.google.com [209.85.220.174]) by mail.postgresql.org (Postfix) with ESMTP id 3AA41B5DC5F for ; Wed, 31 Aug 2011 05:30:56 -0300 (ADT) Received: by mail-vx0-f174.google.com with SMTP id 9so252791vxi.19 for ; Wed, 31 Aug 2011 01:30:56 -0700 (PDT) Received: by 10.52.156.100 with SMTP id wd4mr121896vdb.76.1314779456196; Wed, 31 Aug 2011 01:30:56 -0700 (PDT) Received: from [10.1.1.8] (124-169-102-162.dyn.iinet.net.au [124.169.102.162]) by mx.google.com with ESMTPS id u3sm3004219vcc.43.2011.08.31.01.30.53 (version=SSLv3 cipher=OTHER); Wed, 31 Aug 2011 01:30:55 -0700 (PDT) Message-ID: <4E5D8F68.4010102@ringerc.id.au> Date: Wed, 31 Aug 2011 09:33:28 +0800 From: Craig Ringer User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20110812 Thunderbird/6.0 MIME-Version: 1.0 To: Andy Colson CC: "pgsql-performance@postgresql.org" Subject: Re: IN or EXISTS References: <4E5D485F.8060300@squeakycode.net> In-Reply-To: <4E5D485F.8060300@squeakycode.net> Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit X-Virus-Scanned: Maia Mailguard 1.0.1 X-Spam-Status: No, hits=-0.357 tagged_above=-10 required=5 tests=BAYES_00=-1.9, DATE_IN_PAST_06_12=1.543 X-Spam-Level: X-Archive-Number: 201108/309 X-Sequence-Number: 44756 On 31/08/2011 4:30 AM, Andy Colson wrote: > Hi all, > > I have read things someplace saying not exists was better than not > in... or something like that. Not sure if that was for in/exists and > not in/not exists, and for a lot of records or not. > `EXISTS' may perform faster than `IN', yes. Using `IN' it is necessary to build a list of values then iterate over them to check for a match. By contrast, `EXISTS' may use a simple index lookup or the like to test for the presence of a value. On the other hand, the `IN' subquery is uncorrelated needs only run once, where the `EXISTS' subquery is correlated and has to run once for every outer record. That means that the `IN' list approach can be a lot faster where the subquery in question is relatively time consuming for the number of values it returns. For example, if the `IN' query returns only 5 values and takes 100ms, you're scanning 1 million records in the outer query, and the subquery `EXISTS' version would take 50ms, using `IN' is a no-brainer since 1 million times 50ms will be a lot slower than 1 times 100ms plus the time required to scan 5 elements 1 million times. Another complication is the possible presence of NULL in an IN list. Getting NULLs in `IN' lists is a common source of questions on this list, because people are quite surprised by how it works. EXISTS avoids the NULL handling issue (and in the process demonstrates how woefully inconsistent SQL's handling of NULL really is). Theoretically the query planner could transform: SELECT * from y WHERE y.id IN (SELECT DISTINCT z.y_id FROM z WHERE z.y_id IS NOT NULL); into: SELECT * FROM y WHERE EXISTS (SELECT 1 FROM z WHERE z.y_id = y.id) ... or vice versa depending on which it thought would be faster. AFAIK it doesn't currently do this. To be able to do it the planner would need to know how to estimate the cost of scanning an `IN' result list. It'd also need to be able to use constraints on the target table to prove that the result of the `IN' may not contain nulls. To transform the EXISTS version into the IN version where it'd be more efficient, it'd also have to be able to use constraints on the target table to prove that results of a SELECT would be unique without explicit deduplication. All this makes me wonder ... does Pg currently support sorting IN lists and using a binary search? It'd be pretty nice to be able to prove that: SELECT * from y WHERE y.id IN (SELECT z.y_id FROM z); is equvalent to: SELECT * FROM y WHERE y.id IN (SELECT DISTINCT z.y_id FROM z WHERE z_id IS NOT NULL) ... and either transform it to an EXISTS test or add an ORDER BY z_id and flag the resultset as sorted so a binary search could be done on it whenever a row hits the IN test. -- Craig Ringer