Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vVoWf-008SIt-1Z for pgsql-hackers@arkaria.postgresql.org; Wed, 17 Dec 2025 10:12:58 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vVoWe-00C0uj-0o for pgsql-hackers@arkaria.postgresql.org; Wed, 17 Dec 2025 10:12:57 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vVoWd-00C0uZ-2r for pgsql-hackers@lists.postgresql.org; Wed, 17 Dec 2025 10:12:56 +0000 Received: from mail-lf1-x130.google.com ([2a00:1450:4864:20::130]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1vVoWb-001D5P-02 for pgsql-hackers@lists.postgresql.org; Wed, 17 Dec 2025 10:12:55 +0000 Received: by mail-lf1-x130.google.com with SMTP id 2adb3069b0e04-598f59996aaso308777e87.1 for ; Wed, 17 Dec 2025 02:12:52 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=enterprisedb.com; s=google; t=1765966372; x=1766571172; darn=lists.postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=Q9QWwNX6RoaspLwnFlMnhniigqP06XS3wCFfNn/OfKY=; b=S32FZ2wqfCKFHXZKLCOhW69hXq60AdzvmVwW8i5cU6IRyoIBHYOjFQfPH/IAB4q722 eliBXIDelSm0bWKaQdOFbVRj71SVSoe2guWYgTNsXKONL9kI8zU3DkTtDM9zCoR/T2WE LJxXMpuecj+cc+WUjxJmZ+poxMsJYTvKqSYNhrL2T3gKPFQ7pZRpCFQW1wyKVGMQoX2B Nqhg2AWUx5jebiMtxuggI7sD341LJulKoNSdxQqInM32/Yh5AnphHJgvfC9Igl0vfgQ5 RZ80dSW75VZIieJHbN1S7U0HoGPkUCZAOdXDnoFohILZbarB1FXFB59jiyh8ieBbgSYF DpQA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1765966372; x=1766571172; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=Q9QWwNX6RoaspLwnFlMnhniigqP06XS3wCFfNn/OfKY=; b=tC462GQqyEirtCs4qsLOjr523RlSWpMNxX/aMc3LCH4DUwv+CgIlXTjMNSo+AZnn4f F0BP9PzbuXtsqLiVuTL3sbf4w7h9pi/xlqXCD3XbWOb0oAUmGpyqcXBCv+zDMRebPhU0 BK4nRG3XKuHuElHLhNf7SItOe43MXCXBDRFMgm+70mw6T4pWVJ1ykUbx5dRLQtUshkKb PwLjgoTLga2YtRq84Q3JVUEZv7Zo5B4FylCRvY/9ubC4Dk+WXWD7g5JTp1lNji9XhWzn V+IXd/XGhCkNNLxxsSgQcQmNZwyl2dN3KaYadv2pco7mg0PMquRZQrwQ5jA7QG45PfiY 3ImA== X-Forwarded-Encrypted: i=1; AJvYcCX16sN9NzTd1tdUJ6EIjBDUjbvv0nt4Oyk3f19yUu2bii5HusMp3ECkONfgx21EET/jBr4GYh/N4RVncCTg@lists.postgresql.org X-Gm-Message-State: AOJu0Yy8ptBMzjCW5trBUFnwzta1mPydL7HeqK1ZNdVDFpKqfrGsYVIH x8nQZDlPdEqa6RFx1WQUN69lDq5lZAierH3dgLKXy1TMk4B/L8dM9DovU6/n25AfAt+zvRhq5K0 ZPbXViRKhLXvOSzTUfPHd5lLDFCNSGXjeTo7RdJKe X-Gm-Gg: AY/fxX6ztKHjhIud+hfQy2Bxg1AxIzOP1Ip0T+tmyRqCS3XL2xAEIOtiWvMqPWHE6U1 SkAw0NEwsWv6HjY4mOSjI9IHTyc8ZXwRQYKr8i/1j+OLfVu1ypmrbIcUhlpacX8uQ4rY1TXiL/w DQ+ERcMiFiFYlC61GMyf1WsOUKPzY17fGVXsxLsUsl+uW2S4uIVD4yNTdLa2cJSo02gGsvQK3u4 xELFTKPWEp/2BUbhooXWcLBFR5CC5077FsNT7j549+RPRrcQgEEzb/dM0Sk4rxxVxlXaek= X-Google-Smtp-Source: AGHT+IFvbaTy83RHZVkenUneizIarDalV2Y6wd0BQrrPdP8mdzrHUZzJ84oBduOTgxV1x5f3rrDaJxJUNGk2brvy9s8= X-Received: by 2002:a05:6512:398c:b0:594:2bc4:8284 with SMTP id 2adb3069b0e04-598faa928d4mr5940374e87.49.1765966371529; Wed, 17 Dec 2025 02:12:51 -0800 (PST) MIME-Version: 1.0 References: In-Reply-To: From: Jakub Wartak Date: Wed, 17 Dec 2025 11:12:40 +0100 X-Gm-Features: AQt7F2q1d3ORP4wtoHPt7aZvX6MrQUheshp1f9qDyWsZfprFtrKtOXB_bRttfrg Message-ID: Subject: Re: pg_plan_advice To: Robert Haas Cc: Jacob Champion , Dian Fay , Matheus Alcantara , PostgreSQL Hackers Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Mon, Dec 15, 2025 at 9:06=E2=80=AFPM Robert Haas = wrote: > > Here's v7. [..] OK, so I've tested today from Your's branch directly, so I hope that was also v7. Given the following q20 query: SELECT s_name, s_address FROM supplier, nation WHERE s_suppkey in (SELECT ps_suppkey FROM partsupp WHERE ps_partkey in (SELECT p_partkey FROM part WHERE p_name LIKE 'forest%' ) AND ps_availqty > (SELECT 0.5 * sum(l_quantity) FROM lineitem WHERE l_partkey =3D ps_partkey AND l_suppkey =3D ps_suppkey AND l_shipdate >=3D DATE '1994-01-01' AND l_shipdate < DATE '1994-01-01' + INTERVAL '1' year ) ) AND s_nationkey =3D n_nationkey AND n_name =3D 'CANADA' ORDER BY s_name; in normal conditions (w/o advice) the above query generates: Sort (cost=3D1010985030.44..1010985030.59 rows=3D61 width=3D51) Sort Key: supplier.s_name -> Nested Loop (cost=3D0.42..1010985028.63 rows=3D61 width=3D51) Join Filter: (nation.n_nationkey =3D supplier.s_nationkey) -> Seq Scan on nation (cost=3D0.00..1.31 rows=3D1 width=3D4) Filter: (n_name =3D 'CANADA'::bpchar) -> Nested Loop Semi Join (cost=3D0.42..1010985008.29 rows=3D1522 width=3D55) Join Filter: (partsupp.ps_suppkey =3D supplier.s_suppkey) -> Seq Scan on supplier (cost=3D0.00..249.30 rows=3D7730 w= idth=3D59) -> Materialize (cost=3D0.42..1010755994.57 rows=3D1973 wid= th=3D4) -> Nested Loop (cost=3D0.42..1010755984.71 rows=3D1973 width=3D4) -> Seq Scan on part (cost=3D0.00..4842.25 rows=3D1469 width=3D4) Filter: ((p_name)::text ~~ 'forest%'::text= ) -> Index Scan using pk_partsupp on partsupp (cost=3D0.42..688053.87 rows=3D1 width=3D8) Index Cond: (ps_partkey =3D part.p_partkey= ) Filter: ((ps_availqty)::numeric > (SubPlan expr_1)) SubPlan expr_1 -> Aggregate (cost=3D172009.42..172009.44 rows=3D1 width=3D32) -> Seq Scan on lineitem (cost=3D0.00..172009.42 rows=3D1 width=3D5) Filter: ((l_shipdate >=3D '1994-01-01'::date) AND (l_shipdate < '1995-01-01 00:00:00'::timestamp without time zone) AND (l_partkey =3D partsupp.ps_partkey) AND (l_suppkey =3D partsupp.ps_suppkey)) Generated Plan Advice: JOIN_ORDER(nation (supplier (part partsupp))) NESTED_LOOP_PLAIN(partsupp partsupp) <--- [X] NESTED_LOOP_MATERIALIZE(partsupp) SEQ_SCAN(nation supplier part lineitem@expr_1) INDEX_SCAN(partsupp public.pk_partsupp) SEMIJOIN_NON_UNIQUE((partsupp part)) NO_GATHER(supplier nation partsupp part lineitem@expr_1) Please see the - I think it's confusing? - NESTED_LOOP_MATERIALIZE(partsupp partsupp) - that's 2x the same string? This causes it to turn into below plan -- I've marked the problem with [X] Sort (cost=3D50035755.50..50035755.66 rows=3D61 width=3D51) Sort Key: supplier.s_name -> Nested Loop (cost=3D12562154.32..50035753.70 rows=3D61 width=3D51) Join Filter: (nation.n_nationkey =3D supplier.s_nationkey) -> Seq Scan on nation (cost=3D0.00..1.31 rows=3D1 width=3D4) Filter: (n_name =3D 'CANADA'::bpchar) -> Nested Loop Semi Join (cost=3D12562154.32..50035733.36 rows=3D1522 width=3D55) [X] -- missing Join Filter here -> Seq Scan on supplier (cost=3D0.00..249.30 rows=3D7730 w= idth=3D59) [X] -- HJ instead of Materialize+Nested Loop below: -> Hash Join (cost=3D12562154.32..12567002.09 rows=3D1 wid= th=3D4) Hash Cond: (part.p_partkey =3D partsupp.ps_partkey) -> Seq Scan on part (cost=3D0.00..4842.25 rows=3D1469 width=3D4) Filter: ((p_name)::text ~~ 'forest%'::text) -> Hash (cost=3D12562154.02..12562154.02 rows=3D24 w= idth=3D8) -> Index Scan using pk_partsupp on partsupp (cost=3D0.42..12562154.02 rows=3D24 width=3D8) [X] -- wrong Index Cond below (suppkey instead of partkey) Index Cond: (ps_suppkey =3D supplier.s_sup= pkey) Filter: ((ps_availqty)::numeric > (SubPlan expr_1)) SubPlan expr_1 -> Aggregate (cost=3D172009.42..172009.44 rows=3D1 width=3D32) -> Seq Scan on lineitem (cost=3D0.00..172009.42 rows=3D1 width=3D5) Filter: ((l_shipdate >=3D '1994-01-01'::date) AND (l_shipdate < '1995-01-01 00:00:00'::timestamp without time zone) AND (l_partkey =3D partsupp.ps_partkey) AND (l_suppkey =3D partsupp.ps_suppkey)) Supplied Plan Advice: SEQ_SCAN(nation) /* matched */ SEQ_SCAN(supplier) /* matched */ SEQ_SCAN(part) /* matched */ SEQ_SCAN(lineitem@expr_1) /* matched */ INDEX_SCAN(partsupp public.pk_partsupp) /* matched */ JOIN_ORDER(nation (supplier (part partsupp))) /* matched, conflicting */ NESTED_LOOP_PLAIN(partsupp) /* matched, conflicting */ NESTED_LOOP_PLAIN(partsupp) /* matched, conflicting */ NESTED_LOOP_MATERIALIZE(partsupp) /* matched, conflicting, failed */ SEMIJOIN_NON_UNIQUE((partsupp part)) /* matched, conflicting */ NO_GATHER(supplier) /* matched */ NO_GATHER(nation) /* matched */ NO_GATHER(partsupp) /* matched */ NO_GATHER(part) /* matched */ NO_GATHER(lineitem@expr_1) /* matched */ So the difference is basically between: set pg_plan_advice.advice =3D '[..] NESTED_LOOP_PLAIN(partsupp partsupp) NESTED_LOOP_MATERIALIZE(partsupp) [..]'; which causes wrong plan and outcome: NESTED_LOOP_MATERIALIZE(partsupp) /* matched, conflicting, failed */ and apparently proper advice like below which has better yield: set pg_plan_advice.advice =3D '[..] NESTED_LOOP_PLAIN(part partsupp) NESTED_LOOP_MATERIALIZE(partsupp) [..]'; which is not generated , but caused good plan, however it also prints: NESTED_LOOP_PLAIN(part) /* matched, conflicting, failed */ NESTED_LOOP_MATERIALIZE(partsupp) /* matched, conflicting */ but that seems "failed" there, seems to be untrue? Another idea is perhaps, we could have some elog(WARNING) - but not Asserts() - in assert-only enabled build that could alert us in case of duplicated entries being detected for the same ops in pg_plan_advice_explain_feedback()? -J.