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 1n7ht3-0005yb-KS for pgsql-bugs@arkaria.postgresql.org; Wed, 12 Jan 2022 17:58:17 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1n7ht1-0008V1-BG for pgsql-bugs@arkaria.postgresql.org; Wed, 12 Jan 2022 17:58:15 +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 1n7ht1-0008Us-2x for pgsql-bugs@lists.postgresql.org; Wed, 12 Jan 2022 17:58:15 +0000 Received: from sss.pgh.pa.us ([66.207.139.130]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1n7hsy-0003eW-Il for pgsql-bugs@lists.postgresql.org; Wed, 12 Jan 2022 17:58:14 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.15.2/8.15.2) with ESMTP id 20CHwALI3011207; Wed, 12 Jan 2022 12:58:11 -0500 From: Tom Lane To: 1165125080@qq.com cc: pgsql-bugs@lists.postgresql.org Subject: Re: BUG #17364: Errors caused by views In-reply-to: <17364-3072d21ebb3da224@postgresql.org> References: <17364-3072d21ebb3da224@postgresql.org> Comments: In-reply-to PG Bug reporting form message dated "Wed, 12 Jan 2022 15:53:31 +0000" MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-ID: <3011205.1642010290.1@sss.pgh.pa.us> Content-Transfer-Encoding: quoted-printable Date: Wed, 12 Jan 2022 12:58:10 -0500 Message-ID: <3011206.1642010290@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk PG Bug reporting form writes: > I run the following SQL statements on postgres13.2. They are simple, but= an > error occurs. > create table test1 (id int, c_data varchar); > insert into test1 values (1, '201201'); > insert into test1 values (2, 'error'); > create table test2(id int, status varchar); > insert into test2 values (1, 'on'); > insert into test2 values (2, 'off'); > create view test_view as > SELECT test1.c_data > from test1 LEFT JOIN test2 > ON (test2.id =3D test1.id) > WHERE (test2.status)::text =3D 'on'::text; > select * from test_view; > c_data > ---------- > 20201101 > (1 row) > select * from > (select to_date(c_data, 'YYYYMMDD') as date > from test_view) t1 > where t1.date >=3D '2020 - 10 - 01':: TIMESTAMP; > ERROR: invalid value "erro" for "YYYY" > DETAIL: Value must be an integer. The reason this is failing is that the outer WHERE clause is being pushed down to the scan of test1, so it's applied before the join occurs, and then to_date spits up on the "error" value. You can see that in EXPLAIN: =3D# explain select * from (select to_date(c_data, 'YYYYMMDD') as date from test_view) t1 = where t1.date >=3D '2020-10-01':: TIMESTAMP; QUERY PLAN = = --------------------------------------------------------------------------= ----------------------------------------- Hash Join (cost=3D25.95..56.75 rows=3D13 width=3D4) Hash Cond: (test1.id =3D test2.id) -> Seq Scan on test1 (cost=3D0.00..29.05 rows=3D423 width=3D36) Filter: (to_date((c_data)::text, 'YYYYMMDD'::text) >=3D '2020-10-= 01 00:00:00'::timestamp without time zone) -> Hash (cost=3D25.88..25.88 rows=3D6 width=3D4) -> Seq Scan on test2 (cost=3D0.00..25.88 rows=3D6 width=3D4) Filter: ((status)::text =3D 'on'::text) (7 rows) That's not something we're going to consider a bug, because if we prevented the push-down from happening it would be quite disastrous for the performance of many queries. There are a couple of things you can do to control it yourself, though: * Insert "OFFSET 0" as an optimization fence: =3D# explain select * from (select to_date(c_data, 'YYYYMMDD') as date from test_view offset 0) t1 where t1.date >=3D '2020-10-01':: TIMESTAMP; QUERY PLAN = = --------------------------------------------------------------------------= - Subquery Scan on t1 (cost=3D25.95..54.36 rows=3D13 width=3D4) Filter: (t1.date >=3D '2020-10-01 00:00:00'::timestamp without time zon= e) -> Hash Join (cost=3D25.95..53.89 rows=3D38 width=3D4) Hash Cond: (test1.id =3D test2.id) -> Seq Scan on test1 (cost=3D0.00..22.70 rows=3D1270 width=3D36= ) -> Hash (cost=3D25.88..25.88 rows=3D6 width=3D4) -> Seq Scan on test2 (cost=3D0.00..25.88 rows=3D6 width=3D= 4) Filter: ((status)::text =3D 'on'::text) (8 rows) OFFSET 0 in the view definition would serve too, I think, but I didn't try it. * Wrap the possibly-error-causing function in a VOLATILE wrapper function: =3D# create or replace function mytodate(varchar) returns date as $$ select to_date($1, 'YYYYMMDD')$$ language sql volatile; CREATE FUNCTION =3D# explain verbose select * from (select mytodate(c_data) as date from test_view) t1 where t1.date >=3D '2020-10-01':: TIMESTAMP; QUERY PLAN = = --------------------------------------------------------------------------= ----- Subquery Scan on t1 (cost=3D25.95..54.36 rows=3D13 width=3D4) Output: t1.date Filter: (t1.date >=3D '2020-10-01 00:00:00'::timestamp without time zon= e) -> Hash Join (cost=3D25.95..53.89 rows=3D38 width=3D4) Output: to_date((test1.c_data)::text, 'YYYYMMDD'::text) Hash Cond: (test1.id =3D test2.id) -> Seq Scan on public.test1 (cost=3D0.00..22.70 rows=3D1270 wid= th=3D36) Output: test1.id, test1.c_data -> Hash (cost=3D25.88..25.88 rows=3D6 width=3D4) Output: test2.id -> Seq Scan on public.test2 (cost=3D0.00..25.88 rows=3D6 = width=3D4) Output: test2.id Filter: ((test2.status)::text =3D 'on'::text) (13 rows) The presence of a volatile function in the subquery's output list has about the same effect as OFFSET 0, preventing the WHERE condition from being pushed down past it. In both cases you're significantly de-optimizing your query, so these answers might not be very satisfactory if you have a large amount of data to work with. I'd really suggest reconsidering your data design, or making the query a bit less fragile, perhaps with a sub-select expression along the lines of case when c_data ~ '^\d+$' then to_date(c_data, 'YYYYMMDD') else null end regards, tom lane