Thread: obtuse plpgsql function needs
given create table t1 (f,f1,f2,f3); create table t2 (f,f4,f5,f6); i'm trying to create a function concat() that does something like: select f,concat() as info from t1; which returns a result set equivalent to: select f,('f1:' || f1 || '- f2:' || f2 || '- f3:' || f3) as x from t1; or select f,concat() as info from t2; returns equivalent select f,('f4:' || f4 || ' - f5:' || f5 || ' - f6:' || f6) as x from t2; I'm starting to believe this is not possible, has anyone already done it? :-) Robert Treat -- Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL
Robert, > I'm starting to believe this is not possible, has anyone already done > it? :-) It sounds doable but you need more explicit examples; I can't quite tell what you're trying to do. -- Josh Berkus Aglio Database Solutions San Francisco
You'll need to pass the values down to your concat function (which I suggest you don't call concat) and have it return a text type. What exactly is your problem? I must be missing something. elein On Tue, Jul 22, 2003 at 06:31:52PM -0400, Robert Treat wrote: > given > > create table t1 (f,f1,f2,f3); > create table t2 (f,f4,f5,f6); > > i'm trying to create a function concat() that does something like: > > select f,concat() as info from t1; > > which returns a result set equivalent to: > select f,('f1:' || f1 || '- f2:' || f2 || '- f3:' || f3) as x from t1; > > or > select f,concat() as info from t2; > returns equivalent > > select f,('f4:' || f4 || ' - f5:' || f5 || ' - f6:' || f6) as x from t2; > > > I'm starting to believe this is not possible, has anyone already done > it? :-) > > Robert Treat > -- > Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL > > > ---------------------------(end of broadcast)--------------------------- > TIP 8: explain analyze is your friend >
On Tue, 2003-07-22 at 19:33, elein wrote: > You'll need to pass the values down to your > concat function (which I suggest you don't call concat) > and have it return a text type. > > What exactly is your problem? I must be missing something. > The problem is that I need the function to be generic so that I don't have to pass the values down to the function, it just grabs the values automagically based on the table it's being called against. Robert Treat > elein > > On Tue, Jul 22, 2003 at 06:31:52PM -0400, Robert Treat wrote: > > given > > > > create table t1 (f,f1,f2,f3); > > create table t2 (f,f4,f5,f6); > > > > i'm trying to create a function concat() that does something like: > > > > select f,concat() as info from t1; > > > > which returns a result set equivalent to: > > select f,('f1:' || f1 || '- f2:' || f2 || '- f3:' || f3) as x from t1; > > > > or > > select f,concat() as info from t2; > > returns equivalent > > > > select f,('f4:' || f4 || ' - f5:' || f5 || ' - f6:' || f6) as x from t2; > > -- Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL
On Wed, 2003-07-23 at 09:06, Robert Treat wrote: > On Tue, 2003-07-22 at 19:33, elein wrote: > > You'll need to pass the values down to your > > concat function (which I suggest you don't call concat) > > and have it return a text type. > > > > What exactly is your problem? I must be missing something. > > > > The problem is that I need the function to be generic so that I don't > have to pass the values down to the function, it just grabs the values > automagically based on the table it's being called against. > > Robert Treat > Hmm... I neglected to mention that I was trying to find a solution that didn't use ctid or oid. If I abandon that path I think it will be straightforward... Robert Treat -- Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL
How will you know in your function what the field names are (you won't) and how many fields to concat unless the function would only work on a fixed number of fields? If it only works on a fixed number of fields, you still have:myconcat( text, text, text, text ) called by select f, myconcat( f,f1,f2,f3) from t1; andselect f, myconcat( f,f4,f5,f6) from t2; The cost is typing in the param list. For a variable length record it is trickier. You can do it in C, of course. The key pieces needed to do this are:* Ability to pass a generic RECORD to a function. This *might* be in 7.4 but I'mnot sure. myconcat( t1 ); or possibly myconcat (t1.*);* Ability to know the number of columns in the RECORD A pg_catalogquery* Ability to access the columns by order in a loop AFAIK you have to access the columns by name. If you can work through those issues, then you'll have it. The pieces are available in several areas, the generic types and languages like plpython and plperl which may be able to loop through a generic tuple, if they could input a tuple. I will hang onto this problem and if either of us finds a solution, I'd like to publish it in general bits. elein On Wed, Jul 23, 2003 at 09:06:49AM -0400, Robert Treat wrote: > On Tue, 2003-07-22 at 19:33, elein wrote: > > You'll need to pass the values down to your > > concat function (which I suggest you don't call concat) > > and have it return a text type. > > > > What exactly is your problem? I must be missing something. > > > > The problem is that I need the function to be generic so that I don't > have to pass the values down to the function, it just grabs the values > automagically based on the table it's being called against. > > Robert Treat > > > elein > > > > On Tue, Jul 22, 2003 at 06:31:52PM -0400, Robert Treat wrote: > > > given > > > > > > create table t1 (f,f1,f2,f3); > > > create table t2 (f,f4,f5,f6); > > > > > > i'm trying to create a function concat() that does something like: > > > > > > select f,concat() as info from t1; > > > > > > which returns a result set equivalent to: > > > select f,('f1:' || f1 || '- f2:' || f2 || '- f3:' || f3) as x from t1; > > > > > > or > > > select f,concat() as info from t2; > > > returns equivalent > > > > > > select f,('f4:' || f4 || ' - f5:' || f5 || ' - f6:' || f6) as x from t2; > > > > -- > Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL >
Elein, Robert, I think Robert can do this in 7.3.3, and in PL/pgSQL. But I'm not going any further on it until Robert clarifies his examples, because I'm not sure what he's talking about. In your example, Robert, you use "f1" to indicate both the column f1 and the value of the column f1. This makes it impossible for me to understand which of the two you want. Try again? -- Josh Berkus Aglio Database Solutions San Francisco
elein <elein@varlena.com> writes: > You can do it in C, of course. Yeah. Also you could do it easily in plperl or pltcl (composite-type arguments get passed as perl hashes or Tcl arrays respectively). plpgsql does not have any facility for run-time determination of field names, so you're pretty much out of luck in that particular language. regards, tom lane
So, other than C, plperl or pltcl is the way to go. As long as they can input generic composite types (I wasn't sure of that, but I should have known), they can access columns as array elements so you can loop through them. And they'll tell you the number of arguments. Ta da! elein On Wed, Jul 23, 2003 at 03:15:50PM -0400, Tom Lane wrote: > elein <elein@varlena.com> writes: > > You can do it in C, of course. > > Yeah. Also you could do it easily in plperl or pltcl (composite-type > arguments get passed as perl hashes or Tcl arrays respectively). > plpgsql does not have any facility for run-time determination of field > names, so you're pretty much out of luck in that particular language. > > regards, tom lane >
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Perhaps something like this?. Called like thus: SELECT a,b,c,msgmaker('t1',ctid) FROM t1 WHERE a=b; CREATE OR REPLACE FUNCTION msgmaker(text,tid) RETURNS text AS ' DECLARE mytable ALIAS FOR $1; mytid ALIAS FOR $2; myctid TEXT; myquery TEXT; mylen SMALLINT := 20; yourlen SMALLINT; mydec SMALLINT; myinfo TEXT; myrec RECORD; biglist TEXT :=\'Error\'; BEGIN myquery := \' SELECT length(attname) AS lenny FROM pg_attribute WHERE attnum >=1 AND attrelid = (SELECT oid FROM pg_classWHERE relname = \'\'\' || mytable || \'\'\') ORDER BY 1 DESC LIMIT 1\'; FOR myrec IN EXECUTE myquery LOOP mylen := myrec.lenny; END LOOP; myquery := \' SELECT attname, atttypid, atttypmod FROM pg_attribute WHERE attnum >=1 AND attrelid = (SELECT oid FROMpg_class WHERE relname = \'\'\' || mytable || \'\'\') ORDER BY attname ASC\'; myinfo := \'SELECT \'; FOR myrec IN EXECUTE myquery LOOP myinfo := myinfo || \'\'\'- \' || myrec.attname || \': \'; yourlen := LENGTH(myrec.attname); LOOP myinfo := myinfo || \' \'; yourlen := yourlen + 1; EXIT WHEN yourlen > mylen; END LOOP; myinfo := myinfo || \'\'\' || COALESCE(\'; IF myrec.atttypid = 1184 THEN myinfo := myinfo || \'TO_CHAR(\'|| myrec.attname || \',\'\'Mon DD, YYYY HH24:MI\'\')\'; ELSIF myrec.atttypid = 16 THEN myinfo := myinfo|| \'CASE WHEN \' || myrec.attname || \' IS TRUE THEN \'\'True\'\' ELSE \'\'False\'\' END\'; ELSIF myrec.atttypid= 17 THEN myinfo := myinfo || \'ENCODE(\' || myrec.attname || \',\'\'hex\'\')\'; ELSIF myrec.atttypid= 1700 THEN SELECT substr(rtrim(format_type(myrec.atttypid, myrec.atttypmod),\')\'), position(\',\' INformat_type(myrec.atttypid, myrec.atttypmod))+1) INTO mydec; myinfo := myinfo || \'TO_CHAR(\' || myrec.attname || \',\'\'FM99999999990\'; IF mydec > 1 THEN myinfo := myinfo || \'.\'; LOOP myinfo := myinfo || \'0\'; mydec := mydec - 1; EXIT WHEN mydec < 1; END LOOP; END IF; myinfo := myinfo || \'\'\')\'; ELSE myinfo := myinfo || myrec.attname; END IF; myinfo := myinfo || \'::text,\'\'<null>\'\'::text) ||\'\'\\\\n\'\' || \\n\'; END LOOP; SELECT mytid INTO myctid; myinfo := myinfo || \'\'\'\\\\n\'\' AS info FROM \' || mytable || \' WHERE ctid = \'\'\' || myctid || \'\'\'\'; FOR myrec IN EXECUTE myinfo LOOP biglist := myrec.info; END LOOP; RETURN biglist; END; ' LANGUAGE 'plpgsql'; - -- Greg Sabino Mullane greg@turnstep.com PGP Key: 0x14964AC8 200307231536 -----BEGIN PGP SIGNATURE----- Comment: http://www.turnstep.com/pgp.html iD8DBQE/HuPCvJuQZxSWSsgRAnNsAJ9Qljeo+2NkBIp17TKb6SRf2T6WwACg8bwV A2TBRJdMzk0jpw67sIk3+uc= =cjEZ -----END PGP SIGNATURE-----
Questions for the group: 1) any way to do this without the ctid/oid? Sounds like I could do select a,b,msgmaker(*) from t1 where a=b; in pltcl (which was an early inclination I abandoned, perhaps prematurely) 2) would it be faster in pltcl? seems like it would if i didn't have to do the catalog lookups, but is pltcl inherently faster anyways? thanks for the input so far. Robert Treat On Wed, 2003-07-23 at 15:38, greg@turnstep.com wrote: > SELECT a,b,c,msgmaker('t1',ctid) FROM t1 WHERE a=b; > > > CREATE OR REPLACE FUNCTION msgmaker(text,tid) RETURNS text AS ' > > DECLARE > > mytable ALIAS FOR $1; > mytid ALIAS FOR $2; > myctid TEXT; > > myquery TEXT; > mylen SMALLINT := 20; > yourlen SMALLINT; > mydec SMALLINT; > myinfo TEXT; > myrec RECORD; > biglist TEXT := \'Error\'; > > BEGIN > > myquery := \' > SELECT length(attname) AS lenny FROM pg_attribute > WHERE attnum >=1 > AND attrelid = (SELECT oid FROM pg_class WHERE relname = \'\'\' || mytable || \'\'\') > ORDER BY 1 DESC LIMIT 1\'; > > FOR myrec IN EXECUTE myquery LOOP > mylen := myrec.lenny; > END LOOP; > > myquery := \' > SELECT attname, atttypid, atttypmod FROM pg_attribute > WHERE attnum >=1 > AND attrelid = (SELECT oid FROM pg_class WHERE relname = \'\'\' || mytable || \'\'\') > ORDER BY attname ASC\'; > > myinfo := \'SELECT \'; > > FOR myrec IN EXECUTE myquery LOOP > myinfo := myinfo || \'\'\'- \' || myrec.attname || \': \'; > yourlen := LENGTH(myrec.attname); > LOOP > myinfo := myinfo || \' \'; > yourlen := yourlen + 1; > EXIT WHEN yourlen > mylen; > END LOOP; > myinfo := myinfo || \'\'\' || COALESCE(\'; > IF myrec.atttypid = 1184 THEN > myinfo := myinfo || \'TO_CHAR(\' || myrec.attname || \',\'\'Mon DD, YYYY HH24:MI\'\')\'; > ELSIF myrec.atttypid = 16 THEN > myinfo := myinfo || \'CASE WHEN \' || myrec.attname || \' IS TRUE THEN \'\'True\'\' ELSE \'\'False\'\' END\'; > ELSIF myrec.atttypid = 17 THEN > myinfo := myinfo || \'ENCODE(\' || myrec.attname || \',\'\'hex\'\')\'; > ELSIF myrec.atttypid = 1700 THEN > SELECT substr(rtrim(format_type(myrec.atttypid, myrec.atttypmod),\')\'), position(\',\' IN format_type(myrec.atttypid,myrec.atttypmod))+1) INTO mydec; > myinfo := myinfo || \'TO_CHAR(\' || myrec.attname || \',\'\'FM99999999990\'; > IF mydec > 1 THEN > myinfo := myinfo || \'.\'; > LOOP > myinfo := myinfo || \'0\'; > mydec := mydec - 1; > EXIT WHEN mydec < 1; > END LOOP; > END IF; > myinfo := myinfo || \'\'\')\'; > ELSE > myinfo := myinfo || myrec.attname; > END IF; > myinfo := myinfo || \'::text,\'\'<null>\'\'::text) || \'\'\\\\n\'\' || \\n\'; > END LOOP; > > SELECT mytid INTO myctid; > > myinfo := myinfo || \'\'\'\\\\n\'\' AS info FROM \' || mytable || \' WHERE ctid = \'\'\' || myctid || \'\'\'\'; > > FOR myrec IN EXECUTE myinfo LOOP > biglist := myrec.info; > END LOOP; > > RETURN biglist; > > END; > > ' LANGUAGE 'plpgsql'; > -- Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL
Robert, > 2) would it be faster in pltcl? seems like it would if i didn't have to > do the catalog lookups, but is pltcl inherently faster anyways? Probably, yes. Execution of dynamic query strings in PL/pgSQL tends to be pretty slow. -- -Josh BerkusAglio Database SolutionsSan Francisco
On Wed, 2003-07-23 at 15:38, greg@turnstep.com wrote: > FOR myrec IN EXECUTE myinfo LOOP > biglist := myrec.info; > END LOOP; > One other thing, I hate when I have to do things like the above, can we get a TODO like: allow 'EXECUTE var INTO record' in plpgsql Robert Treat -- Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL
Robert Treat wrote: > On Wed, 2003-07-23 at 15:38, greg@turnstep.com wrote: > > FOR myrec IN EXECUTE myinfo LOOP > > biglist := myrec.info; > > END LOOP; > > > > One other thing, I hate when I have to do things like the above, can we > get a TODO like: > > allow 'EXECUTE var INTO record' in plpgsql So the TODO would be? Allow PL/pgSQL EXECUTE to return a single record outside a loop -- Bruce Momjian | http://candle.pha.pa.us pgman@candle.pha.pa.us | (610) 359-1001+ If your life is a hard drive, | 13 Roberts Road + Christ can be your backup. | Newtown Square, Pennsylvania19073
On Wednesday 23 July 2003 19:06, Bruce Momjian wrote: > Robert Treat wrote: > > On Wed, 2003-07-23 at 15:38, greg@turnstep.com wrote: > > > FOR myrec IN EXECUTE myinfo LOOP > > > biglist := myrec.info; > > > END LOOP; > > > > One other thing, I hate when I have to do things like the above, can we > > get a TODO like: > > > > allow 'EXECUTE var INTO record' in plpgsql > > So the TODO would be? > > Allow PL/pgSQL EXECUTE to return a single record outside a loop that's what I wrote, but not what I meant :-) I do like the sound of it though, but really what I meant to say was: EXECUTE var1 INTO var2 but this assumes a number of things, namely that executing var1 will return only one field, and one row. I guess that would be: Allow PL/pgSQL EXECUTE to return a single variable outside a loop Robert Treat -- Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL
elein <elein@varlena.com> writes: > So, other than C, plperl or pltcl is the way to go. > As long as they can input generic composite types > (I wasn't sure of that, but I should have known), Come to think of it, that is a problem: we don't have any way to declare a function as taking "any tuple type". So even though pltcl or plperl functions could be written to work with such input, we can't declare them. This is a problem even for C functions. You could declare a C function as taking "any", but then you can't even check that what you got was a tuple ... Something to work on for 7.5, I suppose. regards, tom lane
Bruce-- Something for the todo list. This would be extremely handy. At minimum C functions should be able to ask the type of thing that was actually passed in and get a legitimate answer even if the type were a rowtype. This will also lead to the need for unnamed rowtypes, sooner or later. I know, I know, send a patch. --elein On Thu, Jul 24, 2003 at 01:07:18AM -0400, Tom Lane wrote: > elein <elein@varlena.com> writes: > > So, other than C, plperl or pltcl is the way to go. > > As long as they can input generic composite types > > (I wasn't sure of that, but I should have known), > > Come to think of it, that is a problem: we don't have any way to declare > a function as taking "any tuple type". So even though pltcl or plperl > functions could be written to work with such input, we can't declare them. > This is a problem even for C functions. You could declare a C function > as taking "any", but then you can't even check that what you got was a > tuple ... > > Something to work on for 7.5, I suppose. > > regards, tom lane > > ---------------------------(end of broadcast)--------------------------- > TIP 6: Have you searched our list archives? > > http://archives.postgresql.org >
Does Oracle have a syntax for this? --------------------------------------------------------------------------- Robert Treat wrote: > On Wednesday 23 July 2003 19:06, Bruce Momjian wrote: > > Robert Treat wrote: > > > On Wed, 2003-07-23 at 15:38, greg@turnstep.com wrote: > > > > FOR myrec IN EXECUTE myinfo LOOP > > > > biglist := myrec.info; > > > > END LOOP; > > > > > > One other thing, I hate when I have to do things like the above, can we > > > get a TODO like: > > > > > > allow 'EXECUTE var INTO record' in plpgsql > > > > So the TODO would be? > > > > Allow PL/pgSQL EXECUTE to return a single record outside a loop > > that's what I wrote, but not what I meant :-) I do like the sound of it > though, but really what I meant to say was: > EXECUTE var1 INTO var2 > but this assumes a number of things, namely that executing var1 will return > only one field, and one row. I guess that would be: > Allow PL/pgSQL EXECUTE to return a single variable outside a loop > > Robert Treat > -- > Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL > > ---------------------------(end of broadcast)--------------------------- > TIP 5: Have you checked our extensive FAQ? > > http://www.postgresql.org/docs/faqs/FAQ.html > -- Bruce Momjian | http://candle.pha.pa.us pgman@candle.pha.pa.us | (610) 359-1001+ If your life is a hard drive, | 13 Roberts Road + Christ can be your backup. | Newtown Square, Pennsylvania19073
I don't seem to have any plsql specfic documentation, and the rest of my oracle documentation isn't specfific enough. Anyone else? Robert Treat On Thursday 31 July 2003 00:12, Bruce Momjian wrote: > Does Oracle have a syntax for this? > > --------------------------------------------------------------------------- > > Robert Treat wrote: > > On Wednesday 23 July 2003 19:06, Bruce Momjian wrote: > > > Robert Treat wrote: > > > > On Wed, 2003-07-23 at 15:38, greg@turnstep.com wrote: > > > > > FOR myrec IN EXECUTE myinfo LOOP > > > > > biglist := myrec.info; > > > > > END LOOP; > > > > > > > > One other thing, I hate when I have to do things like the above, can > > > > we get a TODO like: > > > > > > > > allow 'EXECUTE var INTO record' in plpgsql > > > > > > So the TODO would be? > > > > > > Allow PL/pgSQL EXECUTE to return a single record outside a loop > > > > that's what I wrote, but not what I meant :-) I do like the sound of it > > though, but really what I meant to say was: > > EXECUTE var1 INTO var2 > > but this assumes a number of things, namely that executing var1 will > > return only one field, and one row. I guess that would be: > > Allow PL/pgSQL EXECUTE to return a single variable outside a loop > > > > Robert Treat > > -- > > Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL > > > > ---------------------------(end of broadcast)--------------------------- > > TIP 5: Have you checked our extensive FAQ? > > > > http://www.postgresql.org/docs/faqs/FAQ.html -- Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL
Robert, > I don't seem to have any plsql specfic documentation, and the rest of my > oracle documentation isn't specfific enough. Anyone else? As far as I can tell from my PL/SQL guide to Oracle8, PL/SQL does not permit exectution of strings-as-queries at all. So there's no equivalent in PL/SQL. -- -Josh BerkusAglio Database SolutionsSan Francisco
On Fri, Aug 01, 2003 at 01:06:18PM -0700, Josh Berkus wrote: > As far as I can tell from my PL/SQL guide to Oracle8, PL/SQL does not permit > exectution of strings-as-queries at all. So there's no equivalent in PL/SQL. I'm not an Oracle bunny but they seem to have something vaguely similar to what we do; they call it "EXECUTE IMMEDIATE" and the concept is described as "Dynamic SQL". http://download-west.oracle.com/docs/cd/A87860_01/doc/appdev.817/a77069/10_dynam.htm#4376 Richard
Guys, > I'm not an Oracle bunny but they seem to have something vaguely similar > to what we do; they call it "EXECUTE IMMEDIATE" and the concept is > described as "Dynamic SQL". Aha. I see it now; a pretty awful OO-package-style format. I don't think we want to imitate this. -- -Josh BerkusAglio Database SolutionsSan Francisco
OK, so what should the TODO item be? --------------------------------------------------------------------------- Josh Berkus wrote: > Guys, > > > I'm not an Oracle bunny but they seem to have something vaguely similar > > to what we do; they call it "EXECUTE IMMEDIATE" and the concept is > > described as "Dynamic SQL". > > Aha. I see it now; a pretty awful OO-package-style format. I don't think we > want to imitate this. > > -- > -Josh Berkus > Aglio Database Solutions > San Francisco > > > ---------------------------(end of broadcast)--------------------------- > TIP 7: don't forget to increase your free space map settings > -- Bruce Momjian | http://candle.pha.pa.us pgman@candle.pha.pa.us | (610) 359-1001+ If your life is a hard drive, | 13 Roberts Road + Christ can be your backup. | Newtown Square, Pennsylvania19073
Bruce, > OK, so what should the TODO item be? Go with the simple and intuitive: EXECUTE query_var INTO record_var; -- -Josh BerkusAglio Database SolutionsSan Francisco
Added. --------------------------------------------------------------------------- Josh Berkus wrote: > Bruce, > > > OK, so what should the TODO item be? > > Go with the simple and intuitive: > > EXECUTE query_var INTO record_var; > > -- > -Josh Berkus > Aglio Database Solutions > San Francisco > > -- Bruce Momjian | http://candle.pha.pa.us pgman@candle.pha.pa.us | (610) 359-1001+ If your life is a hard drive, | 13 Roberts Road + Christ can be your backup. | Newtown Square, Pennsylvania19073