Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pAnP0-0000Q6-R7 for pgsql-performance@arkaria.postgresql.org; Thu, 29 Dec 2022 07:32:35 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1pAnOy-0006oJ-7j for pgsql-performance@arkaria.postgresql.org; Thu, 29 Dec 2022 07:32:32 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pAnOx-0006oA-2j for pgsql-performance@lists.postgresql.org; Thu, 29 Dec 2022 07:32:31 +0000 Received: from gproxy5-pub.mail.unifiedlayer.com ([67.222.38.55] helo=progateway7-pub.mail.pro1.eigbox.com) by makus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pAnOr-00087M-4i for pgsql-performance@lists.postgresql.org; Thu, 29 Dec 2022 07:32:29 +0000 Received: from cmgw14.mail.unifiedlayer.com (unknown [10.0.90.129]) by progateway7.mail.pro1.eigbox.com (Postfix) with ESMTP id A0A3210045C2C for ; Thu, 29 Dec 2022 07:32:05 +0000 (UTC) Received: from host2097.hostmonster.com ([67.20.114.243]) by cmsmtp with ESMTP id AnOXpzBbDZJgEAnOXpnn1p; Thu, 29 Dec 2022 07:32:05 +0000 X-Authority-Reason: nr=8 X-Authority-Analysis: v=2.4 cv=d7QwdTvE c=1 sm=1 tr=0 ts=63ad4275 a=H2TdJq7IqD1OdGR5eBO4Vg==:117 a=H2TdJq7IqD1OdGR5eBO4Vg==:17 a=dLZJa+xiwSxG16/P+YVxDGlgEgI=:19 a=mLVZY08b0QfoWQHY:21 a=sHyYjHe8cH0A:10:nop_rcvd_month_year a=xK3SKg5X3mEA:10:endurance_base64_authed_username_1 a=r77TgQKjGQsHNAKrUKIA:9 a=epTmVMiNAAAA:8 a=JJMZSaG8UvmUN4gf0t8A:9 a=QEXdDO2ut3YA:10:nop_charset_2 a=bu5opKppuF8A:10:demote_hacked_domain_1 a=c5g3-b1IsEIA:10:demote_hacked_domain_7 a=2eQ1Rd5vAAAA:8 a=mPMMiL685FP3-mIUuLEA:9 a=UgjrOTHjxyln450s:21 a=_W_S_7VecoQA:10:nop_html a=eokPoFTNKD01KP5zalAy:22 a=ndEWmUVY6Yapc0oHF_P4:22 a=7PAQaQrchw-xh4qYgjVZ:22 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=schadow.us; s=default; h=In-Reply-To:From:References:Cc:To:Subject:MIME-Version:Date: Message-ID:Content-Type:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=v9DFTFG7PD4mBRxH2lonQKaARgM/FSepVrLqAJ8uxd4=; b=fy9znklmGexkM+mdcReMi2mSWv fRhF6+w0goLmO214+6B1TYnsId13w+EDjDMbY7U5BuWXLVcrsGMJtqjqYag9hwwZkbVoKbMCU+e9c Gi/eSEWApKM5Jq/lMSNoz5Yvp; Received: from [1.46.146.119] (port=62632 helo=[192.168.125.228]) by host2097.hostmonster.com with esmtpsa (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1pAnOW-003RWQ-No; Thu, 29 Dec 2022 00:32:05 -0700 Content-Type: multipart/alternative; boundary="------------Ho3fyGhr0cCfnVHBUQFRRvXf" Message-ID: Date: Thu, 29 Dec 2022 02:31:59 -0500 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Thunderbird/102.6.1 Subject: Re: When you really want to force a certain join type? Content-Language: en-US To: Justin Pryzby Cc: pgsql-performance@lists.postgresql.org References: <70b2ee7b-8e8b-d884-8ca2-590a242308b8@gusw.net> <20221228154837.GX1153@telsasoft.com> From: Gunther Schadow In-Reply-To: <20221228154837.GX1153@telsasoft.com> X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - host2097.hostmonster.com X-AntiAbuse: Original Domain - lists.postgresql.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - gusw.net X-BWhitelist: no X-Source-IP: 1.46.146.119 X-Source-L: No X-Exim-ID: 1pAnOW-003RWQ-No X-Source: X-Source-Args: X-Source-Dir: X-Source-Sender: ([192.168.125.228]) [1.46.146.119]:62632 X-Source-Auth: ebiz+schadow.us X-Email-Count: 1 X-Source-Cap: cHJhZ21hdDE7cHJhZ21hdDE7aG9zdDIwOTcuaG9zdG1vbnN0ZXIuY29t X-Local-Domain: yes List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk This is a multi-part message in MIME format. --------------Ho3fyGhr0cCfnVHBUQFRRvXf Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit On 12/28/2022 10:48 AM, Justin Pryzby wrote: > Maybe the new parameter in v15 would help. > > https://www.postgresql.org/docs/15/runtime-config-query.html#GUC-RECURSIVE-WORKTABLE-FACTOR > recursive_worktable_factor (floating point) > > Sets the planner's estimate of the average size of the working table > of a recursive query, as a multiple of the estimated size of the > initial non-recursive term of the query. This helps the planner > choose the most appropriate method for joining the working table to > the query's other tables. The default value is 10.0. A smaller value > such as 1.0 can be helpful when the recursion has low “fan-out” from > one step to the next, as for example in shortest-path queries. Graph > analytics queries may benefit from larger-than-default values. Thanks that's something I will try after I upgraded. But speaking of such other uses for recursive queries, I can say I have quite a bit of experience of turning graph related "traversal" and search and optimization and classification queries into SQL, in short, computing the transitive closure. And usually I have stayed away from the recursive WITH query and instead set up a start table and then perform the iterative step. And there are two ways to go about it. Say you have a graph, simple nodes and arcs. You want to find all paths through the graph. Now you can set up start nodes and then extend them at the end by joining the recursive table to the simple arc table and extend your path every time. This is what the WITH RECURSIVE supports. These queries linearly iterate as many times as the length of the longest path. with recursive arcs as (   select source, target, 1 as distance, ...     from ... ), paths as (   select * from arcs   union all   select a.source, b.target, a.distance + 1 as distance, ...    from paths a inner join_*arcs *_b on(b.source = a.target) ) select * from paths But another way is to join paths with paths. It would be this, which I think I have seen postgresql unable to deal with: with recursive arcs as (   select source, target, 1 as distance, ...     from ... ), paths as (   select * from arcs   union all   select a.source, b.target, a.distance + 1 as distance, ...    from paths a inner join_*paths *_b on(b.source = a.target) ) select * from paths So, instead of the recursive union to join back to the fixed table, it joins the recursive table to the recursive table, and the benefit of that is that these queries converge much quicker. Instead of going 10 iterations to find a path of length 10, you go 1 iteration to find all paths of 2 (if distance 1 is the base table of all arcs), then next you find paths of up to 4 then you find paths of up to 8, then 16, 32, ... This converges much faster. I usually do that as follows create table paths as select source, target, 1 as distance, ... from arcs; prepare rep as insert into paths(source, target, distance, ...) select a.source, b.target, a.distance + b.distance as distance, ...   from paths a inner join paths b on(b.source = a.target) except select * from paths; execute rep; execute rep; ... or instead of the except, in order to minimize distances: where not exists (select 1 from paths x                    where x.source = a.source and x.target = a.target and x.distance < a.distance) I have even done a group by in the recursive step which replaces the paths relation at every iteration (e.g. with only minimal distance paths). Since this converges so rapidly I often prefer that approach over a recursive union query. I think in IBM DB2 allowed to join the recursive table with itself. Is this something you want to support at some time? Also, why even use the RECURSIVE keyword, DB2 didn't need it, and the query analyzer should immediately see the recursion, so no need to have that keyword. regards, -Gunther --------------Ho3fyGhr0cCfnVHBUQFRRvXf Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit
On 12/28/2022 10:48 AM, Justin Pryzby wrote:
Maybe the new parameter in v15 would help.

https://www.postgresql.org/docs/15/runtime-config-query.html#GUC-RECURSIVE-WORKTABLE-FACTOR
recursive_worktable_factor (floating point)

    Sets the planner's estimate of the average size of the working table
    of a recursive query, as a multiple of the estimated size of the
    initial non-recursive term of the query. This helps the planner
    choose the most appropriate method for joining the working table to
    the query's other tables. The default value is 10.0. A smaller value
    such as 1.0 can be helpful when the recursion has low “fan-out” from
    one step to the next, as for example in shortest-path queries. Graph
    analytics queries may benefit from larger-than-default values.

Thanks that's something I will try after I upgraded.

But speaking of such other uses for recursive queries, I can say I have quite a bit of experience of turning graph related "traversal" and search and optimization and classification queries into SQL, in short, computing the transitive closure. And usually I have stayed away from the recursive WITH query and instead set up a start table and then perform the iterative step. And there are two ways to go about it. Say you have a graph, simple nodes and arcs. You want to find all paths through the graph.

Now you can set up start nodes and then extend them at the end by joining the recursive table to the simple arc table and extend your path every time. This is what the WITH RECURSIVE supports. These queries linearly iterate as many times as the length of the longest path.

with recursive arcs as (
  select source, target, 1 as distance, ...
    from ...
), paths as (
  select * from arcs
  union all
  select a.source, b.target, a.distance + 1 as distance, ...
    from paths a inner join arcs b
      on(b.source = a.target)
)
select * from paths

But another way is to join paths with paths. It would be this, which I think I have seen postgresql unable to deal with:

with recursive arcs as (
  select source, target, 1 as distance, ...
    from ...
), paths as (
  select * from arcs
  union all
  select a.source, b.target, a.distance + 1 as distance, ...
    from paths a inner join paths b
      on(b.source = a.target)
)
select * from paths

So, instead of the recursive union to join back to the fixed table, it joins the recursive table to the recursive table, and the benefit of that is that these queries converge much quicker. Instead of going 10 iterations to find a path of length 10, you go 1 iteration to find all paths of 2 (if distance 1 is the base table of all arcs), then next you find paths of up to 4 then you find paths of up to 8, then 16, 32, ... This converges much faster. I usually do that as follows

create table paths as
select source, target, 1 as distance, ...
  from arcs;

prepare rep as
insert into paths(source, target, distance, ...)
select a.source, b.target, a.distance + b.distance as distance, ... 
  from paths a inner join paths b on(b.source = a.target) 
except
select * from paths;

execute rep;
execute rep;
...

or instead of the except, in order to minimize distances:

where not exists (select 1 from paths x 
                   where x.source = a.source 
                     and x.target = a.target
                     and x.distance < a.distance)

I have even done a group by in the recursive step which replaces the paths relation at every iteration (e.g. with only minimal distance paths).

Since this converges so rapidly I often prefer that approach over a recursive union query.

I think in IBM DB2 allowed to join the recursive table with itself. Is this something you want to support at some time?

Also, why even use the RECURSIVE keyword, DB2 didn't need it, and the query analyzer should immediately see the recursion, so no need to have that keyword.

regards,
-Gunther

--------------Ho3fyGhr0cCfnVHBUQFRRvXf--