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.94.2) (envelope-from ) id 1ruEPv-003iXa-0W for pgsql-general@arkaria.postgresql.org; Tue, 09 Apr 2024 16:33:51 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1ruEPt-007Wek-6U for pgsql-general@arkaria.postgresql.org; Tue, 09 Apr 2024 16:33:49 +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.94.2) (envelope-from ) id 1ruEPs-007Wea-OX for pgsql-general@lists.postgresql.org; Tue, 09 Apr 2024 16:33:48 +0000 Received: from mail-oa1-x2e.google.com ([2001:4860:4864:20::2e]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1ruEPl-0003BW-He for pgsql-general@postgresql.org; Tue, 09 Apr 2024 16:33:47 +0000 Received: by mail-oa1-x2e.google.com with SMTP id 586e51a60fabf-22e6b61d652so1864850fac.0 for ; Tue, 09 Apr 2024 09:33:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1712680418; x=1713285218; darn=postgresql.org; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=pRE7evKyK66k2Ac6cNdh9EpE8i5DGCVk9y19WsRwD+Q=; b=aGgb0u9BzK/lV37mtcKmgyu7/8ChHNhOGXg0HgUVQYCwQD5gd2/LTqdhke6Husn5Oq VOQzL0+lcJiTPP7ygkZCMyQYX42X+AfEwS0wjghaEDQqDa3jhUX6HcOMemUyCZG+2u34 9YSWxpFBqJ9RnR6pyz5sv3kd94bBaK1Elt1r+/rk/5ElTYKSGZmXslOVIKeoXltxRhdS vxoLuTGO2MM67M5sZcR79MM74zjoBS/9aUYm+oZSHIoMoCwTiyTLemhSf/2Kh+RIfjG6 ztEC5dkd3o2qx/Vwf8Q1nXabR4aF07pEcAh88qFADdFO83+9xVOBo8BrC0SXMEu1s9Tt iGxg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1712680418; x=1713285218; h=to:subject:message-id:date:from:mime-version:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=pRE7evKyK66k2Ac6cNdh9EpE8i5DGCVk9y19WsRwD+Q=; b=wCaiBIKjjm2Rny+Joa3WhzG/dFVXHZa7ZM0cATXWmzZfKU92iTPQu7r+VBvQEcnO9d pOu/TNpgsU29Sds3eRHc8vaNYU7IWob20iJWwkDrS4LTYcD31AN+lVj6Wv9htELnA9ac dX90dtGN8lJRkLepU6vpYcgdGUU65SwNi8Pz+YaPVl29Yi8bMPeevk+vDLTmh0pXjw0G IZMai6v7PjCk1Tnp02PYgaeGkdl/lAc9fft7g7SUH/ONH+wROql/mIX6zgy5QSJEKUMo Jfh991te3VdukgtQwYVT7w85qT4Fwum4dwDItqE+2W/KYHZNYjzgZPxsotNYW3QOW0oW rdJQ== X-Gm-Message-State: AOJu0YyMSEXmV7EyDevLYrS3bAtMSFWbTBbORazJ6IbCJ8OhQUCx3VkX iS8tTDdQd3b3pbfC/0HBvcxilR8EulfJOB2Sh/xd4gKzzeG48dfveJxtyYXB0K40LJoM+x3cDJv r9MU+d1QxMDUCLaoWeyI6tej5mLR+m3Vb36Y= X-Google-Smtp-Source: AGHT+IGryQrZLZ8hLyLjh7j/xdGWHv4yJqJ2nl3ElIt1TDvZ3rrbFRbR91rwta+OcK1xYpA7yhRvR+YrII0tyfMBeX0= X-Received: by 2002:a05:6870:658b:b0:22e:ddcb:b495 with SMTP id fp11-20020a056870658b00b0022eddcbb495mr120106oab.19.1712680418171; Tue, 09 Apr 2024 09:33:38 -0700 (PDT) MIME-Version: 1.0 From: Ron Johnson Date: Tue, 9 Apr 2024 12:33:27 -0400 Message-ID: Subject: PL/pgSQL techniques better than bash for dynamic DO? To: pgsql-general Content-Type: multipart/alternative; boundary="000000000000f0660b0615ac7b6c" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000f0660b0615ac7b6c Content-Type: text/plain; charset="UTF-8" PG 9.6.11, if relevant, migrating to PG 14 Real Soon Now. I must purge the oldest X period of records from 70 tables, every Sunday. The field name, interval (X days or months) and date (CURRENT_DATE or CURRENT_TIMESTAMP) varies for each table. Thus, I put all the relevant data in a tab-separated value file, and use bash to read through it, purging one table at a time. This works well, except for Foreign Key constraints; carefully ordering the file to purge records in the correct order eliminates most FK errors, but not all. Therefore, I created an anonymous DO statement to delete the "deletable" old records, while skipping the ones that would fail from a FK constraint. (Eventually, the records in the FK table will get deleted, so eventually the records who's DELETE failed will succeed in getting deleted.) (NOTE: I cannot change the FK constraints to ON DELETE CASCADE, and nor do I want to fight with the 3rd party app vendor, since it defeats the purpose of FK constraints.) Here's the snippet of bash code: local Schema=$1 local Table=$2 local Field=$3 local DtCol=$4 # CURRENT_TIMESTAMP or CURRENT_DATE local Thresh=$5 # example: '90 day' local FQTable=${Schema}.${Table} DeS="DO \$\$ DECLARE delsum INTEGER = 0; delcnt INTEGER; skipsum integer = 0; cur_row CURSOR FOR SELECT $Field, ${Table}_id from ${FQTable} where $Field < (${DtCol} - interval ${Thresh}); BEGIN FOR arow IN cur_row LOOP BEGIN DELETE FROM ${FQTable} WHERE CURRENT OF cur_row; GET DIAGNOSTICS delcnt = ROW_COUNT; delsum = delsum + delcnt; EXCEPTION WHEN others THEN skipsum = skipsum + 1; RAISE NOTICE ' Skipped ${FQTable} WHERE ${Table}_id = %; ${Field} = %', arow.${Table}_id, arow.${Field}; END; END LOOP; RAISE NOTICE 'Sum of deleted rows: %', delsum; RAISE NOTICE 'Sum of skipped rows: %', skipsum; END \$\$; " It generates the perfectly functional SQL: DO $$ DECLARE delsum INTEGER = 0; delcnt INTEGER; skipsum integer = 0; cur_row CURSOR FOR SELECT modified_on, check_id from tms.check where modified_on < (CURRENT_TIMESTAMP - interval '90 day'); BEGIN FOR arow IN cur_row LOOP BEGIN DELETE FROM tms.check WHERE CURRENT OF cur_row; GET DIAGNOSTICS delcnt = ROW_COUNT; delsum = delsum + delcnt; EXCEPTION WHEN others THEN skipsum = skipsum + 1; RAISE NOTICE ' Skipped tms.check WHERE check_id = %; modified_on = %', arow.check_id, arow.modified_on; END; END LOOP; RAISE NOTICE 'Sum of deleted rows: %', delsum; RAISE NOTICE 'Sum of skipped rows: %', skipsum; END $$; Can I do this better in PL/pgSQL with dynamic SQL (that doesn't get hairy with nested quotes, etc)? --000000000000f0660b0615ac7b6c Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
PG 9.6.11, if relevant, migrating to PG 14 Real Soon = Now.

I must purge the oldest X period of records from 7= 0 tables, every Sunday.=C2=A0 The field name, interval (X days or months) a= nd date (CURRENT_DATE or CURRENT_TIMESTAMP) varies for each table.
Thus= , I put all the relevant data in a tab-separated value file, and use bash t= o read through it, purging one table at a time.=C2=A0 This works well, exce= pt for Foreign Key constraints; carefully ordering the file to purge record= s in the correct order eliminates most FK errors, but not all.
Therefore, I created an anonymous DO statement to delete the &= quot;deletable" old records, while skipping the ones that would fail f= rom a FK constraint.=C2=A0 (Eventually, the records in the FK table will ge= t deleted, so eventually the records who's DELETE failed will succeed i= n getting deleted.)

(NOTE: I cannot change the FK = constraints to ON DELETE CASCADE, and nor do I want to fight with=C2=A0the= =C2=A03rd party app vendor, since it defeats the purpose of FK constraints.= )

Here's the snippet of bash code:
<= font face=3D"monospace">=C2=A0 =C2=A0 local Schema=3D$1
=C2=A0 =C2=A0 lo= cal Table=3D$2
=C2=A0 =C2=A0 local Field=3D$3
=C2=A0 =C2=A0 local DtC= ol=3D$4 =C2=A0# CURRENT_TIMESTAMP or CURRENT_DATE
=C2=A0 =C2=A0 local Th= resh=3D$5 # example: '90 day'
=C2=A0 =C2=A0 local FQTable=3D${Sc= hema}.${Table}
=C2=A0 =C2=A0 = DeS=3D"DO \$\$
DECLARE
=C2=A0 =C2=A0 delsum INTEGER =3D 0;
= =C2=A0 =C2=A0 delcnt INTEGER;
=C2=A0 =C2=A0 skipsum integer =3D 0;
= =C2=A0 =C2=A0 cur_row CURSOR FOR
=C2=A0 =C2=A0 =C2=A0 =C2=A0 SELECT $Fie= ld, ${Table}_id=C2=A0
=C2=A0 =C2= =A0 =C2=A0 =C2=A0 from ${FQTable}=C2=A0
=C2=A0 =C2=A0 =C2=A0 =C2=A0 where $Field < (${DtCol} - interval $= {Thresh});
BEGIN
=C2=A0 =C2=A0 FOR arow IN cur_row
=C2=A0 =C2=A0 L= OOP
=C2=A0 =C2=A0 =C2=A0 =C2=A0 BEGIN
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 DELETE FROM ${FQTable} WHERE CURRENT OF cur_row;
=C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 GET DIAGNOSTICS delcnt =3D ROW_COUNT;
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 delsum =3D delsum + delcnt;=C2=A0 =C2=A0 =C2=A0 =C2=A0 EXCEPTIO= N
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 WHEN others THEN
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 skipsum =3D skipsum + 1;=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 RAISE NOTICE '= ; =C2=A0 =C2=A0Skipped ${FQTable} WHERE ${Table}_id =3D %; ${Field} =3D %&#= 39;,=C2=A0
=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 arow.${Table}_id, arow.${F= ield};
=C2=A0 =C2=A0 =C2=A0 =C2=A0 END;
=C2=A0 =C2=A0 END LOOP;
= =C2=A0 =C2=A0 RAISE NOTICE 'Sum of deleted rows: %', delsum;
=C2= =A0 =C2=A0 RAISE NOTICE 'Sum of skipped rows: %', skipsum;
END \= $\$;
"


It generates the perfect= ly functional SQL:
DO $$
DECLARE
= =C2=A0 =C2=A0 delsum INTEGER =3D 0;
=C2=A0 =C2=A0 delcnt INTEGER;
=C2= =A0 =C2=A0 skipsum integer =3D 0;
=C2=A0 =C2=A0 cur_row CURSOR FOR
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 SELECT modified_on, check_id=C2=A0
=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 from tms.check=C2= =A0
=C2=A0 =C2=A0 =C2=A0 =C2=A0 w= here modified_on < (CURRENT_TIMESTAMP - interval '90 day');
B= EGIN
=C2=A0 =C2=A0 FOR arow IN cur_row
=C2=A0 =C2=A0 LOOP
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 BEGIN
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 DEL= ETE FROM tms.check WHERE CURRENT OF cur_row;
=C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 GET DIAGNOSTICS delcnt =3D ROW_COUNT;
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 delsum =3D delsum + delcnt;
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 EXCEPTION
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 WHEN othe= rs THEN
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 skipsum = =3D skipsum + 1;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= RAISE NOTICE ' =C2=A0 =C2=A0Skipped tms.check WHERE check_id =3D %; mo= dified_on =3D %',=C2=A0
=C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 arow.che= ck_id, arow.modified_on;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 END;
=C2=A0 =C2= =A0 END LOOP;
=C2=A0 =C2=A0 RAISE NOTICE 'Sum of deleted rows: %'= ;, delsum;
=C2=A0 =C2=A0 RAISE NOTICE 'Sum of skipped rows: %', = skipsum;
END $$;

Can I do this bette= r in PL/pgSQL with dynamic SQL (that doesn't get hairy with nested quot= es, etc)?
--000000000000f0660b0615ac7b6c--