<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8839574367290288724</id><updated>2011-11-27T15:29:58.215-08:00</updated><category term='plpgsql'/><category term='postgresql'/><category term='plperl'/><category term='8.4'/><title type='text'>Pavel Stehule's blog</title><subtitle type='html'>Some notes about PostgreSQL</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>47</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-4611822574577658859</id><published>2011-11-12T01:12:00.000-08:00</published><updated>2011-11-12T01:13:26.996-08:00</updated><title type='text'>new version of plpgsql_lint released (for PostgreSQL 9.0 and 9.1)</title><content type='html'>Just notice: I moved a development of plpgsql_lint to &lt;a href="https://github.com/okbob/plpgsql_lint"&gt;github&lt;/a&gt;.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;removed coverage tests - no usefull as I though&lt;/li&gt;&lt;li&gt;cleaned code and enhance support for cursors &lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-4611822574577658859?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/4611822574577658859/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=4611822574577658859' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4611822574577658859'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4611822574577658859'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/11/new-version-of-plpgsqllint-released-for.html' title='new version of plpgsql_lint released (for PostgreSQL 9.0 and 9.1)'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-4829386234442542129</id><published>2011-10-23T01:32:00.000-07:00</published><updated>2011-10-23T04:47:27.274-07:00</updated><title type='text'>Change a unknown record's field in PL/pgSQL</title><content type='html'>Hello&lt;p&gt;Week ago was a request on &lt;a href="http://stackoverflow.com/questions/7711432/how-to-set-value-of-composite-variable-field-using-dynamic-sql"&gt;stackoverflow&lt;/a&gt; about change of composite variable's field by dynamic SQL. I wrote a first very simply solution:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION public.setfield(a anyelement, text, text)&lt;br /&gt;RETURNS anyelement&lt;br /&gt;LANGUAGE plpgsql&lt;br /&gt;AS $function$&lt;br /&gt;begin&lt;br /&gt;  create temp table aux as select $1.*;&lt;br /&gt;  execute 'update aux set ' || quote_ident($2) || ' = ' || quote_literal($3);&lt;br /&gt;  select into $1 * from aux;&lt;br /&gt;  drop table aux;&lt;br /&gt;  return $1;&lt;br /&gt;end;&lt;br /&gt;$function$&lt;br /&gt;&lt;/pre&gt;It's works, but it's slow and it consumes lot of shared memory (impracticable for repeated using in one transaction). Next version was better - it is faster and isn't hungry:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION x.setfield2(anyelement, text, text)&lt;br /&gt; RETURNS anyelement&lt;br /&gt; LANGUAGE plpgsql&lt;br /&gt;AS $function$&lt;br /&gt;DECLARE &lt;br /&gt;  _name text;&lt;br /&gt;  _values text[];&lt;br /&gt;  _value text;&lt;br /&gt;  _attnum int;&lt;br /&gt;BEGIN&lt;br /&gt;  FOR _name, _attnum&lt;br /&gt;     IN SELECT a.attname, a.attnum&lt;br /&gt;           FROM pg_catalog.pg_attribute a &lt;br /&gt;          WHERE a.attrelid = (SELECT typrelid&lt;br /&gt;                                 FROM pg_type&lt;br /&gt;                                WHERE oid = pg_typeof($1)::oid) &lt;br /&gt;  LOOP&lt;br /&gt;    IF _name = $2 THEN&lt;br /&gt;      _value := $3;&lt;br /&gt;    ELSE&lt;br /&gt;      EXECUTE 'SELECT (($1).' || quote_ident(_name) || ')::text' INTO _value USING $1;&lt;br /&gt;    END IF;&lt;br /&gt;    _values[_attnum] :=  COALESCE('"' || replace(replace(_value, '"', '""'), '''', '''''') || '"', '');&lt;br /&gt;  END LOOP;&lt;br /&gt;  EXECUTE 'SELECT (' || pg_typeof($1)::text || '''(' || array_to_string(_values,',') || ')'').*' INTO $1; &lt;br /&gt;  RETURN $1;&lt;br /&gt;END;&lt;br /&gt;$function$;&lt;br /&gt;&lt;/pre&gt;There are a few slower points: EXECUTE in loop, array's update in loop.&lt;p&gt;But &lt;a href="http://stackoverflow.com/users/939860/erwin-brandstetter"&gt;Erwin Brandstetter&lt;/a&gt; found a probably best and most simply solution - there are no more ways on PL/pgSQL level.&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION public.setfield3(anyelement, text, text)&lt;br /&gt;RETURNS anyelement&lt;br /&gt;AS $body$&lt;br /&gt;DECLARE&lt;br /&gt; _list text;&lt;br /&gt;&lt;br /&gt;BEGIN&lt;br /&gt;_list := (&lt;br /&gt;   SELECT string_agg(x.fld, ',')&lt;br /&gt;   FROM   (&lt;br /&gt;      SELECT CASE WHEN a.attname = $2&lt;br /&gt;              THEN quote_literal($3)&lt;br /&gt;              ELSE quote_ident(a.attname)&lt;br /&gt;             END AS fld&lt;br /&gt;      FROM   pg_catalog.pg_attribute a &lt;br /&gt;      WHERE  a.attrelid = (SELECT typrelid&lt;br /&gt;                           FROM   pg_type&lt;br /&gt;                           WHERE  oid = pg_typeof($1)::oid) &lt;br /&gt;      ORDER BY a.attnum&lt;br /&gt;   ) x&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;EXECUTE '&lt;br /&gt;SELECT ' || _list || '&lt;br /&gt;FROM   (SELECT $1.*) x'&lt;br /&gt;USING  $1&lt;br /&gt;INTO   $1;&lt;br /&gt;&lt;br /&gt;RETURN $1;&lt;br /&gt;END;&lt;br /&gt;$body$ LANGUAGE plpgsql;&lt;br /&gt;&lt;/pre&gt;There is no PL/pgSQL's loop and there is just one EXECUTE. More - this code is just simple - without lot of string (quoting) operations.&lt;p&gt;Very nice, Erwin!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-4829386234442542129?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/4829386234442542129/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=4829386234442542129' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4829386234442542129'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4829386234442542129'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/10/change-unknown-records-field-in-plpgsql.html' title='Change a unknown record&apos;s field in PL/pgSQL'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-3199905065556912736</id><published>2011-07-18T03:22:00.000-07:00</published><updated>2011-07-20T02:14:40.500-07:00</updated><title type='text'>updated plpgsql_lint released</title><content type='html'>Hello&lt;p&gt;I uploaded a bugfix version of &lt;i&gt;plpgsql_lint&lt;/i&gt;.&lt;p&gt;&lt;a href="http://kix.fsv.cvut.cz/~stehule/download/plpgsql_lint_2011-07-20.tgz"&gt;http://kix.fsv.cvut.cz/~stehule/download/plpgsql_lint_2011-07-20.tgz&lt;/a&gt;&lt;p&gt;It support a functions with OUT parameters now:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION public.gg(OUT a integer, OUT b integer)&lt;br /&gt; RETURNS record&lt;br /&gt; LANGUAGE sql&lt;br /&gt;AS $function$ select 10,20 $function$&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION public.t1()&lt;br /&gt; RETURNS void&lt;br /&gt; LANGUAGE plpgsql&lt;br /&gt;AS $function$&lt;br /&gt;declare r record;&lt;br /&gt;begin &lt;br /&gt;  raise notice 'start';&lt;br /&gt;  r := gg();&lt;br /&gt;  raise notice '% %', r.a, r.x; -- bug function returns (a,b)&lt;br /&gt;  return;&lt;br /&gt;end;&lt;br /&gt;$function$&lt;br /&gt;&lt;br /&gt;postgres=# select t1();&lt;br /&gt;ERROR:  record "r" has no field "x" -- this bug was found before own execution, there is no message "start"&lt;br /&gt;CONTEXT:  SQL statement "SELECT r.x"&lt;br /&gt;PL/pgSQL function "t1" line 5 at RAISE&lt;br /&gt;&lt;/pre&gt;it running after fixing:&lt;pre&gt;&lt;br /&gt;postgres=# select t1();&lt;br /&gt;NOTICE:  start&lt;br /&gt;NOTICE:  10 20&lt;br /&gt; t1 &lt;br /&gt;────&lt;br /&gt; &lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 10.287 ms&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-3199905065556912736?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/3199905065556912736/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=3199905065556912736' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3199905065556912736'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3199905065556912736'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/07/updated-plpgsqllint-released.html' title='updated plpgsql_lint released'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-3279866932803305697</id><published>2011-07-14T22:03:00.000-07:00</published><updated>2011-07-14T22:03:20.916-07:00</updated><title type='text'>plpgsql lint</title><content type='html'>HelloI wrote a new contrib module &lt;i&gt;plpgsql_lint&lt;/i&gt; for PostgreSQL 9.0. It is a validator of embeded SQL in plpgsql functions. PLpgSQL runtime checks a syntax when function is created, but it doesn't check a semantic of sql - so you can refer to not existing tables, columns, variables. These errors are found when interpret try to evaluate some SQL:&lt;pre&gt; postgres=# select * from tablename ;&lt;br /&gt; columnname &lt;br /&gt;────────────&lt;br /&gt;         10&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;        CREATE OR REPLACE FUNCTION public.foo(x integer)&lt;br /&gt;         RETURNS integer&lt;br /&gt;         LANGUAGE plpgsql&lt;br /&gt;1       AS $function$&lt;br /&gt;2       declare r record;&lt;br /&gt;3       begin&lt;br /&gt;4         if x &amp;gt; 0 then&lt;br /&gt;5           for r in select columnam from tablename&lt;br /&gt;6           loop&lt;br /&gt;7             x := x + r.colname;&lt;br /&gt;8           end loop;&lt;br /&gt;9         end if;&lt;br /&gt;10        return x;&lt;br /&gt;11      end;&lt;br /&gt;12      $function$&lt;br /&gt;&lt;/pre&gt;Function foo contains a two errors - usage of non existing column "columnnam" and reference on non existing field in record "colname". These bugs are not detected when I run function with negative argument:&lt;pre&gt;postgres=# select foo(-1);&lt;br /&gt; foo &lt;br /&gt;─────&lt;br /&gt;  -1&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 0.352 ms&lt;br /&gt;&lt;/pre&gt;with active &lt;i&gt;plpgsql_lint&lt;/i&gt; module, these bugs are diagnosed without dependency on function's parameter value:&lt;pre&gt;postgres=# load 'plpgsql';&lt;br /&gt;LOAD&lt;br /&gt;Time: 0.139 ms&lt;br /&gt;postgres=# load 'plpgsql_lint';&lt;br /&gt;LOAD&lt;br /&gt;Time: 0.137 ms&lt;br /&gt;&lt;br /&gt;postgres=# select foo(-1);&lt;br /&gt;ERROR:  column "columnam" does not exist&lt;br /&gt;LINE 1: select columnam from tablename&lt;br /&gt;               ^&lt;br /&gt;QUERY:  select columnam from tablename&lt;br /&gt;CONTEXT:  PL/pgSQL function "foo" line 4 at FOR over SELECT rows&lt;br /&gt;&lt;br /&gt;-- after fixing FOR statement&lt;br /&gt;postgres=# select foo(-1);&lt;br /&gt;ERROR:  record "r" has no field "colname"&lt;br /&gt;CONTEXT:  SQL statement "SELECT x + r.colname"&lt;br /&gt;PL/pgSQL function "foo" line 6 at assignment&lt;br /&gt;&lt;/pre&gt;This module helps to find all errors in code.&lt;p&gt;It can do a little bit more - it watches a coverage of plpgsql statements in nodes:&lt;p&gt;you can get a value of coverage tests:&lt;pre&gt;postgres=# select statement_coverage('foo');&lt;br /&gt; statement_coverage &lt;br /&gt;────────────────────&lt;br /&gt;                0.5&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 0.423 ms&lt;br /&gt;postgres=# select decision_coverage('foo');&lt;br /&gt; decision_coverage &lt;br /&gt;───────────────────&lt;br /&gt;                 0&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 0.333 ms&lt;br /&gt;&lt;/pre&gt;and you can see, what lines of function was executed:&lt;pre&gt;postgres=# select coverage_get_model('foo');&lt;br /&gt;                      coverage_get_model                      &lt;br /&gt;──────────────────────────────────────────────────────────────&lt;br /&gt; &amp;lt;blo count="0" lineno="2" stmt="statement block"&amp;gt;           &lt;br /&gt;   &amp;lt;ifs count="1" lineno="3" stmt="IF"&amp;gt;                      &lt;br /&gt;     &amp;lt;ift&amp;gt;                                                   &lt;br /&gt;       &amp;lt;lop count="0" lineno="4" stmt="FOR over SELECT rows"&amp;gt;&lt;br /&gt;         &amp;lt;stm count="0" lineno="6" stmt="assignment"&amp;gt;       &lt;br /&gt;       &amp;lt;/stm&amp;gt;&amp;lt;/lop&amp;gt;                                                &lt;br /&gt;     &amp;lt;/ift&amp;gt;                                                  &lt;br /&gt;   &amp;lt;/ifs&amp;gt;                                                    &lt;br /&gt;   &amp;lt;ret count="1" lineno="9" stmt="RETURN"&amp;gt;                 &lt;br /&gt; &amp;lt;/ret&amp;gt;&lt;br /&gt;&amp;lt;/blo&amp;gt;    &lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;Installation&lt;/h2&gt;You have to put code to contrib directory and compile and install from source code. A other installation methods are not supported now:&lt;br /&gt;&lt;pre&gt;[pavel@pavel-stehule postgresql-9.0.4]$ pwd&lt;br /&gt;/home/pavel/src/postgresql-9.0.4&lt;br /&gt;[pavel@pavel-stehule postgresql-9.0.4]$ cd contrib/plpgsql_lint&lt;br /&gt;[pavel@pavel-stehule plpgsql_lint]$ make clean&lt;br /&gt;rm -f plpgsql_lint.so   libplpgsql_lint.a &lt;br /&gt;rm -f plpgsql_coverage.so plpgsql_lint.so plpgsql_coverage.o plpgsql_lint.o&lt;br /&gt;rm -f plpgsql_lint.sql&lt;br /&gt;rm -f plpgsql_coverage.o plpgsql_lint.o&lt;br /&gt;[pavel@pavel-stehule plpgsql_lint]$ make all&lt;br /&gt;sed 's,MODULE_PATHNAME,$libdir/plpgsql_lint,g' plpgsql_lint.sql.in &amp;gt;plpgsql_lint.sql&lt;br /&gt;gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing -fwrapv -g -fpic -fpic -I../../src/pl/plpgsql/src -I. -I. -I../../src/include -D_GNU_SOURCE -I/usr/include/libxml2   -c -o plpgsql_coverage.o plpgsql_coverage.c&lt;br /&gt;gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing -fwrapv -g -fpic -fpic -I../../src/pl/plpgsql/src -L../../src/port -Wl,--as-needed -Wl,-rpath,'/usr/local/pgsql90/lib',--enable-new-dtags  -shared -o plpgsql_coverage.so plpgsql_coverage.o&lt;br /&gt;gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing -fwrapv -g -fpic -fpic -I../../src/pl/plpgsql/src -I. -I. -I../../src/include -D_GNU_SOURCE -I/usr/include/libxml2   -c -o plpgsql_lint.o plpgsql_lint.c&lt;br /&gt;gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing -fwrapv -g -fpic -fpic -I../../src/pl/plpgsql/src -shared -o plpgsql_lint.so plpgsql_coverage.o plpgsql_lint.o -L../../src/port -Wl,--as-needed -Wl,-rpath,'/usr/local/pgsql90/lib',--enable-new-dtags  &lt;br /&gt;[pavel@pavel-stehule plpgsql_lint]$ su&lt;br /&gt;Password: &lt;br /&gt;[root@pavel-stehule plpgsql_lint]# make install&lt;br /&gt;/bin/mkdir -p '/usr/local/pgsql90/lib'&lt;br /&gt;/bin/mkdir -p '/usr/local/pgsql90/share/contrib'&lt;br /&gt;/bin/mkdir -p '/usr/local/pgsql90/lib'&lt;br /&gt;/bin/sh ../../config/install-sh -c -m 755  plpgsql_lint.so '/usr/local/pgsql90/lib/plpgsql_lint.so'&lt;br /&gt;/bin/sh ../../config/install-sh -c -m 644 plpgsql_lint.sql '/usr/local/pgsql90/share/contrib'&lt;br /&gt; /bin/sh ../../config/install-sh -c -m 755  plpgsql_coverage.so '/usr/local/pgsql90/lib'&lt;br /&gt; /bin/sh ../../config/install-sh -c -m 755  plpgsql_lint.so '/usr/local/pgsql90/lib'&lt;br /&gt;&lt;/pre&gt;and in console as user &lt;i&gt;postgres&lt;/i&gt;:&lt;br /&gt;&lt;pre&gt;test=# load 'plpgsql';&lt;br /&gt;LOAD&lt;br /&gt;Time: 0.232 ms&lt;br /&gt;test=# \i /usr/local/pgsql90/share/contrib/plpgsql_lint.sql &lt;br /&gt;CREATE FUNCTION&lt;br /&gt;Time: 21.790 ms&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;Time: 8.356 ms&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;Time: 8.145 ms&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;Time: 8.487 ms&lt;br /&gt;test=# &lt;br /&gt;&lt;/pre&gt;After loading module with command LOAD:&lt;br /&gt;&lt;pre&gt;test=# load 'plpgsql';&lt;br /&gt;LOAD&lt;br /&gt;test=# load 'plpgsql_lint';&lt;br /&gt;LOAD&lt;br /&gt;&lt;/pre&gt;the modul is active.&lt;br /&gt;&lt;h2&gt;Limits&lt;/h2&gt;It cannot to work with temporary objects - because these objects does not exist when validation is processed.&lt;p&gt;It cannot to work with record variables, that has a value assigned via assign statement:&lt;pre&gt;create or replace function fx(out a int, out b int)&lt;br /&gt;as $$ ... $$&lt;br /&gt;&lt;br /&gt;DECLARE r record;&lt;br /&gt;BEGIN&lt;br /&gt;  r := fx();&lt;br /&gt;  RAISE NOTICE '% %', r.a, r.b; -- this is not possible with plpgsql_lint&lt;br /&gt;  ...&lt;br /&gt;&lt;/pre&gt;you should to fix function or disable lint:&lt;br /&gt;&lt;pre&gt;CREATE TYPE fx_type AS (a int, b int);&lt;br /&gt;CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;RETURNS fx_type AS $$ .. $$&lt;br /&gt;&lt;br /&gt;DECLARE r fx_type; -- don't use combination record and assign statement&lt;br /&gt;BEGIN&lt;br /&gt;  ..&lt;br /&gt;&lt;/pre&gt;Blocking lint of some function is possible via option&lt;br /&gt;&lt;pre&gt;test=# CREATE OR REPLACE FUNCTION public.foo(x integer)&lt;br /&gt; RETURNS integer&lt;br /&gt; LANGUAGE plpgsql&lt;br /&gt;AS $function$&lt;br /&gt;declare r record;&lt;br /&gt;begin&lt;br /&gt;  if x &amp;gt; 0 then&lt;br /&gt;    for r in select columnname from tablename&lt;br /&gt;    loop&lt;br /&gt;      x := x + r.columnname;&lt;br /&gt;    end loop;&lt;br /&gt;  end if; &lt;br /&gt;  return x;&lt;br /&gt;end;&lt;br /&gt;$function$ set plpgsql.disable_lint to on; -- don't use a validator on this function&lt;br /&gt;&lt;/pre&gt;This module is released under BSD license and source codes are downloadable from &lt;a href="http://kix.fsv.cvut.cz/%7Estehule/download/plpgsql_lint_2011-07-15.tgz"&gt;http://kix.fsv.cvut.cz/~stehule/download/plpgsql_lint_2011-07-15.tgz&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-3279866932803305697?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/3279866932803305697/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=3279866932803305697' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3279866932803305697'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3279866932803305697'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/07/plpgsql-lint.html' title='plpgsql lint'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-5510266638269076922</id><published>2011-05-27T04:31:00.000-07:00</published><updated>2011-05-27T04:31:58.208-07:00</updated><title type='text'>some news</title><content type='html'>HelloI uploaded a new version of Orafce - devel version, with support for 9.1 extension feature.&lt;pre&gt;&lt;br /&gt;test=# \dx&lt;br /&gt;                                         List of installed extensions&lt;br /&gt;  Name   │ Version │   Schema   │                                 Description                                  &lt;br /&gt;─────────┼─────────┼────────────┼──────────────────────────────────────────────────────────────────────────────&lt;br /&gt; orafce  │ 3.03    │ public     │ functions and operators that emulates a basic functions and packages from Or…&lt;br /&gt;         │         │            │…acle RDBMS&lt;br /&gt; plpgsql │ 1.0     │ pg_catalog │ PL/pgSQL procedural language&lt;br /&gt;(2 rows)&lt;br /&gt;&lt;/pre&gt;Other note - my last work on PL/PSM implementation was released under BSD licence - &lt;a href="http://archives.postgresql.org/pgsql-announce/2011-05/msg00012.php"&gt;http://archives.postgresql.org/pgsql-announce/2011-05/msg00012.php&lt;/a&gt;. Thank you very much CZ.NIC (my current employer).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-5510266638269076922?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/5510266638269076922/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=5510266638269076922' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/5510266638269076922'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/5510266638269076922'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/05/some-news.html' title='some news'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-2972310168594223020</id><published>2011-04-22T01:46:00.000-07:00</published><updated>2011-04-22T01:46:24.509-07:00</updated><title type='text'>PSM (zero) is completed, and needs you</title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;Hello,&lt;p/&gt;yesterday I finished work on prototype implementation of SQL/PSM language for PostgreSQL. Now, this language supports all necessary features:&lt;br /&gt;&lt;ul style="text-align: left;"&gt;&lt;li&gt;basic features - arrays, composite types, triggers&lt;/li&gt;&lt;li&gt; advanced features - table returned functions, IN/OUT variables&lt;/li&gt;&lt;li&gt;SQL/PSM specific features - warning, exceptions (general, SQLCODE) handlers, SIGNAL, RESIGNAL statements&lt;/li&gt;&lt;li&gt;some DB2 or MySQL features - multi assign statement, support for magic SQLSTATE and SQLCODE variables&lt;/li&gt;&lt;/ul&gt;some samples:&lt;pre&gt;&lt;br /&gt;create or replace function test74_2()&lt;br /&gt;returns text as $$&lt;br /&gt;begin atomic&lt;br /&gt;  declare not_found condition for sqlstate '03000';&lt;br /&gt;  declare undo handler for not_found&lt;br /&gt;    begin&lt;br /&gt;      declare xx, yy text;&lt;br /&gt;      get stacked diagnostics xx = condition_identifier, yy = returned_sqlstate;&lt;br /&gt;      return xx || ' Signal handled ' || yy;&lt;br /&gt;    end;&lt;br /&gt;  signal not_found;&lt;br /&gt;end;&lt;br /&gt;$$ language psm0;&lt;br /&gt;&lt;br /&gt;create or replace function test66(a int, out r int) as $$&lt;br /&gt;begin&lt;br /&gt;  declare continue handler for sqlstate '01002'&lt;br /&gt;    set r = r + 1;&lt;br /&gt;  declare continue handler for sqlstate '01003'&lt;br /&gt;    set r = r + 2;&lt;br /&gt;  set r = 0;&lt;br /&gt;x: while a &gt; 0 do&lt;br /&gt;     if a % 2 = 0 then&lt;br /&gt;       signal sqlstate '01002';&lt;br /&gt;     else&lt;br /&gt;       signal sqlstate '01003';&lt;br /&gt;     end if;&lt;br /&gt;     set a = a - 1;&lt;br /&gt;   end while;&lt;br /&gt;end;&lt;br /&gt;$$ language psm0;&lt;br /&gt;&lt;/pre&gt;&lt;p/&gt;This PL isn't designed as an replacement of PL/pgSQL. It is designed as second language with little bit different philosophy and goals.&lt;br /&gt;&lt;ul style="text-align: left;"&gt;&lt;li&gt;complete validation of embedded SQL in compile time,&lt;/li&gt;&lt;li&gt;result of embedded SQL is transformed to target type early&lt;/li&gt;&lt;/ul&gt;The main goal is a searching bugs inside embeded SQL early - usually in compile time. This goal changes a rules in languages. PSM is very static language. There are more tasks, that should be solved via dynamic SQL than in PL/pgSQL. On second hand, lot of PL/pgSQL runtime bugs can be detected in PSM in compile time.&lt;br /&gt;&lt;br /&gt;ToDo:&lt;br /&gt;&lt;ul style="text-align: left;"&gt;&lt;li&gt;Complete revision and review, more comments - &lt;i&gt;any volunteers&lt;/i&gt; ?&lt;/li&gt;&lt;li&gt;Performance optimizations,&lt;/li&gt;&lt;li&gt;Cleaning of error messages&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Source code is available from &lt;a href="https://github.com/okbob/plpsm0"&gt;github&lt;/a&gt;.&lt;br /&gt;&lt;/div&gt;&lt;b&gt;I invite any help!&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-2972310168594223020?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/2972310168594223020/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=2972310168594223020' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/2972310168594223020'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/2972310168594223020'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/04/psm-zero-is-completed-and-needs-you.html' title='PSM (zero) is completed, and needs you'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-7804439238620648501</id><published>2011-03-28T10:21:00.000-07:00</published><updated>2011-04-22T00:56:40.690-07:00</updated><title type='text'>MySQL div</title><content type='html'>I have to port one benchmark from MySQL. Test wasn't success because there was a div by zero. A result in MySQL is NULL, PostgreSQL raises a exception. I created new operator, that simulates MySQL's behave:&lt;pre&gt;&lt;br /&gt;postgres=# CREATE OR REPLACE FUNCTION my_div(double precision, double precision) &lt;br /&gt;           RETURNS double precision AS $$ &lt;br /&gt;             SELECT CASE WHEN $2 = 0 &lt;br /&gt;                              THEN NULL::double precision &lt;br /&gt;                              ELSE $1::double precision / $2::double precision END; &lt;br /&gt;           $$ LANGUAGE sql;&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;postgres=# select my_div(2,3);&lt;br /&gt;      my_div       &lt;br /&gt;-------------------&lt;br /&gt; 0.666666666666667&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# select my_div(2,0);&lt;br /&gt; my_div &lt;br /&gt;--------&lt;br /&gt;       &lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# CREATE OPERATOR // (PROCEDURE = my_div, LEFTARG = double precision, RIGHTARG= double precision);&lt;br /&gt;CREATE OPERATOR&lt;br /&gt;postgres=# SELECT 10//3;&lt;br /&gt;     ?column?     &lt;br /&gt;------------------&lt;br /&gt; 3.33333333333333&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# SELECT 10//0;&lt;br /&gt; ?column? &lt;br /&gt;----------&lt;br /&gt;         &lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-7804439238620648501?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/7804439238620648501/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=7804439238620648501' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/7804439238620648501'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/7804439238620648501'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/03/mysql-div.html' title='MySQL div'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-4706526798698567743</id><published>2011-03-27T11:12:00.000-07:00</published><updated>2011-03-27T11:12:04.156-07:00</updated><title type='text'>simple function for test if somebody is superuser</title><content type='html'>I needed a small functions:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION is_superuser(text)&lt;br /&gt;RETURNS boolean AS $$&lt;br /&gt;  SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = $1 AND rolsuper);&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;postgres=# SELECT is_superuser('tom');&lt;br /&gt; is_superuser &lt;br /&gt;--------------&lt;br /&gt; f&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-4706526798698567743?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/4706526798698567743/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=4706526798698567743' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4706526798698567743'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4706526798698567743'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/03/simple-function-for-test-if-somebody-is.html' title='simple function for test if somebody is superuser'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-3067732937413227993</id><published>2011-02-04T03:22:00.000-08:00</published><updated>2011-02-04T03:22:59.128-08:00</updated><title type='text'>SQL/PSM runtime</title><content type='html'>I enhanced a PLPSM executor engine to show a more informations when exception is raised. Now PLPSM has same and maybe better error processing than PL/pgSQL. - Almost all code coming from PL/pgSQL. Still, there are lot of work - mainly about exception trapping. &lt;pre&gt;&lt;br /&gt;pavel=# \sf fib&lt;br /&gt;CREATE OR REPLACE FUNCTION public.fib(n integer)&lt;br /&gt; RETURNS double precision&lt;br /&gt; LANGUAGE psm0&lt;br /&gt;AS $function$xxx:begin&lt;br /&gt;  declare f1 double precision default 0.0;&lt;br /&gt;  declare f2 double precision default 1.0;&lt;br /&gt;  declare cnt int default 1;&lt;br /&gt;  while cnt &lt;= n do&lt;br /&gt;    set (f2, f1, cnt) = (f1 + f2, f2, cnt + 1);&lt;br /&gt;  end while;&lt;br /&gt;  return f2;&lt;br /&gt;end;&lt;br /&gt;$function$&lt;br /&gt;pavel=# select fib(1000);&lt;br /&gt;          fib          &lt;br /&gt;───────────────────────&lt;br /&gt; 7.03303677114228e+208&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 16.680 ms&lt;br /&gt;pavel=# select fib(2000);&lt;br /&gt;ERROR:  value out of range: overflow&lt;br /&gt;CONTEXT:  SQL statement "SELECT ($2 + $3)::double precision,($3)::double precision,($4 + 1)::integer"&lt;br /&gt;PLPSM function "fib"  Oid 604602 line 6&lt;br /&gt;&lt;br /&gt;   4   declare cnt int default 1;&lt;br /&gt;   5   while cnt &lt;= n do&lt;br /&gt;   6     set (f2, f1, cnt) = (f1 + f2, f2, cnt + 1);&lt;br /&gt;   7   end while;&lt;br /&gt;   8   return f2;&lt;br /&gt;&lt;br /&gt;Local variables:&lt;br /&gt;&lt;br /&gt;   0 fib.$1  integer              = 2000&lt;br /&gt;   0 fib.n  integer              = 2000&lt;br /&gt;   1 xxx.f1  double precision     = 8.07763763215622e+307&lt;br /&gt;   2 xxx.f2  double precision     = 1.3069892237634e+308&lt;br /&gt;   3 xxx.cnt  integer              = 1476&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-3067732937413227993?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/3067732937413227993/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=3067732937413227993' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3067732937413227993'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3067732937413227993'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/02/sqlpsm-runtime.html' title='SQL/PSM runtime'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-8560529921281385535</id><published>2011-01-07T01:12:00.000-08:00</published><updated>2011-01-07T01:12:32.429-08:00</updated><title type='text'>The work on SQL/PSM compiler started</title><content type='html'>There are a basic code for plpsm language implementation. It's downloadable from  &lt;a href="https://github.com/okbob/plpsm0"&gt;github&lt;/a&gt;. Now only basic statements are supported - see a test.sql file. &lt;pre&gt;&lt;br /&gt;pavel=# create or replace function test12(a int)&lt;br /&gt;returns int as $$&lt;br /&gt;begin&lt;br /&gt;  declare b,c,d int default 0;&lt;br /&gt;  set b = a + 1, c = b + 1, d = c + 1;&lt;br /&gt;  return d;&lt;br /&gt;end;&lt;br /&gt;$$ language psm0;&lt;br /&gt;NOTICE:  &lt;br /&gt;   Datums: 4 variables &lt;br /&gt;   Size: 16 instruction&lt;br /&gt;&lt;br /&gt;    0  CopyParam 0, 0, size:4, byval:BYVAL&lt;br /&gt;    1  ExecExpr "SELECT (0)::integer",{}&lt;br /&gt;    2  SaveTo 1, size:4, byval:BYVAL&lt;br /&gt;    3  ExecExpr "SELECT (0)::integer",{}&lt;br /&gt;    4  SaveTo 2, size:4, byval:BYVAL&lt;br /&gt;    5  ExecExpr "SELECT (0)::integer",{}&lt;br /&gt;    6  SaveTo 3, size:4, byval:BYVAL&lt;br /&gt;    7  ExecExpr "SELECT ($1 + 1)::integer",{23,23,23,23}&lt;br /&gt;    8  SaveTo 1, size:4, byval:BYVAL&lt;br /&gt;    9  ExecExpr "SELECT ($2 + 1)::integer",{23,23,23,23}&lt;br /&gt;   10  SaveTo 2, size:4, byval:BYVAL&lt;br /&gt;   11  ExecExpr "SELECT ($3 + 1)::integer",{23,23,23,23}&lt;br /&gt;   12  SaveTo 3, size:4, byval:BYVAL&lt;br /&gt;   13  ExecExpr "SELECT ($4)::integer",{23,23,23,23}&lt;br /&gt;   14  Return size:4, byval:BYVAL&lt;br /&gt;   15  Done.&lt;br /&gt;&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-8560529921281385535?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/8560529921281385535/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=8560529921281385535' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/8560529921281385535'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/8560529921281385535'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/01/work-on-sqlpsm-compiler-started.html' title='The work on SQL/PSM compiler started'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-1674971349989068652</id><published>2011-01-01T08:27:00.000-08:00</published><updated>2011-01-01T08:27:21.337-08:00</updated><title type='text'>pltoolbox released</title><content type='html'>Hello&lt;br /&gt;I released new version of pst collection. This version was enhanced about a regress tests and &lt;a href="http://www.pgsql.cz/index.php/PL_toolbox_%28en%29"&gt;documentation&lt;/a&gt;. At the same time I renamed this package to &lt;a href="http://pgfoundry.org/frs/?group_id=1000457"&gt;&lt;i&gt;PL toolbox&lt;/i&gt;&lt;/a&gt;, because functions from this package are intended for using in stored procedures.&lt;br /&gt;Happy New Year&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-1674971349989068652?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/1674971349989068652/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=1674971349989068652' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1674971349989068652'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1674971349989068652'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2011/01/pltoolbox-released.html' title='pltoolbox released'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-3067654562704315536</id><published>2010-12-29T10:58:00.000-08:00</published><updated>2010-12-29T10:58:48.222-08:00</updated><title type='text'>Bitmapset for PL/pgSQL</title><content type='html'>PostgreSQL has a nice functions for operations over bitmapset. A bitmapset is set of positive integers. It should be smaller than array, and, what is important for me. it support very fast test if some value is in set or not. PostgreSQL internally use this functions, but there are not possibility to use it from SQL (PL/pgSQL). I wrote a wrapper for this functionality and moved it to PST collection - &lt;a href="http://pgfoundry.org/frs/download.php/2909/pstcoll-10-12-29.tgz"&gt;http://pgfoundry.org/frs/download.php/2909/pstcoll-10-12-29.tgz&lt;/a&gt;.so there is some preview:&lt;br /&gt;&lt;pre&gt;pavel=# select pg_column_size(pst.bitmapset '{1,2,3,4}');&lt;br /&gt; pg_column_size &lt;br /&gt;----------------&lt;br /&gt;              8&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select pg_column_size(array[1,2,3,4]);&lt;br /&gt; pg_column_size &lt;br /&gt;----------------&lt;br /&gt;             40&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select pst.add_members('{}', 1, 2, 4,8);&lt;br /&gt; add_members &lt;br /&gt;-------------&lt;br /&gt; {1,2,4,8}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select pst.is_member(pst.add_members('{}', 1, 2, 4,8), 8);&lt;br /&gt; is_member &lt;br /&gt;-----------&lt;br /&gt; t&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select pst.bitmapset_union('{1,2,3}','{6,2,9}');&lt;br /&gt; bitmapset_union &lt;br /&gt;-----------------&lt;br /&gt; {1,2,3,6,9}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;Without bitmapsets we have to use a arrays. But bitmaps are better for storing some flags - it's more adequate tool. &lt;br /&gt;&lt;pre&gt;pavel=# select count(*) from omega;&lt;br /&gt;  count  &lt;br /&gt;---------&lt;br /&gt; 1010000&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select bitmapset_collect(a) from omega;&lt;br /&gt;    bitmapset_collect     &lt;br /&gt;--------------------------&lt;br /&gt; {0,1,2,3,4,5,6,7,8,9,10}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 558.667 ms&lt;br /&gt;&lt;br /&gt;pavel=# select array_agg(distinct a) from omega;&lt;br /&gt;        array_agg         &lt;br /&gt;--------------------------&lt;br /&gt; {0,1,2,3,4,5,6,7,8,9,10}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 3859.567 ms&lt;br /&gt;&lt;/pre&gt;Using a bitmapset is about 7x faster.&lt;br /&gt;&lt;pre&gt;pavel=# select del_members('{2,3,4,5,1}',2,3);&lt;br /&gt; del_members &lt;br /&gt;-------------&lt;br /&gt; {1,4,5}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select bitmapset_is_subset('{1,2,3}','{1,3}');&lt;br /&gt; bitmapset_is_subset &lt;br /&gt;---------------------&lt;br /&gt; f&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select bitmapset_overlap('{1,2,3}','{1,3}');&lt;br /&gt; bitmapset_overlap &lt;br /&gt;-------------------&lt;br /&gt; t&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select bitmapset_difference('{1,2,3}','{1,3}');&lt;br /&gt; bitmapset_difference &lt;br /&gt;----------------------&lt;br /&gt; {2}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;Probably this simple wrapper can be enhanced - some GiST or GIN index can be nice. The bitmapset is limited only on integers. There is not simple way to use it together with enums for example.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-3067654562704315536?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/3067654562704315536/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=3067654562704315536' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3067654562704315536'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3067654562704315536'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/12/bitmapset-for-plpgsql.html' title='Bitmapset for PL/pgSQL'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-6626755329414582535</id><published>2010-12-10T07:39:00.000-08:00</published><updated>2010-12-10T07:43:55.932-08:00</updated><title type='text'>Iteration over record in PL/pgSQL</title><content type='html'>Hello &lt;p&gt;A iteration over record in PL/pgSQL is old well known problem. When we try to write a general triggers in PL/pgSQL we can find a break, because we are not able to iterate over record. There was a some workarounds, but these solutions are simply slow and complex. So I wrote a set of functions that can helps.  &lt;p&gt;First functions is &lt;i&gt;record_expand&lt;/i&gt;. This function is similar to &lt;i&gt;unnest&lt;/i&gt; function, but related object is record: &lt;pre&gt; &lt;br /&gt;postgres=# select * from pst.record_expand(row('10',null,'Ahoj', current_date)); &lt;br /&gt; name |   value    |   typ    &lt;br /&gt;------+------------+--------- &lt;br /&gt; f1   | 10         | unknown &lt;br /&gt; f2   |            | unknown &lt;br /&gt; f3   | Ahoj       | unknown &lt;br /&gt; f4   | 2010-12-10 | date &lt;br /&gt;(4 rows) &lt;br /&gt;&lt;/pre&gt;Now isn't problem to write general trigger for detecting a changed colums: &lt;pre&gt;&lt;br /&gt;CREATE TABLE foo(a int, b int, c text, d int); &lt;br /&gt; &lt;br /&gt;CREATE OR REPLACE FUNCTIO update_trg_func() &lt;br /&gt;RETURNS trigger as $$ &lt;br /&gt;DECLARE r record; &lt;br /&gt;BEGIN &lt;br /&gt;  FOR r IN SELECT n.name, o.value as oldval, n.value as newval &lt;br /&gt;              FROM pst.record_expand(new) n, &lt;br /&gt;                   pst.record_expand(old) o &lt;br /&gt;             WHERE n.name = o.name &lt;br /&gt;               AND n.value IS DISTINCT FROM o.value &lt;br /&gt;  LOOP  &lt;br /&gt;    RAISE NOTICE '% % %', r.name, r.oldval, r.newval; &lt;br /&gt;END LOOP; &lt;br /&gt;RETURN NULL; &lt;br /&gt;END; &lt;br /&gt;$$ LANGUAGE plpgsql; &lt;br /&gt; &lt;br /&gt;CREATE TRIGGER update_trg &lt;br /&gt;  AFTER UPDATE ON foo &lt;br /&gt;  FOR EACH ROW EXECUTE PROCEDURE update_trg_func(); &lt;br /&gt; &lt;br /&gt;postgres=# UPDATE foo SET c = 'Pavel', a = 40; &lt;br /&gt;NOTICE:  a 30 40 &lt;br /&gt;NOTICE:  c omega Pavel &lt;br /&gt;UPDATE 1 &lt;br /&gt;&lt;/pre&gt;Next function allows update of any dynamic record. This function is &lt;i&gt;record_set_fields&lt;/i&gt;: It's a variadic functions - you can enter a fields and values to change: &lt;pre&gt;&lt;br /&gt;postgres=# SELECT pst.record_set_fields(row(0,0,'',0)::foo, 'd', 100, 'c', 'Hello');&lt;br /&gt;  record_set_fields&lt;br /&gt;-------------------&lt;br /&gt;  (0,0,Hello,100)&lt;br /&gt;&lt;/pre&gt;It can be used for dynamic initialization of wide tables - for some OLAP purposes: &lt;pre&gt;  &lt;br /&gt; CREATE TABLE t(a0 int, a1 int, a2 int, a3 int, a4 int, a5 int); &lt;br /&gt; &lt;br /&gt;-- set all null fields &lt;i&gt;ax&lt;/i&gt; on value -1000 &lt;br /&gt; &lt;br /&gt;CREATE OR REPLACE FUNCTION insert_trg_func()&lt;br /&gt;RETURNS trigger as $$&lt;br /&gt;DECLARE name text;&lt;br /&gt;BEGIN&lt;br /&gt;   FOR name IN SELECT x.name &lt;br /&gt;                  FROM pst.record_expand(new) x &lt;br /&gt;                 WHERE x.value IS NULL AND x.name LIKE 'a%'&lt;br /&gt;   LOOP  &lt;br /&gt;     new = pst.record_set_fields(new, name, -1000);&lt;br /&gt;   END LOOP; &lt;br /&gt;   RETURN new;&lt;br /&gt;END; &lt;br /&gt;$$ LANGUAGE plpgsql;  &lt;br /&gt;&lt;br /&gt;CREATE TRIGGER insert_trg &lt;br /&gt;   BEFORE INSERT ON t &lt;br /&gt;   FOR EACH ROW EXECUTE PROCEDURE insert_trg_func(); &lt;br /&gt; &lt;br /&gt;postgres=# INSERT INTO t(a3,a5) VALUES(100,100);&lt;br /&gt;INSERT 0 1&lt;br /&gt;postgres=# SELECT * FROM t;&lt;br /&gt;  a0   |  a1   |  a2   | a3  |  a4   | a5  &lt;br /&gt;-------+-------+-------+-----+-------+-----&lt;br /&gt; -1000 | -1000 | -1000 | 100 | -1000 | 100&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;Last function is &lt;i&gt;record_get_field&lt;/i&gt;. Its returns a value of entered field.&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION insert_trg_func() &lt;br /&gt;RETURNS TRIGGER AS $$ &lt;br /&gt;DECLARE name text; &lt;br /&gt;BEGIN&lt;br /&gt;  FOR i IN 0..5 LOOP&lt;br /&gt;    IF pst.record_get_field(new, 'a'||i) IS NULL THEN&lt;br /&gt;      new := pst.record_set_fields(new, 'a'||i, -1000); &lt;br /&gt;    END IF; &lt;br /&gt;  END LOOP; r&lt;br /&gt;  RETURN new; &lt;br /&gt;END; &lt;br /&gt;$$ LANGUAGE plpgsql; &lt;br /&gt;&lt;/pre&gt;These package is available from pgfoundry &lt;a href="http://pgfoundry.org/frs/shownotes.php?release_id=1749 "&gt;http://pgfoundry.org/frs/shownotes.php?release_id=1749&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-6626755329414582535?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/6626755329414582535/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=6626755329414582535' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/6626755329414582535'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/6626755329414582535'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/12/iteration-over-record-in-plpgsql.html' title='Iteration over record in PL/pgSQL'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-7581702906130645646</id><published>2010-11-26T05:56:00.000-08:00</published><updated>2010-11-26T21:20:34.072-08:00</updated><title type='text'>new version of pst collection is relased</title><content type='html'>Hello&lt;br /&gt;I released a updated version of pst collection. What is a pst collection? It is a collection of usable functions for PostgreSQL valuable for my work. Some from these function will be in next version of PostgreSQL, others not. You can download pst collection from &lt;a href="http://pgfoundry.org/frs/download.php/2890/pstcoll-10-11-27.tgz"&gt;http://pgfoundry.org/frs/download.php/2890/pstcoll-10-11-27.tgz&lt;/a&gt; pgfoundry.What you can find there: formatting functions - &lt;b&gt;sprintf&lt;/b&gt;, format (with same behave like buildin function from 9.1), left, right, reverse, concat, concat_ws, concat_js, concat_sql. Parse functions - chars_to_array, diff_string, lc_substring. Date function - next_day and last_day. Utility function - counter.Some examples: &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;pavel=# set search_path = 'pst';&lt;br /&gt;SET&lt;br /&gt;Time: 0.411 ms&lt;br /&gt;pavel=# select sprintf('|%10s|', 'hello');&lt;br /&gt;   sprintf    &lt;br /&gt;──────────────&lt;br /&gt; |     hello|&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select sprintf('|%-10s|', 'hello');&lt;br /&gt;   sprintf    &lt;br /&gt;──────────────&lt;br /&gt; |hello     |&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select format('INSERT INTO %I VALUES(%L)', 'tabname', 'some value');&lt;br /&gt;                  format                  &lt;br /&gt;──────────────────────────────────────────&lt;br /&gt; INSERT INTO tabname VALUES('some value')&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select concat_js(10,22,'weqwe',true, current_date);&lt;br /&gt;            concat_js            &lt;br /&gt;─────────────────────────────────&lt;br /&gt; 10,22,"weqwe",true,"2010-11-26"&lt;br /&gt;&lt;br /&gt;pavel=# select chars_to_array('příliš žlutý kůň');&lt;br /&gt;            chars_to_array             &lt;br /&gt;───────────────────────────────────────&lt;br /&gt; {p,ř,í,l,i,š," ",ž,l,u,t,ý," ",k,ů,ň}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select diff_string('Ahoj','Ahojky');&lt;br /&gt;  diff_string   &lt;br /&gt;────────────────&lt;br /&gt; Ahoj&amp;lt;ins&amp;gt;ky&amp;lt;/&amp;gt;&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select diff_string('Ahojky','Ahoj');&lt;br /&gt;  diff_string   &lt;br /&gt;────────────────&lt;br /&gt; Ahoj&amp;lt;del&amp;gt;ky&amp;lt;/&amp;gt;&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# select diff_string('red dog and cat','green dog and horse');&lt;br /&gt;                           diff_string                           &lt;br /&gt;─────────────────────────────────────────────────────────────────&lt;br /&gt; &amp;lt;ins&amp;gt;g&amp;lt;/&amp;gt;re&amp;lt;del&amp;gt;d&amp;lt;/&amp;gt;&amp;lt;ins&amp;gt;en&amp;lt;/&amp;gt; dog and &amp;lt;del&amp;gt;cat&amp;lt;/&amp;gt;&amp;lt;ins&amp;gt;horse&amp;lt;/&amp;gt;&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;pavel=# create table target(a int);&lt;br /&gt;CREATE TABLE&lt;br /&gt;Time: 53.819 ms&lt;br /&gt;pavel=# insert into target select counter(i, 5000, true) from generate_series(1,20000) g(i);&lt;br /&gt;NOTICE:  processed 5000 rows, current value is '5000'&lt;br /&gt;NOTICE:  processed 10000 rows, current value is '10000'&lt;br /&gt;NOTICE:  processed 15000 rows, current value is '15000'&lt;br /&gt;NOTICE:  processed 20000 rows, current value is '20000'&lt;br /&gt;INSERT 0 20000&lt;br /&gt;Time: 1170.040 ms&lt;br /&gt;&lt;/pre&gt;I hope so these function will by useful for you too.&lt;br /&gt;&lt;br /&gt;This package should to work on PostgreSQL 8.4, 9.0 &lt;br /&gt;Pavel&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-7581702906130645646?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/7581702906130645646/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=7581702906130645646' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/7581702906130645646'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/7581702906130645646'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/11/new-version-of-pst-collection-is.html' title='new version of pst collection is relased'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-6049305845526043129</id><published>2010-10-28T03:03:00.000-07:00</published><updated>2010-10-28T03:03:42.762-07:00</updated><title type='text'>Simple counter for batch operations</title><content type='html'>HelloI am working with large transformations now. It is some like:&lt;pre&gt;&lt;br /&gt;CREATE TABLE source_table(..);&lt;br /&gt;CREATE TABLE destination_table(..);&lt;br /&gt;&lt;br /&gt;/* transformation function */&lt;br /&gt;CREATE OR REPLACE FUNCTION to_destination_table(source_table) RETURNS RECORD AS $$ ...&lt;br /&gt;&lt;br /&gt;/* transformation */&lt;br /&gt;INSERT INTO destination_table&lt;br /&gt;   SELECT (r).*&lt;br /&gt;  FROM (SELECT to_destionation_table(_source) r&lt;br /&gt;           FROM source _source) x&lt;br /&gt;&lt;/pre&gt;The batch operation is relative long. so I had to wrote simple counter. It is simple function, that counts a call and returns input value without any change. My function has a three parameters, first is a value for forwarding, second is a frequency of notification, and third parameter specifies a printing input value:&lt;pre&gt;&lt;br /&gt;nic=# select sum(counter(v, 20, false)) from generate_series(1,100) g(v);&lt;br /&gt;NOTICE:  processed 20 rows&lt;br /&gt;NOTICE:  processed 40 rows&lt;br /&gt;NOTICE:  processed 60 rows&lt;br /&gt;NOTICE:  processed 80 rows&lt;br /&gt;NOTICE:  processed 100 rows&lt;br /&gt; sum  &lt;br /&gt;------&lt;br /&gt; 5050&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;nic=# select sum(counter(v, 20, true)) from generate_series(1,100) g(v);&lt;br /&gt;NOTICE:  processed 20 rows, current value is '20'&lt;br /&gt;NOTICE:  processed 40 rows, current value is '40'&lt;br /&gt;NOTICE:  processed 60 rows, current value is '60'&lt;br /&gt;NOTICE:  processed 80 rows, current value is '80'&lt;br /&gt;NOTICE:  processed 100 rows, current value is '100'&lt;br /&gt; sum  &lt;br /&gt;------&lt;br /&gt; 5050&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;This function is very simple - see source code:&lt;pre&gt;&lt;br /&gt;#include "funcapi.h"&lt;br /&gt;#include "utils/lsyscache.h"&lt;br /&gt;&lt;br /&gt;PG_FUNCTION_INFO_V1(pst_counter);&lt;br /&gt;&lt;br /&gt;Datum pst_counter(PG_FUNCTION_ARGS);&lt;br /&gt;&lt;br /&gt;typedef struct&lt;br /&gt;{&lt;br /&gt; long int iterations;&lt;br /&gt; int freq;&lt;br /&gt; Oid  typoutput;&lt;br /&gt;} counter_cache;&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt; * raise notice every n call,&lt;br /&gt; * returns input without change&lt;br /&gt; */&lt;br /&gt;Datum&lt;br /&gt;pst_counter(PG_FUNCTION_ARGS)&lt;br /&gt;{&lt;br /&gt; Datum value = PG_GETARG_DATUM(0);&lt;br /&gt; counter_cache *ptr = (counter_cache *) fcinfo-&gt;flinfo-&gt;fn_extra;&lt;br /&gt;&lt;br /&gt; if (ptr == NULL)&lt;br /&gt; {&lt;br /&gt;&lt;br /&gt;  fcinfo-&gt;flinfo-&gt;fn_extra = MemoryContextAlloc(fcinfo-&gt;flinfo-&gt;fn_mcxt,&lt;br /&gt;          sizeof(counter_cache));&lt;br /&gt;  ptr = (counter_cache *) fcinfo-&gt;flinfo-&gt;fn_extra;&lt;br /&gt;  ptr-&gt;iterations = 0;&lt;br /&gt;  ptr-&gt;typoutput = InvalidOid;&lt;br /&gt;&lt;br /&gt;  if (PG_ARGISNULL(1))&lt;br /&gt;   elog(ERROR, "second parameter (output frequency) must not be NULL");&lt;br /&gt;&lt;br /&gt;  ptr-&gt;freq = PG_GETARG_INT32(1);&lt;br /&gt;&lt;br /&gt;  if (!PG_ARGISNULL(2) &amp;&amp; PG_GETARG_BOOL(2))&lt;br /&gt;  {&lt;br /&gt;   Oid valtype;&lt;br /&gt;   Oid typoutput;&lt;br /&gt;   bool typIsVarlena;&lt;br /&gt;&lt;br /&gt;   valtype = get_fn_expr_argtype(fcinfo-&gt;flinfo, 0);&lt;br /&gt;   getTypeOutputInfo(valtype, &amp;typoutput, &amp;typIsVarlena);&lt;br /&gt;   ptr-&gt;typoutput = typoutput;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; if (++ptr-&gt;iterations % ptr-&gt;freq == 0)&lt;br /&gt; {&lt;br /&gt;  if (!OidIsValid(ptr-&gt;typoutput))&lt;br /&gt;  {&lt;br /&gt;   elog(NOTICE, "processed %ld rows", ptr-&gt;iterations);&lt;br /&gt;  }&lt;br /&gt;  else&lt;br /&gt;  {&lt;br /&gt;   /* show a processed row, when it's requested */&lt;br /&gt;   if (PG_ARGISNULL(0))&lt;br /&gt;    elog(NOTICE, "processed %ld rows, current value is null", ptr-&gt;iterations);&lt;br /&gt;   else&lt;br /&gt;   {&lt;br /&gt;    elog(NOTICE, "processed %ld rows, current value is '%s'", ptr-&gt;iterations,&lt;br /&gt;              OidOutputFunctionCall(ptr-&gt;typoutput, value));&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt;&lt;br /&gt; PG_RETURN_DATUM(value);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION counter(anyelement, int, bool)&lt;br /&gt;RETURNS anyelement&lt;br /&gt;AS 'MODULE_PATHNAME','pst_counter' LANGUAGE C;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;And usage isn't complex too:&lt;pre&gt;&lt;br /&gt;/* transformation */&lt;br /&gt;INSERT INTO destination_table&lt;br /&gt;   SELECT (r).*&lt;br /&gt;  FROM (SELECT counter(to_destionation_table(_source), 1000, true) r&lt;br /&gt;           FROM source _source) x&lt;br /&gt;&lt;/pre&gt;real output:&lt;pre&gt;&lt;br /&gt;psql84:convert.sql:183: NOTICE:  processed 1230000 rows, current value is '(959674199,"2010-10-25 23:50:29.404568","2010-10-25 23:50:29.491842",,3,501,190000029,XXXXXX,f,9,21)'&lt;br /&gt;psql84:convert.sql:183: NOTICE:  processed 1240000 rows, current value is '(959473959,"2010-10-25 19:20:52.498152","2010-10-25 19:20:52.564384",,3,501,189854563,XXXXXX,f,9,23)'&lt;br /&gt;psql84:convert.sql:183: NOTICE:  processed 1250000 rows, current value is '(958884965,"2010-10-25 08:53:30.026877","2010-10-25 08:53:30.106039",,3,101,189849045,XXXXXX,f,13,48)'&lt;br /&gt;psql84:convert.sql:183: NOTICE:  processed 1260000 rows, current value is '(959483959,"2010-10-25 19:32:02.136491","2010-10-25 19:32:04.070922",,3,500,189969079,XXXXXX,f,9,48)'&lt;br /&gt;psql84:convert.sql:183: NOTICE:  processed 1270000 rows, current value is '(959488959,"2010-10-25 19:36:40.691078","2010-10-25 19:36:41.155615",,3,500,189037026,,f,9,)'&lt;br /&gt;psql84:convert.sql:183: NOTICE:  processed 1280000 rows, current value is '(959493959,"2010-10-25 19:41:59.21508","2010-10-25 19:41:59.244398",,3,101,189971167,XXXXXX,f,13,42)'&lt;br /&gt;psql84:convert.sql:183: NOTICE:  processed 1290000 rows, current value is '(959498959,"2010-10-25 19:49:22.494294","2010-10-25 19:49:22.516124",,3,1010,189971599,XXXXXX,f,9,53)'&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-6049305845526043129?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/6049305845526043129/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=6049305845526043129' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/6049305845526043129'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/6049305845526043129'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/10/simple-counter-for-batch-operations.html' title='Simple counter for batch operations'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-1664486626404516443</id><published>2010-10-21T01:02:00.000-07:00</published><updated>2010-10-21T01:04:29.892-07:00</updated><title type='text'>diff string for PostgreSQL</title><content type='html'>Hello,I am working on migration a large dataset from archive to a new format. This work is joined with searching a small differences between original and transformed data. For this work I implemented a small library for PostgreSQL. It contains a two functions: diff_string and lc_substring.These functions should to support multibyte encoding. I hope, so this can be useful for someone.&lt;pre&gt;&lt;br /&gt;postgres=# select lc_substring('Hello World','ello');&lt;br /&gt; lc_substring &lt;br /&gt;──────────────&lt;br /&gt; ello&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# select diff_string('Hello World','ello');&lt;br /&gt;         diff_string         &lt;br /&gt;─────────────────────────────&lt;br /&gt; &amp;lt;del&amp;gt;H&amp;lt;/&amp;gt;ello&amp;lt;del&amp;gt; World&amp;lt;/&amp;gt;&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;This library exists on pgFoundry&lt;a href=" http://pgfoundry.org/frs/?group_id=1000457"&gt; http://pgfoundry.org/frs/?group_id=1000457&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-1664486626404516443?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/1664486626404516443/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=1664486626404516443' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1664486626404516443'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1664486626404516443'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/10/diff-string-for-postgresql.html' title='diff string for PostgreSQL'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-2505071425360347421</id><published>2010-10-13T23:10:00.000-07:00</published><updated>2010-10-14T00:33:45.025-07:00</updated><title type='text'>use a plpgsql well</title><content type='html'>Hello&lt;br /&gt;&lt;br /&gt;I found a following code on net. It's good example of bad code (written by PHP coder):&lt;br /&gt;&lt;pre&gt;01.CREATE OR REPLACE FUNCTION delete_data(IN data integer[]) RETURNS integer AS&lt;br /&gt;02.--DECLARATION OF FUNCTION&lt;br /&gt;03.$$&lt;br /&gt;04.DECLARE&lt;br /&gt;05.--DECLARATION OF LOCAL VARIABLES&lt;br /&gt;06.sql varchar;&lt;br /&gt;07.i integer;&lt;br /&gt;08.BEGIN&lt;br /&gt;09.--START OF THE PROCEDURE&lt;br /&gt;10.i := 0;&lt;br /&gt;11.loop&lt;br /&gt;12.--LOOP THROUGH AOUR ARRAY OF DATA&lt;br /&gt;13.if (data[i][1] IS NULL) then&lt;br /&gt;14.return 1;&lt;br /&gt;15.--IF WE LOOPED THROUG ALL OF ARRAY, IT'S DONE&lt;br /&gt;16.exit;&lt;br /&gt;17.end if;&lt;br /&gt;18.sql := 'DELETE FROM data_table WHERE data_table.id='||data[i];&lt;br /&gt;19.--THE SQL ITSELF, COMBINED WITH VARIABLE&lt;br /&gt;20.execute(sql);&lt;br /&gt;21.--THE EXECUTE FUNCTION BUILT IN PL/pgSQL, EXECUTES VARCHAR SQLs&lt;br /&gt;22.i := i + 1;&lt;br /&gt;23.end loop;&lt;br /&gt;24.--END OF THE PROCEDURE&lt;br /&gt;25. &lt;br /&gt;26.END;&lt;br /&gt;27.--END OF FUNCTION'S LOGIC&lt;br /&gt;28. &lt;br /&gt;29.$$&lt;br /&gt;30.LANGUAGE 'plpgsql';&lt;br /&gt;31.--DECLARATION OF LANGUAGE USED&lt;br /&gt;&lt;/pre&gt;what is wrong:&lt;br /&gt;&lt;ul&gt;&lt;li&gt; using a dynamic SQL - really, there cannot be a dynamic SQL &lt;/li&gt;&lt;li&gt; this code is cryptographic - It needs big fantasy to see iteration over array in this code&lt;/li&gt;&lt;li&gt; inconsistency - keywords, useless braces in IF statement (PL/pgSQL isn't PHP)&lt;/li&gt;&lt;li&gt; useless return value - not handled case, when input array is empty&lt;/li&gt;&lt;/ul&gt;why the coder didn't write just?:&lt;br /&gt;&lt;pre&gt;01.CREATE OR REPLACE FUNCTION delete_data(IN data integer[]) &lt;br /&gt;02.RETURNS void AS $$&lt;br /&gt;03.BEGIN&lt;br /&gt;04.  FOR i IN array_lower(data,1)..array_upper(data,1)&lt;br /&gt;05.  LOOP&lt;br /&gt;06.    DELETE FROM data_table WHERE data_table.id = data[i];&lt;br /&gt;07.  END LOOP;&lt;br /&gt;08.END;&lt;br /&gt;09.$$ LANGUAGE plpgsql;&amp;nbsp;&lt;/pre&gt;or better (if you like procedures and dislike SQL in your app. code) (inpiration by Thermic):&lt;pre&gt;&lt;br /&gt;  CREATE OR REPLACE FUNCTION delete_data(data integer[])&lt;br /&gt;  RETURNS void AS $$&lt;br /&gt;    DELETE FROM data_table WHERE data_table.id = ANY($1)&lt;br /&gt;  $$ LANGUAGE sql;&lt;br /&gt;&lt;/pre&gt;note: you can use a plpgsql or sql. Both environments has a few advantages and disadvantages. For using in web environments I little bit prefer SQL language (when it's called once per session, when size of input array can be significantly different).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-2505071425360347421?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/2505071425360347421/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=2505071425360347421' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/2505071425360347421'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/2505071425360347421'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/10/use-plpgsql-well.html' title='use a plpgsql well'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-1299425503401439708</id><published>2010-09-30T09:09:00.000-07:00</published><updated>2010-09-30T09:09:01.770-07:00</updated><title type='text'>PostgreSQL 9.1 - iteration over array</title><content type='html'>I finished patch that enable iteration over array. I hope so new syntax can helps to better readability. There are a few samples from regress tests:&lt;pre&gt;&lt;br /&gt;create or replace function fora_test()&lt;br /&gt;returns int as $$&lt;br /&gt;declare x int; s int = 0;&lt;br /&gt;begin&lt;br /&gt;  for x in array array[1,2,3,4,5,6,7,8,9,10]&lt;br /&gt;  loop&lt;br /&gt;    s := s + x;&lt;br /&gt;  end loop;&lt;br /&gt;  return s;&lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;&lt;br /&gt;select fora_test();&lt;br /&gt;&lt;br /&gt;create or replace function subscripts(anyarray, int)&lt;br /&gt;returns int[] as $$&lt;br /&gt;select array(select generate_subscripts($1,$2));&lt;br /&gt;$$ language sql;&lt;br /&gt;&lt;br /&gt;create or replace function fora_test()&lt;br /&gt;returns int as $$&lt;br /&gt;declare x int; s int = 0;&lt;br /&gt;   a int[] := array[1,2,3,4,5,6,7,8,9,10];&lt;br /&gt;begin&lt;br /&gt;  for x in array subscripts(a, 1)&lt;br /&gt;  loop&lt;br /&gt;    s := s + a[x];&lt;br /&gt;  end loop;&lt;br /&gt;  return s;&lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;&lt;br /&gt;create or replace function fora_test()&lt;br /&gt;returns int as $$&lt;br /&gt;declare x record;&lt;br /&gt;   a fora_point[] := array[(1,2),(3,4),(5,6)];&lt;br /&gt;begin&lt;br /&gt;  for x in array a&lt;br /&gt;  loop&lt;br /&gt;    raise notice 'point=%', x.x;&lt;br /&gt;  end loop;&lt;br /&gt;  return 0;&lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;&lt;br /&gt;select fora_test();&lt;br /&gt;&lt;br /&gt;create or replace function fora_test()&lt;br /&gt;returns int as $$&lt;br /&gt;declare x int; y int;&lt;br /&gt;   a fora_point[] := array[(1,2),(3,4),(5,6)];&lt;br /&gt;begin&lt;br /&gt;  for x, y in array a&lt;br /&gt;  loop&lt;br /&gt;    raise notice 'point=%,%', x, y;&lt;br /&gt;  end loop;&lt;br /&gt;  return 0;&lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-1299425503401439708?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/1299425503401439708/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=1299425503401439708' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1299425503401439708'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1299425503401439708'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/09/postgresql-91-iteration-over-array.html' title='PostgreSQL 9.1 - iteration over array'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-6689971337289081829</id><published>2010-08-29T09:39:00.000-07:00</published><updated>2010-08-29T23:38:31.558-07:00</updated><title type='text'>performance issue of SQL functions</title><content type='html'>I though so sql functions is always faster than plpgsql function. But it's not true. Only when sql function is inlined, then function is faster, else plpgsql can be faster. Now I know, so sql functions where parameter is twice and more used are not inlined - because it means double parameter evaluation.you can see:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION is_empty(text) &lt;br /&gt;RETURNS bool AS $$ &lt;br /&gt;SELECT $1 IS NULL OR $1 = '' &lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;/pre&gt;This function returns true when parameter is empty string or is null. The first parameter is used twice and then the body of this function isn't inlined. &lt;pre&gt;&lt;br /&gt;postgres=# EXPLAIN VERBOSE SELECT count(is_empty(CASE WHEN random() &lt; 0.5 THEN NULL ELSE 'x' END)) FROM generate_series(1,100000);&lt;br /&gt;                                                 QUERY PLAN                                                 &lt;br /&gt;------------------------------------------------------------------------------------------------------------&lt;br /&gt; Aggregate  (cost=12.50..12.77 rows=1 width=0)&lt;br /&gt;   Output: count(is_empty(CASE WHEN (random() &lt; 0.5::double precision) THEN NULL::text ELSE 'x'::text END))&lt;br /&gt;   -&gt;  Function Scan on pg_catalog.generate_series  (cost=0.00..10.00 rows=1000 width=0)&lt;br /&gt;         Output: generate_series&lt;br /&gt;         Function Call: generate_series(1, 100000)&lt;br /&gt;(5 rows)&lt;br /&gt;&lt;/pre&gt;The execution time is 458ms. When I modify the body for one time paremeter using:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION is_empty(text) &lt;br /&gt;RETURNS bool AS $$&lt;br /&gt;SELECT COALESCE($1,'') = '' &lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;/pre&gt;Then function is inlined:&lt;pre&gt;&lt;br /&gt;postgres=# EXPLAIN VERBOSE SELECT count(is_empty(CASE WHEN random() &lt; 0.5 THEN NULL ELSE 'x' END)) FROM generate_series(1,100000);&lt;br /&gt;                                                            QUERY PLAN                                                             &lt;br /&gt;-----------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt; Aggregate  (cost=12.50..12.52 rows=1 width=0)&lt;br /&gt;   Output: count((COALESCE(CASE WHEN (random() &lt; 0.5::double precision) THEN NULL::text ELSE 'x'::text END, ''::text) = ''::text))&lt;br /&gt;   -&gt;  Function Scan on pg_catalog.generate_series  (cost=0.00..10.00 rows=1000 width=0)&lt;br /&gt;         Output: generate_series&lt;br /&gt;         Function Call: generate_series(1, 100000)&lt;br /&gt;(5 rows)&lt;br /&gt;&lt;/pre&gt;and execution time is only 68ms (5 x faster than not inlined function).&lt;b&gt;Attention: this note is valid for some more complex parameters - like volatile functions. The life isn't simple - for basic parameters (like columns or simple expression), the most simple SQL function (in my article function with OR operator and twice used parameter) can be better - wirking better with planner.&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-6689971337289081829?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/6689971337289081829/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=6689971337289081829' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/6689971337289081829'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/6689971337289081829'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/08/performance-issue-of-sql-functions.html' title='performance issue of SQL functions'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-2195599527916422380</id><published>2010-08-17T04:59:00.000-07:00</published><updated>2010-08-17T04:59:37.441-07:00</updated><title type='text'>divide a table into a partitions</title><content type='html'>HelloI had to verify a speed of two methods of dividing data to partion. First method is based on dynamic SQL, second is based on cursor scan and prepared statements. I was surprised - both variants has a same speed.&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION public.foo()&lt;br /&gt; RETURNS void&lt;br /&gt; LANGUAGE plpgsql&lt;br /&gt;AS $function$&lt;br /&gt;BEGIN&lt;br /&gt;  FOR i IN 0..9&lt;br /&gt;  LOOP&lt;br /&gt;    RAISE NOTICE '***** % *****', i;&lt;br /&gt;    EXECUTE 'TRUNCATE TABLE ' || ('x' || i)::regclass;&lt;br /&gt;    EXECUTE 'INSERT INTO ' || ('x' || i)::regclass ||&lt;br /&gt;                ' SELECT * FROM x WHERE a &gt;= $1 AND a &lt; $1 + 100000'&lt;br /&gt;       USING i * 100000;&lt;br /&gt;  END LOOP;&lt;br /&gt;  RETURN;&lt;br /&gt;END;&lt;br /&gt;$function$&lt;br /&gt;&lt;/pre&gt;This variant is shorter, but needs a active index on source table.&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION public.foo1()&lt;br /&gt; RETURNS void&lt;br /&gt; LANGUAGE plpgsql&lt;br /&gt;AS $function$&lt;br /&gt;DECLARE&lt;br /&gt;  c CURSOR FOR SELECT * FROM x;&lt;br /&gt;  r RECORD;&lt;br /&gt;BEGIN&lt;br /&gt;  TRUNCATE TABLE x0;&lt;br /&gt;  TRUNCATE TABLE x1;&lt;br /&gt;  TRUNCATE TABLE x2;&lt;br /&gt;  TRUNCATE TABLE x3;&lt;br /&gt;  TRUNCATE TABLE x4;&lt;br /&gt;  TRUNCATE TABLE x5;&lt;br /&gt;  TRUNCATE TABLE x6;&lt;br /&gt;  TRUNCATE TABLE x7;&lt;br /&gt;  TRUNCATE TABLE x8;&lt;br /&gt;  TRUNCATE TABLE x9;&lt;br /&gt;&lt;br /&gt;  FOR r IN c&lt;br /&gt;  LOOP&lt;br /&gt;    CASE&lt;br /&gt;        WHEN r.a BETWEEN 0 AND 99999 THEN&lt;br /&gt;            INSERT INTO x0 VALUES(r.a);&lt;br /&gt;        WHEN r.a BETWEEN 100000 AND 199999 THEN&lt;br /&gt;            INSERT INTO x1 VALUES(r.a);&lt;br /&gt;        WHEN r.a BETWEEN 200000 AND 299999 THEN&lt;br /&gt;            INSERT INTO x2 VALUES(r.a);&lt;br /&gt;        WHEN r.a BETWEEN 300000 AND 399999 THEN&lt;br /&gt;            INSERT INTO x3 VALUES(r.a);&lt;br /&gt;        WHEN r.a BETWEEN 400000 AND 499999 THEN&lt;br /&gt;            INSERT INTO x4 VALUES(r.a);&lt;br /&gt;        WHEN r.a BETWEEN 500000 AND 599999 THEN&lt;br /&gt;            INSERT INTO x5 VALUES(r.a);&lt;br /&gt;        WHEN r.a BETWEEN 600000 AND 699999 THEN&lt;br /&gt;            INSERT INTO x6 VALUES(r.a);&lt;br /&gt;        WHEN r.a BETWEEN 700000 AND 799999 THEN&lt;br /&gt;            INSERT INTO x7 VALUES(r.a);&lt;br /&gt;        WHEN r.a BETWEEN 800000 AND 899999 THEN&lt;br /&gt;            INSERT INTO x8 VALUES(r.a);&lt;br /&gt;        WHEN r.a BETWEEN 900000 AND 1000000 THEN&lt;br /&gt;            INSERT INTO x9 VALUES(r.a);&lt;br /&gt;    END CASE;&lt;br /&gt;  END LOOP;&lt;br /&gt;END;&lt;br /&gt;$function$&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;This variant is longer, but doesn't need a active index on source table.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-2195599527916422380?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/2195599527916422380/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=2195599527916422380' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/2195599527916422380'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/2195599527916422380'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/08/divide-table-into-partitions.html' title='divide a table into a partitions'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-475486307194099884</id><published>2010-07-26T00:51:00.000-07:00</published><updated>2010-07-26T00:51:58.408-07:00</updated><title type='text'>missing %type[] - workaround</title><content type='html'>We are not able to do some more complex functions in plpgsql when returns is polymorphic array type. Why - we are not able to define variable as array of some variable - so input have to be anyarray (and we can do copy of this type) or we to calculate result in one expression used in RETURN statement.But there is one workaround. We can use a OUT polymorphic parameter - function with one OUT parameter is scalar function still. &lt;pre&gt;&lt;br /&gt;-- we cannot do&lt;br /&gt;CREATE OR REPLACE FUNCTION foo(a anyelement)&lt;br /&gt;RETURNS anyarray AS $$&lt;br /&gt;DECLARE b a%type[]; &lt;br /&gt;BEGIN&lt;br /&gt;  ...&lt;br /&gt;&lt;br /&gt;-- we can&lt;br /&gt;CREATE OR REPLACE FUNCTION foo(a anyelement, OUT b anyarray)&lt;br /&gt;AS $$&lt;br /&gt;BEGIN&lt;br /&gt;  b := array_fill(a, ARRAY[1]);&lt;br /&gt;  ...&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-475486307194099884?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/475486307194099884/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=475486307194099884' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/475486307194099884'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/475486307194099884'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/07/missing-type-workaround.html' title='missing %type[] - workaround'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-1104317222466472076</id><published>2010-06-04T00:59:00.000-07:00</published><updated>2010-06-04T00:59:38.909-07:00</updated><title type='text'>the longest common substring of two strings</title><content type='html'>HelloI had to solve one simple task - to find the longest common substring only in SQL:I have a two functions - one returns all substrings of entered string:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION substrings(text) &lt;br /&gt;RETURNS text[] AS $$ &lt;br /&gt;SELECT array(SELECT DISTINCT substring($1 from i for j) &lt;br /&gt;                FROM generate_series(1, length($1)) g(i), &lt;br /&gt;                     generate_series(1, length($1)) h(j)&lt;br /&gt;            )&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;postgres=# SELECT substrings('pavel');&lt;br /&gt;                     substrings                      &lt;br /&gt;-----------------------------------------------------&lt;br /&gt; {e,vel,avel,p,pav,a,pavel,pa,av,ve,ave,l,pave,el,v}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;Second function returns the longest common substrings:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION max_common_substring(text, text) &lt;br /&gt;RETURNS text[] AS $$ &lt;br /&gt;  SELECT ARRAY(SELECT x &lt;br /&gt;                  FROM (SELECT $1, x, length(x), rank() OVER (ORDER BY length(x) DESC) &lt;br /&gt;                           FROM unnest(substrings($2)) g(x) &lt;br /&gt;                          WHERE strpos($1,x) &amp;gt; 0) xx &lt;br /&gt;                 WHERE rank = 1) &lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;postgres=# SELECT max_common_substring('stěhula','stěhule');&lt;br /&gt; max_common_substring &lt;br /&gt;----------------------&lt;br /&gt; {stěhul}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-1104317222466472076?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/1104317222466472076/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=1104317222466472076' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1104317222466472076'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1104317222466472076'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/06/longest-common-substring-of-two-strings.html' title='the longest common substring of two strings'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-8419160193970819950</id><published>2010-05-04T12:56:00.000-07:00</published><updated>2010-05-04T13:00:53.066-07:00</updated><title type='text'>function to_string and to_array</title><content type='html'>Hello&lt;p&gt;I like function &lt;i&gt;string_to_array&lt;/i&gt; and &lt;i&gt;array_to_string&lt;/i&gt;. The power of these functions is unlimited. Last week somebody noticed problems with NULLs related to string_to_array function. It is true - these function doesn't handle NULL well :(.&lt;pre&gt;&lt;br /&gt;postgres=# SELECT string_to_array('1,2,3,NULL,5',',')::int[];&lt;br /&gt;ERROR:  invalid input syntax for integer: "NULL"&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;I would to prepare new version of these function (with better support of NULL) - functions &lt;i&gt;to_array&lt;/i&gt; and &lt;i&gt;to_string&lt;/i&gt;. It can be prepared for 9.1.&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION to_string(anyarray, sep text, nullstr text DEFAULT '') &lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT array_to_string(ARRAY(SELECT coalesce(v::text, $3) &lt;br /&gt;                                FROM unnest($1) g(v)),&lt;br /&gt;                       $2)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;postgres=# select to_string(array[1,2,3,4,null,5],','); &lt;br /&gt; to_string  &lt;br /&gt;------------&lt;br /&gt; 1,2,3,4,,5&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# select to_string(array[1,2,3,4,null,5],',','&amp;lt;NULL&amp;gt;');&lt;br /&gt;    to_string     &lt;br /&gt;------------------&lt;br /&gt; 1,2,3,4,&amp;lt;NULL&amp;gt;,5&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION to_array(text, sep text, nullstr text DEFAULT '') &lt;br /&gt;RETURNS text[] AS $$ &lt;br /&gt;  SELECT ARRAY(SELECT CASE &lt;br /&gt;                           WHEN v = $3 THEN NULL::text &lt;br /&gt;                           ELSE v END &lt;br /&gt;                  FROM unnest(string_to_array($1,$2)) g(v)) &lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;postgres=# select to_array('1,2,3,4,,5',',');&lt;br /&gt;     to_array     &lt;br /&gt;------------------&lt;br /&gt; {1,2,3,4,NULL,5}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# select to_array('1,2,3,4,&amp;lt;NULL&amp;gt;,5',',','&amp;lt;NULL&amp;gt');&lt;br /&gt;     to_array     &lt;br /&gt;------------------&lt;br /&gt; {1,2,3,4,NULL,5}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# select to_array('1,2,3,,5',',')::int[];&lt;br /&gt;    to_array    &lt;br /&gt;----------------&lt;br /&gt; {1,2,3,NULL,5}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;These SQL functions can be used for now.&lt;p&gt;Pavel&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-8419160193970819950?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/8419160193970819950/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=8419160193970819950' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/8419160193970819950'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/8419160193970819950'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/05/function-tostring-and-toarray.html' title='function to_string and to_array'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-598808470864948544</id><published>2010-04-21T02:41:00.000-07:00</published><updated>2010-04-21T02:48:58.931-07:00</updated><title type='text'>two small functions for arrays</title><content type='html'>Hello,If you miss some functions for arrays look here:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION array_append_distinct(anyarray, anyelement) &lt;br /&gt;RETURNS anyarray AS $$ &lt;br /&gt;  SELECT ARRAY(SELECT unnest($1) union SELECT $2) &lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;postgres=# select array_append_distinct(array[1,2,3,4],1);&lt;br /&gt; array_append_distinct &lt;br /&gt;-----------------------&lt;br /&gt; {1,2,3,4}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# select array_append_distinct(array[1,2,3,4],7);&lt;br /&gt; array_append_distinct &lt;br /&gt;-----------------------&lt;br /&gt; {1,2,3,4,7}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION array_erase(anyarray, anyelement) &lt;br /&gt;RETURNS anyarray AS $$&lt;br /&gt;  SELECT ARRAY(SELECT v FROM unnest($1) g(v) WHERE v &amp;lt;&amp;gt; $2)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;postgres=# select array_erase(array[1,2,3,2,5], 2);&lt;br /&gt; array_erase &lt;br /&gt;-------------&lt;br /&gt; {1,3,5}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# select array_erase(array[1,2,3,2,5], 1);&lt;br /&gt; array_erase &lt;br /&gt;-------------&lt;br /&gt; {2,3,2,5}&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-598808470864948544?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/598808470864948544/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=598808470864948544' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/598808470864948544'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/598808470864948544'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/04/two-small-functions-for-arrays.html' title='two small functions for arrays'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-5276263111553797839</id><published>2010-04-20T04:28:00.000-07:00</published><updated>2010-04-20T05:43:41.497-07:00</updated><title type='text'>frequent mistakes in plpgsql design</title><content type='html'>Hello &lt;p&gt;last days I had to correct some typical errors in audited code. There are:&lt;ul&gt;&lt;li&gt;&lt;i&gt;non-use coalesce&lt;/i&gt;:&lt;pre&gt;&lt;br /&gt;-- bad&lt;br /&gt;BEGIN&lt;br /&gt;  IF x1 IS NOT NULL THEN&lt;br /&gt;    s := s || x1 || ',';&lt;br /&gt;  ELSE&lt;br /&gt;    s := s || 'NULL, ';&lt;br /&gt;  END IF;&lt;br /&gt;  IF x2 IS NOT NULL THEN ...&lt;br /&gt;&lt;br /&gt;-- good&lt;br /&gt;BEGIN&lt;br /&gt;  s := coalesce(x1 || ',', 'NULL,') || ...&lt;br /&gt;&lt;/pre&gt;&lt;li&gt;&lt;i&gt;using implicit cast date to text&lt;/i&gt;:&lt;pre&gt;&lt;br /&gt;-- bad&lt;br /&gt;BEGIN&lt;br /&gt;  month = substring(current_date::text FROM 6 FOR 2)::int;&lt;br /&gt;&lt;br /&gt;-- good&lt;br /&gt;BEGIN&lt;br /&gt;  month = substring(to_char(current_date,'YYYY-MM-DD') FROM 6 FOR 2)::int;&lt;br /&gt;&lt;br /&gt;-- but better&lt;br /&gt;BEGIN&lt;br /&gt;  month = EXTRACT(month FROM current_date);&lt;br /&gt;&lt;/pre&gt;&lt;li&gt;using EXECUTE instead PERFORM&lt;li&gt;using explicit cursor instead FOR IN SELECT&lt;li&gt;&lt;i&gt;unsecured dynamic SQL&lt;/i&gt;:&lt;pre&gt;&lt;br /&gt;--bad&lt;br /&gt;EXECUTE 'SELECT ' || column_name || ' FROM ' || table_name&lt;br /&gt;           || ' WHERE ' || column_name || e'=\'' || some_variable || e'\''&lt;br /&gt;  INTO var;&lt;br /&gt;&lt;br /&gt;--good&lt;br /&gt;EXECUTE 'SELECT ' quote_ident(column_name) || ' FROM ' || quote_ident(table_name)&lt;br /&gt;           || WHERE ' || quote_ident(column_name) || '=' || quote_literal(some_variable)&lt;br /&gt;  INTO var;&lt;br /&gt;&lt;br /&gt;-- or on 8.4 and higher&lt;br /&gt;EXECUTE 'SELECT ' quote_ident(column_name) || ' FROM ' || table_name::regclass&lt;br /&gt;           || WHERE ' || quote_ident(column_name) || '= $1'&lt;br /&gt;  INTO var&lt;br /&gt;  USING some_variable&lt;br /&gt;&lt;br /&gt;--bad&lt;br /&gt;CREATE OR REPLACE FUNCTION create_table(schemaname varchar, tablename varchar)&lt;br /&gt;RETURNS void AS $$&lt;br /&gt;BEGIN&lt;br /&gt;  EXECUTE 'CREATE TABLE ' || coalesce(schemaname || '.','') || tablename;&lt;br /&gt;  RETRUN;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE plpgsql;&lt;br /&gt;&lt;br /&gt;--good&lt;br /&gt;CREATE OR REPLACE FUNCTION create_table(schemaname varchar, tablename varchar)&lt;br /&gt;RETURNS void AS $$&lt;br /&gt;BEGIN&lt;br /&gt;  EXECUTE 'CREATE TABLE ' || coalesce(quote_ident(schemaname) || '.','') || quote_ident(tablename);&lt;br /&gt;  RETURN;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE plpgsql;&lt;br /&gt;&lt;/pre&gt;&lt;li&gt;&lt;i&gt;long lines&lt;/i&gt; - 100 chars per line is perfect&lt;li&gt;&lt;i&gt;too short functions&lt;/i&gt; - Don't wrap any and only one SQL statement&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-5276263111553797839?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/5276263111553797839/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=5276263111553797839' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/5276263111553797839'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/5276263111553797839'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2010/04/frequent-mistakes-in-plpgsql-design.html' title='frequent mistakes in plpgsql design'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-1967111248433426910</id><published>2009-12-31T02:15:00.000-08:00</published><updated>2009-12-31T03:33:00.551-08:00</updated><title type='text'>Macros for epsql</title><content type='html'>HelloI finished scripting in psql. Now, we are able to write some simply macros for epsql. This feature allows some better customisation. The syntax is trivial:&lt;pre&gt;&lt;br /&gt;\newcommand string&lt;br /&gt;... commands ...&lt;br /&gt;\endnewcommand&lt;br /&gt;&lt;/pre&gt; We can use a parameters inside command definition. It is usual psql variables named 1 .. 10.&lt;pre&gt;&lt;br /&gt;\newcommand desc&lt;br /&gt;\ifdef 1&lt;br /&gt;\d :1&lt;br /&gt;\else&lt;br /&gt;\echo 'missing table name'&lt;br /&gt;\endifdef&lt;br /&gt;\endnewcommand&lt;br /&gt;&lt;/pre&gt;Usage is very simple. New command is written to .psqlrc file. After registration, we can use new command:&lt;pre&gt;&lt;br /&gt;postgres=# desc tab&lt;br /&gt;       Table "public.tab"&lt;br /&gt;┌────────┬─────────┬───────────┐&lt;br /&gt;│ Column │  Type   │ Modifiers │&lt;br /&gt;├────────┼─────────┼───────────┤&lt;br /&gt;│ a      │ integer │           │&lt;br /&gt;│ b      │ integer │           │&lt;br /&gt;│ c      │ integer │           │&lt;br /&gt;└────────┴─────────┴───────────┘&lt;br /&gt;Indexes:&lt;br /&gt;    "ff" btree (a)&lt;br /&gt;&lt;br /&gt;postgres=# desc&lt;br /&gt;missing table name&lt;br /&gt;postgres=# &lt;br /&gt;&lt;/pre&gt;You can download this patch from pgFoundry.&lt;h3&gt;Samples&lt;/h3&gt;I wrote some macros:&lt;h4&gt;Top Ten&lt;/h4&gt;&lt;pre&gt;&lt;br /&gt;\newcommand \tt&lt;br /&gt;\ifdef 1&lt;br /&gt;select relname, relpages, reltuples from pg_class order by relpages desc limit :1;&lt;br /&gt;\else&lt;br /&gt;select relname, relpages, reltuples from pg_class order by relpages desc;&lt;br /&gt;\endifdef&lt;br /&gt;\endnewcommand&lt;br /&gt;&lt;/pre&gt;&lt;i&gt;Usage:&lt;/i&gt;&lt;pre&gt;&lt;br /&gt;postgres=# \tt 3&lt;br /&gt;┌──────────────┬──────────┬───────────┐&lt;br /&gt;│   relname    │ relpages │ reltuples │&lt;br /&gt;├──────────────┼──────────┼───────────┤&lt;br /&gt;│ pg_proc      │       54 │      2232 │&lt;br /&gt;│ pg_depend    │       41 │      5557 │&lt;br /&gt;│ pg_attribute │       36 │      1960 │&lt;br /&gt;└──────────────┴──────────┴───────────┘&lt;br /&gt;(3 rows)&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Regtype&lt;/h4&gt;&lt;pre&gt;&lt;br /&gt;\newcommand \rt&lt;br /&gt;select :{1}::regtype as :[1];&lt;br /&gt;\endnewcommand&lt;br /&gt;&lt;/pre&gt;&lt;i&gt;Usage:&lt;/i&gt;&lt;pre&gt;&lt;br /&gt;postgres=# \rt 23&lt;br /&gt;┌─────────┐&lt;br /&gt;│   23    │&lt;br /&gt;├─────────┤&lt;br /&gt;│ integer │&lt;br /&gt;└─────────┘&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Show tables&lt;/h4&gt;&lt;pre&gt;&lt;br /&gt;\newcommand show tables&lt;br /&gt;\ifdef 1&lt;br /&gt;\dt :1&lt;br /&gt;\else&lt;br /&gt;\dt&lt;br /&gt;\endifdef&lt;br /&gt;\endnewcommand&lt;br /&gt;&lt;/pre&gt;&lt;i&gt;Usage:&lt;/i&gt;&lt;pre&gt;&lt;br /&gt;postgres=# show tables&lt;br /&gt;        List of relations&lt;br /&gt;┌────────┬──────┬───────┬───────┐&lt;br /&gt;│ Schema │ Name │ Type  │ Owner │&lt;br /&gt;├────────┼──────┼───────┼───────┤&lt;br /&gt;│ public │ tab  │ table │ pavel │&lt;br /&gt;└────────┴──────┴───────┴───────┘&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;show create table&lt;/h4&gt;&lt;pre&gt;&lt;br /&gt;\newcommand show create table&lt;br /&gt;\echo `/usr/local/pgsql/bin/pg_dump --schema-only -t :1 :DBNAME| grep -v -P '(^SET)|(^[-])|(^ALTER)|(^$)'`&lt;br /&gt;\endnewcommand&lt;br /&gt;&lt;/pre&gt;&lt;i&gt;Usage:&lt;/i&gt;&lt;pre&gt;&lt;br /&gt;postgres=# show create table tab&lt;br /&gt;CREATE TABLE tab (&lt;br /&gt;    a integer,&lt;br /&gt;    b integer,&lt;br /&gt;    c integer&lt;br /&gt;);&lt;br /&gt;CREATE INDEX ff ON tab USING btree (a);&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-1967111248433426910?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/1967111248433426910/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=1967111248433426910' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1967111248433426910'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1967111248433426910'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/12/macros-for-epsql.html' title='Macros for epsql'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-2826265024971019734</id><published>2009-12-18T02:07:00.000-08:00</published><updated>2009-12-18T02:08:05.252-08:00</updated><title type='text'>New release of epsql</title><content type='html'>I released a new version of epsql - for 8.5. More on &lt;a href="http://www.postgres.cz/index.php/Enhanced-psql"&gt;http://www.postgres.cz/index.php/Enhanced-psql&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-2826265024971019734?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/2826265024971019734/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=2826265024971019734' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/2826265024971019734'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/2826265024971019734'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/12/new-release-of-epsql.html' title='New release of epsql'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-2950031538922626265</id><published>2009-12-15T05:01:00.000-08:00</published><updated>2009-12-15T05:01:26.854-08:00</updated><title type='text'>enhanced psql console - second version</title><content type='html'>HelloI am working on new version of epsql. It has almost all old features and some new:&lt;h2&gt;Scripting support&lt;/h2&gt;I add support for "if" and "forc" statements. It is a complement to new "DO" statement. DO is nice, but doesn't allow parametrisation and doesn't return a value. Iteration over cursor hasn't these limits:&lt;pre&gt;&lt;br /&gt;\timing off&lt;br /&gt;\set VERBOSITY terse&lt;br /&gt;BEGIN;&lt;br /&gt;DECLARE t CURSOR FOR SELECT generate_series(1,5) g;&lt;br /&gt;\forc t&lt;br /&gt;  \if :g % 2 = 0&lt;br /&gt;    DECLARE t2 CURSOR FOR SELECT generate_series(1,4) z;&lt;br /&gt;    \forc t2&lt;br /&gt;      SELECT :g AS g, :z AS z;&lt;br /&gt;    \endforc t2&lt;br /&gt;    CLOSE t2;&lt;br /&gt;  \endif&lt;br /&gt;\endforc t&lt;br /&gt;CLOSE t;&lt;br /&gt;COMMIT;&lt;br /&gt;&lt;br /&gt;\timing off&lt;br /&gt;\set VERBOSITY terse&lt;br /&gt;BEGIN;&lt;br /&gt;DECLARE t CURSOR FOR SELECT generate_series(1,3) g;&lt;br /&gt;\forc t&lt;br /&gt;  DECLARE t2 CURSOR FOR SELECT generate_series(1,4) z;&lt;br /&gt;  \forc t2&lt;br /&gt;    DECLARE t3 CURSOR FOR SELECT generate_series(1,3) j;&lt;br /&gt;    \forc t3&lt;br /&gt;      DECLARE t4 CURSOR FOR SELECT generate_series(1,5) k;&lt;br /&gt;      \forc t4&lt;br /&gt;        SELECT :g AS g, :z AS z, :j AS j, :k AS k;&lt;br /&gt;      \endforc t4&lt;br /&gt;      CLOSE t4;&lt;br /&gt;    \endforc t3&lt;br /&gt;    CLOSE t3;&lt;br /&gt;  \endforc t2&lt;br /&gt;  CLOSE t2;&lt;br /&gt;\endforc t&lt;br /&gt;CLOSE t;&lt;br /&gt;COMMIT;&lt;br /&gt;&lt;br /&gt;\if position('PostgreSQL 8.5' in version()) &lt;&gt; 0&lt;br /&gt;  \echo 'PostgreSQL verze 8.5'&lt;br /&gt;\else&lt;br /&gt;  \echo 'Other version of PostgreSQL'&lt;br /&gt;\endif&lt;br /&gt;&lt;br /&gt;BEGIN;&lt;br /&gt;DECLARE t CURSOR &lt;br /&gt;   FOR SELECT table_schema || '.' || table_name AS tn &lt;br /&gt;          FROM information_schema.tables &lt;br /&gt;         WHERE table_schema = 'pg_catalog';&lt;br /&gt;\forc t&lt;br /&gt;  SELECT * FROM :tn LIMIT 10;&lt;br /&gt;\endforc&lt;br /&gt;COMMIT;&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;quote_ident and quote_literal integration&lt;/h2&gt;Using psql variables could be dificult, when we would to use it correctly. psql doesn't have a syntax for automatic quoting. epsql will use :[var] for quoting as identifier and :{var} for quoting as literal:&lt;pre&gt;&lt;br /&gt;postgres=# \set x 'my tab'&lt;br /&gt;postgres=# select * from :[x];&lt;br /&gt;ERROR:  relation "my tab" does not exist&lt;br /&gt;LINE 1: select * from "my tab";&lt;br /&gt;&lt;br /&gt;postgres=# select :{x};&lt;br /&gt; ?column? &lt;br /&gt;----------&lt;br /&gt; my tab&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;-- versus&lt;br /&gt;postgres=# select :x;&lt;br /&gt;ERROR:  column "my" does not exist&lt;br /&gt;LINE 1: select my tab;&lt;br /&gt;               ^&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-2950031538922626265?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/2950031538922626265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=2950031538922626265' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/2950031538922626265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/2950031538922626265'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/12/enhanced-psql-console-second-version.html' title='enhanced psql console - second version'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-98653266431480732</id><published>2009-12-13T12:16:00.000-08:00</published><updated>2009-12-20T06:33:49.319-08:00</updated><title type='text'>How not to write a plpgsql functions</title><content type='html'>I found very strange PL/pgSQL code. PL/pgSQL has some specifics, and is good, if programmer has good knowledge about it.  The basic rule - don't repeat string or array concat too often inside loop in function. So bad code:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION generate_uuid_v4() RETURNS uuid AS $$&lt;br /&gt;DECLARE&lt;br /&gt;    value VARCHAR(36);&lt;br /&gt;BEGIN&lt;br /&gt;    value =          lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || '-';&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || '-';&lt;br /&gt;    value = value || lpad((to_hex((ceil(random() * 255)::int &amp; 15) | 64)), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || '-';&lt;br /&gt;    value = value || lpad((to_hex((ceil(random() * 255)::int &amp; 63) | 128)), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || '-';&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0');&lt;br /&gt;    RETURN value::uuid;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE 'plpgsql';&lt;br /&gt;&lt;br /&gt;postgres=# select generate_uuid_v4() from generate_series(1,10000);&lt;br /&gt;           generate_uuid_v4           &lt;br /&gt;--------------------------------------&lt;br /&gt; 9833de6f-4ba6-49f1-8d5f-d98d10d79346&lt;br /&gt; 340c6008-b8fe-4267-bcc5-96bcddb5f675&lt;br /&gt;...&lt;br /&gt; 6f54c524-bd02-4efc-84b3-b56d1c93f3ec&lt;br /&gt; 96ee245e-2990-4842-9d36-45a66dab8adb&lt;br /&gt;Time: 407,082 ms&lt;br /&gt;&lt;/pre&gt;This is good sample of wrong using PL/pgSQL language. PLPerl, maybe PLPython will do much better work. When we cannot use these languages, we have to use some tricks. With some modifications original code - we can have 2x faster code.&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION _generate_uuid_v4() RETURNS uuid AS $$&lt;br /&gt;BEGIN&lt;br /&gt;    RETURN  (lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || '-'&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || '-'&lt;br /&gt;   || lpad((to_hex((ceil(random() * 255)::int &amp; 15) | 64)), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || '-'&lt;br /&gt;   || lpad((to_hex((ceil(random() * 255)::int &amp; 63) | 128)), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || '-'&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0'))::uuid;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE 'plpgsql'&lt;br /&gt;&lt;br /&gt;postgres=# select _generate_uuid_v4() from generate_series(1,10000);&lt;br /&gt;          _generate_uuid_v4           &lt;br /&gt;--------------------------------------&lt;br /&gt; 5134fe69-5d2a-4419-bf84-d72dc290f72f&lt;br /&gt; 919e8663-b384-4eae-89d9-1026d6be4127&lt;br /&gt; f23f8f4f-6914-47e8-983f-155ace0d8860&lt;br /&gt;...&lt;br /&gt; 39951d8f-fbd1-48bf-9c5d-157eefbd5af6&lt;br /&gt; 73318784-2cfa-4bcc-ad42-307fc4c173fc&lt;br /&gt;Time: 203,516 ms&lt;br /&gt;&lt;/pre&gt;Original needs 400ms for ten thousand values. Edited code needs only 200ms for same number of values.This code should be inlined - when we use SQL language:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION _generate_uuid_v4() RETURNS uuid AS $$&lt;br /&gt;SELECT (lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || '-'&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || '-'&lt;br /&gt;   || lpad((to_hex((ceil(random() * 255)::int &amp; 15) | 64)), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || '-'&lt;br /&gt;   || lpad((to_hex((ceil(random() * 255)::int &amp; 63) | 128)), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || '-'&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0')&lt;br /&gt;   || lpad(to_hex(ceil(random() * 255)::int), 2, '0'))::uuid;&lt;br /&gt;$$ LANGUAGE SQL;&lt;br /&gt;&lt;br /&gt;postgres=# select _generate_uuid_v4() from generate_series(1,10000);&lt;br /&gt;          _generate_uuid_v4           &lt;br /&gt;--------------------------------------&lt;br /&gt; e725eb84-67ad-485e-bec2-d73485db4e69&lt;br /&gt; 49f605d1-226e-4d9e-8457-30a925c98a0c&lt;br /&gt; ed759055-23e7-4322-a98a-552f65a297ae&lt;br /&gt;...&lt;br /&gt; 414e62c3-1b84-4bc9-8fd7-ba8bd0a72403&lt;br /&gt; 03e27594-42c0-4ec9-a493-636b6d130aad&lt;br /&gt;Time: 148,184 ms&lt;br /&gt;&lt;/pre&gt;With SQL function we safe other 50ms. PLpgSQL is good language - but it isn't C language or Fortran. It needs some different manners.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-98653266431480732?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/98653266431480732/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=98653266431480732' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/98653266431480732'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/98653266431480732'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/12/how-dont-write-plpgsql-functions.html' title='How not to write a plpgsql functions'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-3008755570877508300</id><published>2009-11-25T11:18:00.000-08:00</published><updated>2009-11-25T11:58:28.670-08:00</updated><title type='text'>Aggregate function MEDIAN in PostgreSQL</title><content type='html'>Searching and calculating Median in databases was terrible. Still median isn't ANSI SQL aggregate function. There are two commons method how to calculate median of some column. First - very old, and very slow based on self join alchemy, second - new - based on analytic function. Now, I will be test some newer methods on one million rows large table:&lt;br /&gt;&lt;pre&gt;postgres=# create table milrows(a real);&lt;br /&gt;CREATE TABLE&lt;br /&gt;Time: 7,975 ms&lt;br /&gt;postgres=# insert into milrows select random()*1000000 from generate_series(1,1000000);&lt;br /&gt;INSERT 0 1000000&lt;br /&gt;Time: 6863,575 ms&lt;br /&gt;&lt;/pre&gt;simple scan on this table takes 200ms&lt;br /&gt;&lt;pre&gt;Frompostgres=# select avg(a) from milrows ;&lt;br /&gt;       avg        &lt;br /&gt;──────────────────&lt;br /&gt; 499515.883033113&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 200,176 ms&lt;br /&gt;&lt;/pre&gt;In 8.4 we can use analytic functions. These functions uses TupleStore - internal store feature - it allows work with very large tables - limit is free space on disc.&lt;br /&gt;&lt;h2&gt;Analytic methods&lt;/h2&gt;&lt;pre&gt;--Joe Celko's method&lt;br /&gt;postgres=# SELECT avg(a)::float&lt;br /&gt;              FROM (SELECT a, row_number() OVER (ORDER BY a asc) AS hi,&lt;br /&gt;                              count(*) OVER () + 1 - row_number() OVER (ORDER BY a) AS lo&lt;br /&gt;                       FROM milrows) qs&lt;br /&gt;             WHERE hi IN (lo-1,lo,lo+1);&lt;br /&gt;      avg      &lt;br /&gt;───────────────&lt;br /&gt; 499188.546875&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 4922,678 ms&lt;br /&gt;&lt;br /&gt;-- Andrew Gierth's method &lt;br /&gt;postgres=# select avg(a)&lt;br /&gt;   from ( select a, row_number() over (order by a),count(*) over () from milrows ) s&lt;br /&gt;  where row_number between floor((count::float8-1)/2+1) and ceil((count::float8-1)/2+1)&lt;br /&gt;;&lt;br /&gt;      avg      &lt;br /&gt;───────────────&lt;br /&gt; 499188.546875&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 5021,001 ms&lt;br /&gt;&lt;br /&gt;-- modified Andrew's method (count(*) over () is slow)&lt;br /&gt;postgres=# select avg(a)&lt;br /&gt;   from ( select a, row_number() over (order by a),(select count(*) from milrows) as count from milrows ) s&lt;br /&gt;  where row_number between floor((count::float8-1)/2+1) and ceil((count::float8-1)/2+1)&lt;br /&gt;;&lt;br /&gt;      avg      &lt;br /&gt;───────────────&lt;br /&gt; 499188.546875&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 3931,922 ms&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;Array based methods&lt;/h2&gt;Next methods are based on using an arrays. These methods are fast, but limit for this methods is size of operation memory. For very very large tables could to take all application memory.&lt;br /&gt;&lt;pre&gt;--Regina's method -- it's not 100% correct http://www.postgresonline.com/journal/index.php?/archives/67-Build-Median-Aggregate-Function-in-SQL.html#extended&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION array_median(double precision[])&lt;br /&gt;  RETURNS double precision AS&lt;br /&gt;$$&lt;br /&gt;    SELECT CASE WHEN array_upper($1,1) = 0 THEN null &lt;br /&gt;                ELSE asorted[ceiling(array_upper(asorted,1)/2.0)]::double precision END&lt;br /&gt;       FROM (SELECT ARRAY(SELECT $1[n] &lt;br /&gt;                FROM generate_series(1, array_upper($1, 1)) AS n&lt;br /&gt;               WHERE $1[n] IS NOT NULL&lt;br /&gt;               ORDER BY $1[n]) As asorted) As foo &lt;br /&gt;$$ LANGUAGE 'sql' IMMUTABLE;&lt;br /&gt;&lt;br /&gt;CREATE AGGREGATE median(double precision) (&lt;br /&gt;  SFUNC=array_append,&lt;br /&gt;  STYPE=double precision[],&lt;br /&gt;  FINALFUNC=array_median&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;postgres=# select median(a) from milrows ;&lt;br /&gt;^CCancel request sent&lt;br /&gt;ERROR:  canceling statement due to user request -- killed 5 minutes !don't use array_append for bigger arrays (length &amp;gt; 10000)&lt;br /&gt;postgres=# &lt;br /&gt;&lt;br /&gt;--My method&lt;br /&gt;postgres=# create or replace function median(anyarray) &lt;br /&gt;returns double precision as $$&lt;br /&gt;  select ($1[array_upper($1,1)/2+1]::double precision + $1[(array_upper($1,1)+1) / 2]::double precision) / 2.0; &lt;br /&gt;$$ language sql immutable strict;&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;Time: 1,557 ms&lt;br /&gt;&lt;br /&gt;Time: 2574,677 ms&lt;br /&gt;postgres=# select median(array(select a from milrows where a is not null order by a));&lt;br /&gt;    median     &lt;br /&gt;───────────────&lt;br /&gt; 499188.546875&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 2555,342 ms&lt;br /&gt;&lt;/pre&gt;This week I added support for median aggregate to orafce package. You can download it from url &lt;a href="http://pgfoundry.org/frs/download.php/2472/orafce-3.0.2-devel.tar.gz"&gt;http://pgfoundry.org/frs/download.php/2472/orafce-3.0.2-devel.tar.gz&lt;/a&gt; . Function median use some fetures 8.4 and needs 8.4 - it isn't supported on PostgreSQL 8.3 and older.&lt;br /&gt;&lt;pre&gt;-- orafce 3.0.2 median (needs PostgreSQL 8.4 and higher)&lt;br /&gt;&lt;br /&gt;postgres=# select median(a::float8) from milrows;&lt;br /&gt;    median     &lt;br /&gt;───────────────&lt;br /&gt; 499188.546875&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 687,577 ms&lt;br /&gt;&lt;/pre&gt;It's very fast - if your table has about one million rows (1000000) you can use it (for this table size takes max. 15MB RAM (for one column)).&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-3008755570877508300?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/3008755570877508300/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=3008755570877508300' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3008755570877508300'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3008755570877508300'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/11/aggregate-function-median-in-postgresql.html' title='Aggregate function MEDIAN in PostgreSQL'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-3013302433711341691</id><published>2009-11-14T10:23:00.000-08:00</published><updated>2009-11-14T10:26:09.495-08:00</updated><title type='text'>longtime plpgsql misfeature removed</title><content type='html'>Tom Lane did refactoring of plpgsql source code. These changes are very very important. plpgsql is good language - simple, robust with good error diagnostic. But it had one bizarre behave. plpgsql connects two worlds - procedural ADA like code and SQL statements. Usually there are not problems. But there are one exception - collision of identifiers. Older behave was too simply. Plpgsql identifiers win every-time. It was a source of some bizarre bugs. Look on code:&amp;nbsp; &lt;br /&gt;&lt;pre&gt;postgres=# select * from omega;&lt;br /&gt; a  &lt;br /&gt;────&lt;br /&gt; 10&lt;br /&gt; 20&lt;br /&gt; 30&lt;br /&gt;(3 rows)&lt;br /&gt;&lt;br /&gt;create or replace function foo() &lt;br /&gt;returns void as $$&lt;br /&gt;#variable_conflict use_variable -- compatible with 8.4 and older&lt;br /&gt;declare a integer; &lt;br /&gt;begin &lt;br /&gt;  for a in select a from omega &lt;br /&gt;  loop &lt;br /&gt;    raise notice '%', a;&lt;br /&gt;  end loop; &lt;br /&gt;end; &lt;br /&gt;$$ language plpgsql;&lt;br /&gt;&lt;/pre&gt;This code is very simple. Just show content of table omega.&lt;br /&gt;&lt;pre&gt;postgres=# select foo();&lt;br /&gt;NOTICE:  &amp;lt;null&amp;gt;&lt;br /&gt;NOTICE:  &amp;lt;null&amp;gt;&lt;br /&gt;NOTICE:  &amp;lt;null&amp;gt;&lt;br /&gt; foo &lt;br /&gt;─────&lt;br /&gt; &lt;br /&gt;(1 row)&lt;br /&gt;&lt;/null&gt;&lt;/null&gt;&lt;/null&gt;&lt;/pre&gt;or not? Why we don't see values 10,20,30? Because interpret prefer plpgsql identifier against to sql identifier omega.a. This bug is very strange and some time is very difficult to find it. But it is a history. plpgsql 8.5 is much more cleaner. Wrong code raises en exception:&lt;br /&gt;&lt;pre&gt;postgres=# &lt;br /&gt;create or replace function foo() &lt;br /&gt;returns void as $$&lt;br /&gt;declare a integer;                                              &lt;br /&gt;begin              &lt;br /&gt;  for a in select a from omega &lt;br /&gt;  loop                         &lt;br /&gt;    raise notice '%', a;&lt;br /&gt;  end loop; &lt;br /&gt;end; &lt;br /&gt;$$ language plpgsql;&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;Time: 3,501 ms&lt;br /&gt;postgres=# select foo();&lt;br /&gt;ERROR:  column reference "a" is ambiguous&lt;br /&gt;LINE 1: select a from omega&lt;br /&gt;               ^&lt;br /&gt;DETAIL:  It could refer to either a PL/pgSQL variable or a table column.&lt;br /&gt;QUERY:  select a from omega&lt;br /&gt;CONTEXT:  PL/pgSQL function "foo" line 3 at FOR over SELECT rows&lt;br /&gt;&lt;/pre&gt;We could to fix this problem and we get a good answer:&lt;br /&gt;&lt;pre&gt;postgres=# &lt;br /&gt;create or replace function foo() &lt;br /&gt;returns void as $$&lt;br /&gt;declare a integer; &lt;br /&gt;begin &lt;br /&gt;  for a in select omega.a from omega &lt;br /&gt;  loop &lt;br /&gt;    raise notice '%', a;&lt;br /&gt;  end loop; &lt;br /&gt;end; &lt;br /&gt;$$ language plpgsql;&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;Time: 2,289 ms&lt;br /&gt;postgres=# select foo();&lt;br /&gt;NOTICE:  10&lt;br /&gt;NOTICE:  20&lt;br /&gt;NOTICE:  30&lt;br /&gt; foo &lt;br /&gt;─────&lt;br /&gt; &lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;I am very happy from this changes. &lt;b&gt;Thanks&lt;/b&gt; Tom.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-3013302433711341691?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/3013302433711341691/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=3013302433711341691' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3013302433711341691'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3013302433711341691'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/11/longtime-plpgsql-misfeature-removed.html' title='longtime plpgsql misfeature removed'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-8174338038067879461</id><published>2009-10-29T03:06:00.000-07:00</published><updated>2009-10-29T04:45:20.984-07:00</updated><title type='text'>Named function parameters - a feature of PostgreSQL 8.5</title><content type='html'>Hello&lt;br /&gt;&lt;br /&gt;New PostgreSQL 8.5 alfa 2 allows to use named parameters. You may to know this feature from Oracle or MS SQL server. This feature is possible in PostgreSQL now too. Following function add n working days to specified date. What is new? You can specify a free days (default is based on europen traditions).&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;CREATE TYPE dayname AS ENUM ('sun','mon','tue','wed','thu','fri','sat');&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION dow(dayname) &lt;br /&gt;RETURNS int AS $$ &lt;br /&gt;SELECT CASE $1 &lt;br /&gt;           WHEN 'sun' THEN 0 &lt;br /&gt;           WHEN 'mon' THEN 1 &lt;br /&gt;           WHEN 'tue' THEN 2 &lt;br /&gt;           WHEN 'wed' THEN 3 &lt;br /&gt;           WHEN 'thu' THEN 4 &lt;br /&gt;           WHEN 'fri' THEN 5 &lt;br /&gt;           WHEN 'sat' THEN 6 END; &lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION dows(dayname[]) &lt;br /&gt;RETURNS int[] AS $$ &lt;br /&gt;SELECT ARRAY(SELECT dow(d) FROM UNNEST($1) d(d))&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION addwdays(date, int, freedays text[] = ARRAY['sat','sun']) &lt;br /&gt;RETURNS date AS $$&lt;br /&gt;SELECT ad &lt;br /&gt;   FROM (SELECT $1 + i as ad, &lt;br /&gt;                extract (dow from $1 + i), &lt;br /&gt;                sum(CASE WHEN EXTRACT(dow FROM $1 + i) = ANY(dows($3::dayname[])) THEN 0 ELSE 1 END) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS x &lt;br /&gt;            FROM generate_series(1,1000) g(i)) s &lt;br /&gt;  WHERE x = $2 AND date_part &amp;lt;&amp;gt; ALL(dows($3::dayname[]));&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;postgres=# select current_date, addwdays(current_date,1);&lt;br /&gt;    date    │  addwdays  &lt;br /&gt;────────────┼────────────&lt;br /&gt; 2009-10-29 │ 2009-10-30&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 1,768 ms&lt;br /&gt;postgres=# select current_date, addwdays(current_date,1, ARRAY['fri'] AS freedays);&lt;br /&gt;    date    │  addwdays  &lt;br /&gt;────────────┼────────────&lt;br /&gt; 2009-10-29 │ 2009-10-31&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Please, test it.&lt;br /&gt;see &lt;a href="http://www.postgresql.org/about/news.1152"&gt;http://www.postgresql.org/about/news.1152&lt;/a&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-8174338038067879461?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/8174338038067879461/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=8174338038067879461' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/8174338038067879461'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/8174338038067879461'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/10/named-function-parameters-feature-of.html' title='Named function parameters - a feature of PostgreSQL 8.5'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-3881707887445364241</id><published>2009-10-01T05:00:00.000-07:00</published><updated>2009-10-01T07:17:19.549-07:00</updated><title type='text'>Dynamic access to record fields in PL/pgSQL</title><content type='html'>PL/pgSQL isn't dynamic language. Usually it isn't problem. This language is best as glue of SQL statements. One typical feature is strongly static access to record or row fields. But sometime we need dynamic access. This is very hard task in older version. Some better situation is in 8.4, and maybe in 8.5, this problem is definitely out. New an enhancing of hstore module allows conversion from ROW type to hstore type (this is similar to hash table in Perl). We could to use this functionality:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;postgres=# SELECT * FROM foo;&lt;br /&gt; a  | b  |   c    &lt;br /&gt;----+----+--------&lt;br /&gt; 10 | 20 | Pavel&lt;br /&gt; 30 | 40 | Zdenek&lt;br /&gt;(2 rows)&lt;br /&gt;&lt;br /&gt;postgres=# select (each(hstore(foo))).* from foo;&lt;br /&gt; key | value  &lt;br /&gt;-----+--------&lt;br /&gt; a   | 10&lt;br /&gt; b   | 20&lt;br /&gt; c   | Pavel&lt;br /&gt; a   | 30&lt;br /&gt; b   | 40&lt;br /&gt; c   | Zdenek&lt;br /&gt;(6 rows)&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION trgfce() &lt;br /&gt;RETURNS trigger AS $$&lt;br /&gt;DECLARE r record; &lt;br /&gt;BEGIN &lt;br /&gt;  FOR r IN SELECT (each(hstore(NEW))).* &lt;br /&gt;  LOOP &lt;br /&gt;    RAISE NOTICE 'key:%, value: %', r.key, r.value; &lt;br /&gt;  END LOOP; &lt;br /&gt;  RETURN new; &lt;br /&gt;END&lt;br /&gt;$$ LANGUAGE plpgsql;&lt;br /&gt;&lt;br /&gt;CREATE TRIGGER trg BEFORE INSERT ON foo FOR EACH ROW EXECUTE PROCEDURE trgfce();&lt;br /&gt;&lt;br /&gt;postgres=# INSERT INTO foo VALUES(80,90,'Zbynek');&lt;br /&gt;NOTICE:  key:a, value: 80&lt;br /&gt;NOTICE:  key:b, value: 90&lt;br /&gt;NOTICE:  key:c, value: Zbynek&lt;br /&gt;INSERT 0 1&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-3881707887445364241?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/3881707887445364241/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=3881707887445364241' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3881707887445364241'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3881707887445364241'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/10/dynamic-access-to-record-fields-in.html' title='Dynamic access to record fields in PL/pgSQL'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-7164366842172593746</id><published>2009-08-22T09:26:00.000-07:00</published><updated>2011-10-29T11:01:17.713-07:00</updated><title type='text'>MySQL functions for PostgreSQL</title><content type='html'>Hello&lt;p&gt;I found article about MySQL function &lt;a href="http://http//dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_field"&gt;&lt;span style="font-style: italic;"&gt;field&lt;/span&gt;&lt;/a&gt;. PostgreSQL doesn't support similar function (it is nice together with ORDER BY expression clause), but with new variadic parameters is easy to develop it in pg:&lt;pre&gt;CREATE OR REPLACE FUNCTION field(text, variadic text[])&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;  SELECT i&lt;br /&gt;     FROM generate_subscripts($2,1) g(i)&lt;br /&gt;    WHERE $1 = $2[i]&lt;br /&gt;    UNION ALL&lt;br /&gt;    SELECT 0&lt;br /&gt;    LIMIT 1&lt;br /&gt;$$ LANGUAGE sql STRICT;&lt;/pre&gt;An using looks like:&lt;pre&gt;select * from pet order by field(species, 'cat', 'dog', 'bird');&lt;br /&gt;&lt;br /&gt;+----------+--------+---------+------+------------+------------+&lt;br /&gt;| name     | owner  | species | sex  | birthday   | death      |&lt;br /&gt;+----------+--------+---------+------+------------+------------+&lt;br /&gt;| Fluffy   | Harold | cat     | f    | 1993-02-04 | NULL       |&lt;br /&gt;| Claws    | Gwen   | cat     | m    | 1994-03-17 | NULL       |&lt;br /&gt;| Buffy    | Harold | dog     | f    | 1989-05-13 | NULL       |&lt;br /&gt;| Fang     | Benny  | dog     | m    | 1990-08-27 | NULL       |&lt;br /&gt;| Bowser   | Diane  | dog     | m    | 1989-08-31 | 1995-07-29 |&lt;br /&gt;| Chirpy   | Gwen   | bird    | f    | 1998-09-11 | NULL       |&lt;br /&gt;| Whistler | Gwen   | bird    | NULL | 1997-12-09 | NULL       |&lt;br /&gt;+----------+--------+---------+------+------------+------------+&lt;br /&gt;&lt;/pre&gt;When I played with it, I ported mostly used MySQL functions to PostgreSQL. I hope, so this file can help somebody with porting applications to PostgreSQL or with better orientation in PostgreSQL. Lot of thing with strings or date PostgreSQL is done little bit different than MySQL - these functions can help.&lt;h2&gt;String Functions&lt;/h2&gt;&lt;pre&gt;CREATE OR REPLACE FUNCTION concat(variadic str text[])&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT array_to_string($1, '');&lt;br /&gt;$$ LANGUAGE sql&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION concat_ws(separator text, variadic str text[])&lt;br /&gt;RETURNS text as $$&lt;br /&gt;SELECT array_to_string($2, $1);&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION elt(int, VARIADIC text[])&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT $2[$1];&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION field(varchar, VARIADIC text[])&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT i&lt;br /&gt;FROM generate_subscripts($2,1) g(i)&lt;br /&gt;WHERE $1 = $2[i]&lt;br /&gt;UNION ALL&lt;br /&gt;SELECT 0&lt;br /&gt;LIMIT 1&lt;br /&gt;$$ LANGUAGE sql STRICT;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION find_in_set(str text, strlist text)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT i&lt;br /&gt;   FROM generate_subscripts(string_to_array($2,','),1) g(i)&lt;br /&gt;  WHERE (string_to_array($2, ','))[i] = $1&lt;br /&gt;  UNION ALL&lt;br /&gt;  SELECT 0&lt;br /&gt;  LIMIT 1&lt;br /&gt;$$ LANGUAGE sql STRICT;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION hex(int)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT upper(to_hex($1));&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION hex(bigint)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT upper(to_hex($1));&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION hex(text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT upper(encode($1::bytea, 'hex'))&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;* char is keyword, double quotes are necessary.&lt;br /&gt;*&lt;br /&gt;*  postgres=# select "char"(77,121,83,81,'76');&lt;br /&gt;*  char&lt;br /&gt;* -------&lt;br /&gt;*  MySQL&lt;br /&gt;*/&lt;br /&gt;CREATE OR REPLACE FUNCTION "char"(VARIADIC int[])&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT array_to_string(ARRAY(SELECT chr(unnest($1))),'')&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION lcase(str text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT lower($1)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION left(str text, len int)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT substring($1 FROM 1 FOR $2)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION locate(substr text, str text)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT position($1 in $2)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION reverse(str text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT array_to_string(ARRAY(SELECT substring($1 FROM i FOR 1)&lt;br /&gt;                                FROM generate_series(length($1),1,-1) g(i)),&lt;br /&gt;                       '')&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION right(str text, len int)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT substring($1 FROM length($1) - $2 FOR $2)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION space(n int)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT repeat(' ', $1)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION strcmp(text, text)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT CASE WHEN $1 &amp;lt; $2 THEN -1&lt;br /&gt;WHEN $1 &amp;gt; $2 THEN 1&lt;br /&gt;ELSE 0 END;&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION substring_index(str text, delim text, count int)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT CASE WHEN $3 &amp;gt; 0&amp;nbsp;&lt;br /&gt;THEN array_to_string((string_to_array($1, $2))[1:$3], $2)&lt;br /&gt;ELSE array_to_string(ARRAY(SELECT unnest(string_to_array($1,$2))&lt;br /&gt;                             OFFSET array_upper(string_to_array($1,$2),1) + $3),&lt;br /&gt;                     $2)&lt;br /&gt;END&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION ucase(str text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT upper($1)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE CAST (bytea AS text) WITHOUT FUNCTION AS ASSIGNMENT;&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;* SELECT hex('žlutý kůň'), unhex(hex('žlutý kůň'))&lt;br /&gt;*/&lt;br /&gt;CREATE OR REPLACE FUNCTION unhex(text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT decode($1, 'hex')::text;&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;/pre&gt;Note: On some newer PostgreSQL the function unhex doesn't work well - you can use a second version, that works well for ascii chars or for utf8:&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION unhex(text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT encode(decode($1, 'hex'),'escape');&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;-- UTF8 version&lt;br /&gt;CREATE OR REPLACE FUNCTION unhex(text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT convert_from(decode($1, 'hex'),'utf8');&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;/pre&gt;if you have a superuser rights, you can use a "better" auxilary function, that ensure casting from bytea to text type without changes (it's relative dirty trick):&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION public.direct_bytea_to_cstring(bytea)&lt;br /&gt; RETURNS cstring&lt;br /&gt; LANGUAGE internal&lt;br /&gt; IMMUTABLE STRICT&lt;br /&gt;AS $function$textout$function$&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION unhex(text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT direct_bytea_to_cstring(decode($1, 'hex'))::text;&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;&lt;/pre&gt;You can to try use cast (and use a original function) - you have to have a superuser rights too (this cast can be entered by postgres user only):&lt;pre&gt;&lt;br /&gt;create cast (bytea as text) without function;&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;Date, time Functions&lt;/h2&gt;&lt;pre&gt;/*&lt;br /&gt;* postgres=# select adddate('2008-01-02','31 day');&lt;br /&gt;*   adddate&lt;br /&gt;* -----------&lt;br /&gt;*  2008-02-02&lt;br /&gt;*/&lt;br /&gt;CREATE OR REPLACE FUNCTION adddate(date, interval)&lt;br /&gt;RETURNS date AS $$&lt;br /&gt;SELECT ($1 + $2)::date; $$&lt;br /&gt;LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION curdate()&lt;br /&gt;RETURNS date AS $$&lt;br /&gt;SELECT CURRENT_DATE&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION convert_tz(dt timestamp, from_tz text, to_tz text)&lt;br /&gt;RETURNS timestamp AS $$&lt;br /&gt;SELECT ($1 AT TIME ZONE $2) AT TIME ZONE $3;&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION date(anyelement)&lt;br /&gt;RETURNS date AS $$&lt;br /&gt;SELECT $1::date;&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;SELECT OR REPLACE FUNCTION datediff(date, date)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT $1 - $2&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION date_add(date, interval)&lt;br /&gt;RETURNS date AS $$&lt;br /&gt;SELECT adddate($1, $2)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION date_format(date, text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT to_char($1, _mysqlf_pgsql($2))&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION date_format(timestamp, text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT to_char($1, _mysqlf_pgsql($2))&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION date_sub(date, interval)&lt;br /&gt;RETURNS date AS $$&lt;br /&gt;SELECT ($1 - $2)::date;&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION dayofmonth(date)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT EXTRACT(day from $1)::int&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION day(date)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT dayofmonth($1)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION dayname(date)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT to_char($1, 'TMDay')&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION dayofweek(date)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT EXTRACT(dow FROM $1)::int&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION dayofyear(date)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT EXTRACT(doy FROM $1)::int&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION from_days(int)&lt;br /&gt;RETURNS date AS $$&lt;br /&gt;SELECT date '0001-01-01bc' + $1&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION from_unixtime(double precision)&lt;br /&gt;RETURNS timestamp AS $$&lt;br /&gt;SELECT to_timestamp($1)::timestamp&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION _mysqlf_pgsql(text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT array_to_string(ARRAY(SELECT s&lt;br /&gt;FROM (SELECT CASE WHEN substring($1 FROM i FOR 1) &amp;lt;&amp;gt; '%'&lt;br /&gt;AND substring($1 FROM i-1 FOR 1) &amp;lt;&amp;gt; '%'&lt;br /&gt;THEN substring($1 FROM i for 1)&lt;br /&gt;ELSE CASE substring($1 FROM i FOR 2)&lt;br /&gt;WHEN '%H' THEN 'HH24'&lt;br /&gt;WHEN '%p' THEN 'am'&lt;br /&gt;WHEN '%Y' THEN 'YYYY'&lt;br /&gt;WHEN '%m' THEN 'MM'&lt;br /&gt;WHEN '%d' THEN 'DD'&lt;br /&gt;WHEN '%i' THEN 'MI'&lt;br /&gt;WHEN '%s' THEN 'SS'&lt;br /&gt;WHEN '%a' THEN 'Dy'&lt;br /&gt;WHEN '%b' THEN 'Mon'&lt;br /&gt;WHEN '%W' THEN 'Day'&lt;br /&gt;WHEN '%M' THEN 'Month'&lt;br /&gt;END&lt;br /&gt;END s&lt;br /&gt;FROM generate_series(1,length($1)) g(i)) g&lt;br /&gt;WHERE s IS NOT NULL),&lt;br /&gt;'')&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION get_format(text, text)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT CASE lower($1)&lt;br /&gt;WHEN 'date' THEN&lt;br /&gt;CASE lower($2)&lt;br /&gt;WHEN 'usa' THEN '%m.%d.%Y'&lt;br /&gt;WHEN 'jis' THEN '%Y-%m-%d'&lt;br /&gt;WHEN 'iso' THEN '%Y-%m-%d'&lt;br /&gt;WHEN 'eur' THEN '%d.%m.%Y'&lt;br /&gt;WHEN 'internal' THEN '%Y%m%d'&lt;br /&gt;END&lt;br /&gt;WHEN 'datetime' THEN&lt;br /&gt;CASE lower($2)&lt;br /&gt;WHEN 'usa' THEN '%Y-%m-%d %H-.%i.%s'&lt;br /&gt;WHEN 'jis' THEN '%Y-%m-%d %H:%i:%s'&lt;br /&gt;WHEN 'iso' THEN '%Y-%m-%d %H:%i:%s'&lt;br /&gt;WHEN 'eur' THEN '%Y-%m-%d %H.%i.%s'&lt;br /&gt;WHEN 'internal' THEN '%Y%m%d%H%i%s'&lt;br /&gt;END&lt;br /&gt;WHEN 'time' THEN&lt;br /&gt;CASE lower($2)&lt;br /&gt;WHEN 'usa' THEN '%h:%i:%s %p'&lt;br /&gt;WHEN 'jis' THEN '%H:%i:%s'&lt;br /&gt;WHEN 'iso' THEN '%H:%i:%s'&lt;br /&gt;WHEN 'eur' THEN '%H.%i.%s'&lt;br /&gt;WHEN 'internal' THEN '%H%i%s'&lt;br /&gt;END&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION hour(time)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT EXTRACT(hour FROM $1)::int;&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION hour(timestamp)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT EXTRACT(hour FROM $1)::int;&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION last_day(date)&lt;br /&gt;RETURNS date AS $$&lt;br /&gt;SELECT (date_trunc('month',$1 + interval '1 month'))::date - 1&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION makedate(year int, dayofyear int)&lt;br /&gt;RETURNS date AS $$&lt;br /&gt;SELECT (date '0001-01-01' + ($1 - 1) * interval '1 year' + ($2 - 1) * interval '1 day'):: date&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION maketime(int, int, double precision)&lt;br /&gt;RETURNS time AS $$&lt;br /&gt;SELECT time '00:00:00' + $1 * interval '1 hour' + $2 * interval '1 min'&lt;br /&gt;+ $3 * interval '1 sec'&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION minute(timestamp)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT EXTRACT(minute FROM $1)::int&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION month(date)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT EXTRACT(month FROM $1)::int&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION monthname(date)&lt;br /&gt;RETURNS text AS $$&lt;br /&gt;SELECT to_char($1, 'TMMonth')&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION str_to_date(text, text)&lt;br /&gt;RETURNS date AS $$&lt;br /&gt;SELECT to_date($1, _mysqlf_pgsql($2))&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION time(timestamp)&lt;br /&gt;RETURNS time AS $$&lt;br /&gt;SELECT $1::time&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION to_days(date)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT $1 - '0001-01-01bc'&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION unix_timestamp()&lt;br /&gt;RETURNS double precision AS $$&lt;br /&gt;SELECT EXTRACT(epoch FROM current_timestamp)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION unix_timestamp(timestamp)&lt;br /&gt;RETURNS double precision AS $$&lt;br /&gt;SELECT EXTRACT(epoch FROM $1)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION year(date)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT EXTRACT(year FROM $1)&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION week(date)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;SELECT EXTRACT(week FROM $1)::int;&lt;br /&gt;$$ LANGUAGE sql;&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;GROUP_CONCAT&lt;/h2&gt;PostgreSQL doesn't support aggregate function group_concat. But you can use aggregate &lt;a href="http://www.postgresql.org/docs/8.4/static/functions-aggregate.html"&gt;array_agg&lt;/a&gt;:&lt;pre&gt;postgres=# select * from omega;&lt;br /&gt;+---+&lt;br /&gt;| x |&lt;br /&gt;+---+&lt;br /&gt;| 1 |&lt;br /&gt;| 3 |&lt;br /&gt;| 6 |&lt;br /&gt;+---+&lt;br /&gt;(3 rows)&lt;br /&gt;&lt;br /&gt;postgres=# select array_to_string(array_agg(x),',') from omega;&lt;br /&gt;+-----------------+&lt;br /&gt;| array_to_string |&lt;br /&gt;+-----------------+&lt;br /&gt;| 1,3,6           |&lt;br /&gt;+-----------------+&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;If you search more complete MySQL API, look on &lt;a href="http://pgfoundry.org/projects/mysqlcompat/"&gt;mysqlcompat&lt;/a&gt; project.RegardsPavel&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-7164366842172593746?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/7164366842172593746/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=7164366842172593746' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/7164366842172593746'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/7164366842172593746'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/08/mysql-functions-for-postgresql.html' title='MySQL functions for PostgreSQL'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-1835034213993508960</id><published>2009-08-13T08:28:00.000-07:00</published><updated>2009-08-13T09:02:28.080-07:00</updated><title type='text'>epsql available</title><content type='html'>Hello&lt;br /&gt;&lt;br /&gt;Today I uploaded patch &lt;span style="font-style: italic;"&gt;enhancing psql&lt;/span&gt; on pgfoundry. You can download it from url &lt;a href="http://pgfoundry.org/frs/download.php/2335/epsql.diff"&gt;http://pgfoundry.org/frs/download.php/2335/epsql.diff&lt;/a&gt;. This patch should be applied on postgresql 8.4 source:&lt;br /&gt;&lt;pre&gt;cd postgresql-8.4.0/src/bin/psql&lt;br /&gt;patch -p1 &amp;lt; epsql.diff&lt;br /&gt;make clean&lt;br /&gt;make all&lt;br /&gt;su&lt;br /&gt;make install&lt;br /&gt;&lt;/pre&gt;Enhanced psql has new metacommand &lt;span style="font-style: italic;"&gt;\lf&lt;/span&gt; and new formating option &lt;span style="font-style: italic;"&gt;linestyle&lt;/span&gt; (+2 new border styles).&lt;br /&gt;&lt;h2&gt;\lf&lt;/h2&gt;\lf print function's source code (with and without row numbers):&lt;br /&gt;&lt;pre&gt;postgres=# \lf foo(int)&lt;br /&gt;****    CREATE OR REPLACE FUNCTION public.foo(_a integer)&lt;br /&gt;****     RETURNS integer&lt;br /&gt;****     LANGUAGE plpgsql&lt;br /&gt;****    AS $function$&lt;br /&gt;1    BEGIN&lt;br /&gt;2      RETURN _a + 1;&lt;br /&gt;3    END;&lt;br /&gt;****    $function$&lt;br /&gt;&lt;br /&gt;postgres=# \lf- foo(int)&lt;br /&gt;CREATE OR REPLACE FUNCTION public.foo(_a integer)&lt;br /&gt;RETURNS integer&lt;br /&gt;LANGUAGE plpgsql&lt;br /&gt;AS $function$&lt;br /&gt;BEGIN&lt;br /&gt;RETURN _a + 1;&lt;br /&gt;END;&lt;br /&gt;$function$&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;smart wrap mode&lt;/h2&gt;original wrap mode:&lt;br /&gt;&lt;pre&gt;postgres=# \pset format wrapped&lt;br /&gt;Output format is wrapped.&lt;br /&gt;postgres=# select * from test;&lt;br /&gt;+--------------------------------------------------------------------------------------+&lt;br /&gt;|                                          a                                           |&lt;br /&gt;+--------------------------------------------------------------------------------------+&lt;br /&gt;| Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor inci |&lt;br /&gt;| didunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exerc |&lt;br /&gt;| itation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dol |&lt;br /&gt;| or in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.  |&lt;br /&gt;| Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt m |&lt;br /&gt;| ollit anim id est laborum.                                                           |&lt;br /&gt;+--------------------------------------------------------------------------------------+&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;modified wrap mode:&lt;br /&gt;&lt;pre&gt;postgres=# select * from test;&lt;br /&gt;+--------------------------------------------------------------------------------------+&lt;br /&gt;|                                          a                                           |&lt;br /&gt;+--------------------------------------------------------------------------------------+&lt;br /&gt;| Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor      |&lt;br /&gt;| incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud   |&lt;br /&gt;| exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute      |&lt;br /&gt;| irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla   |&lt;br /&gt;| pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia  |&lt;br /&gt;| deserunt mollit anim id est laborum.                                                 |&lt;br /&gt;+--------------------------------------------------------------------------------------+&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;Border styles&lt;br /&gt;&lt;/h2&gt;&lt;pre&gt;postgres=# \pset border 0&lt;br /&gt;Border style is 0.&lt;br /&gt;postgres=# \d pg_database&lt;br /&gt;Table "pg_catalog.pg_database"&lt;br /&gt;Column       Type    Modifiers&lt;br /&gt;------------- --------- ---------&lt;br /&gt;datname       name      not null&lt;br /&gt;datdba        oid       not null&lt;br /&gt;encoding      integer   not null&lt;br /&gt;datcollate    name      not null&lt;br /&gt;datctype      name      not null&lt;br /&gt;datistemplate boolean   not null&lt;br /&gt;datallowconn  boolean   not null&lt;br /&gt;datconnlimit  integer   not null&lt;br /&gt;datlastsysoid oid       not null&lt;br /&gt;datfrozenxid  xid       not null&lt;br /&gt;dattablespace oid       not null&lt;br /&gt;datconfig     text[] &lt;br /&gt;datacl        aclitem[]&lt;br /&gt;&lt;br /&gt;postgres=# \pset border 1&lt;br /&gt;Border style is 1.&lt;br /&gt;postgres=# \d pg_database&lt;br /&gt; Table "pg_catalog.pg_database"&lt;br /&gt; Column     |   Type    | Modifiers&lt;br /&gt;---------------+-----------+-----------&lt;br /&gt;datname       | name      | not null&lt;br /&gt;datdba        | oid       | not null&lt;br /&gt;encoding      | integer   | not null&lt;br /&gt;datcollate    | name      | not null&lt;br /&gt;datctype      | name      | not null&lt;br /&gt;datistemplate | boolean   | not null&lt;br /&gt;datallowconn  | boolean   | not null&lt;br /&gt;datconnlimit  | integer   | not null&lt;br /&gt;datlastsysoid | oid       | not null&lt;br /&gt;datfrozenxid  | xid       | not null&lt;br /&gt;dattablespace | oid       | not null&lt;br /&gt;datconfig     | text[]    |&lt;br /&gt;datacl        | aclitem[] |&lt;br /&gt;&lt;br /&gt;postgres=# \pset border 2&lt;br /&gt;Border style is 2.&lt;br /&gt;postgres=# \d pg_database&lt;br /&gt;  Table "pg_catalog.pg_database"&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;|    Column     |   Type    | Modifiers |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datname       | name      | not null  |&lt;br /&gt;| datdba        | oid       | not null  |&lt;br /&gt;| encoding      | integer   | not null  |&lt;br /&gt;| datcollate    | name      | not null  |&lt;br /&gt;| datctype      | name      | not null  |&lt;br /&gt;| datistemplate | boolean   | not null  |&lt;br /&gt;| datallowconn  | boolean   | not null  |&lt;br /&gt;| datconnlimit  | integer   | not null  |&lt;br /&gt;| datlastsysoid | oid       | not null  |&lt;br /&gt;| datfrozenxid  | xid       | not null  |&lt;br /&gt;| dattablespace | oid       | not null  |&lt;br /&gt;| datconfig     | text[]    |           |&lt;br /&gt;| datacl        | aclitem[] |           |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;&lt;br /&gt;postgres=# \pset border 3&lt;br /&gt;Border style is 3.&lt;br /&gt;postgres=# \d pg_database&lt;br /&gt;  Table "pg_catalog.pg_database"&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;|    Column     |   Type    | Modifiers |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datname       | name      | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datdba        | oid       | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| encoding      | integer   | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datcollate    | name      | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datctype      | name      | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datistemplate | boolean   | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datallowconn  | boolean   | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datconnlimit  | integer   | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datlastsysoid | oid       | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datfrozenxid  | xid       | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| dattablespace | oid       | not null  |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datconfig     | text[]    |           |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;| datacl        | aclitem[] |           |&lt;br /&gt;+---------------+-----------+-----------+&lt;br /&gt;&lt;br /&gt;postgres=# \pset border 4&lt;br /&gt;Border style is 4.&lt;br /&gt;postgres=# \d pg_database&lt;br /&gt;  Table "pg_catalog.pg_database"&lt;br /&gt;   Column     |   Type    | Modifiers&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datname       | name      | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datdba        | oid       | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;encoding      | integer   | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datcollate    | name      | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datctype      | name      | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datistemplate | boolean   | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datallowconn  | boolean   | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datconnlimit  | integer   | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datlastsysoid | oid       | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datfrozenxid  | xid       | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;dattablespace | oid       | not null&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datconfig     | text[]    |&lt;br /&gt;---------------+-----------+----------&lt;br /&gt;datacl        | aclitem[] |&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;Linestyles&lt;br /&gt;&lt;/h2&gt;&lt;pre&gt;postgres=# \pset linestyle 0&lt;br /&gt;Border line style is 0.&lt;br /&gt;postgres=# \d pg_user_mapping&lt;br /&gt;Table "pg_catalog.pg_user_mapping"&lt;br /&gt;+-----------+--------+-----------+&lt;br /&gt;|  Column   |  Type  | Modifiers |&lt;br /&gt;+-----------+--------+-----------+&lt;br /&gt;| umuser    | oid    | not null  |&lt;br /&gt;+-----------+--------+-----------+&lt;br /&gt;| umserver  | oid    | not null  |&lt;br /&gt;+-----------+--------+-----------+&lt;br /&gt;| umoptions | text[] |           |&lt;br /&gt;+-----------+--------+-----------+&lt;br /&gt;&lt;br /&gt;postgres=# \pset linestyle 1&lt;br /&gt;Border line style is 1.&lt;br /&gt;postgres=# \d pg_user_mapping&lt;br /&gt;Table "pg_catalog.pg_user_mapping"&lt;br /&gt;┌───────────┬────────┬───────────┐&lt;br /&gt;│  Column   │  Type  │ Modifiers │&lt;br /&gt;├───────────┼────────┼───────────┤&lt;br /&gt;│ umuser    │ oid    │ not null  │&lt;br /&gt;├───────────┼────────┼───────────┤&lt;br /&gt;│ umserver  │ oid    │ not null  │&lt;br /&gt;├───────────┼────────┼───────────┤&lt;br /&gt;│ umoptions │ text[] │           │&lt;br /&gt;└───────────┴────────┴───────────┘&lt;br /&gt;&lt;br /&gt;postgres=# \pset linestyle 2&lt;br /&gt;Border line style is 2.&lt;br /&gt;postgres=# \d pg_user_mapping&lt;br /&gt;Table "pg_catalog.pg_user_mapping"&lt;br /&gt;┌───────────┬────────┬───────────┐&lt;br /&gt;│  Column   │  Type  │ Modifiers │&lt;br /&gt;├───────────┴────────┴───────────┤&lt;br /&gt;│ umuser    │ oid    │ not null  │&lt;br /&gt;├───────────┼────────┼───────────┤&lt;br /&gt;│ umserver  │ oid    │ not null  │&lt;br /&gt;├───────────┼────────┼───────────┤&lt;br /&gt;│ umoptions │ text[] │           │&lt;br /&gt;└────────────────────────────────┘&lt;br /&gt;&lt;br /&gt;postgres=# \pset linestyle 3&lt;br /&gt;Border line style is 3.&lt;br /&gt;postgres=# \d pg_user_mapping&lt;br /&gt;Table "pg_catalog.pg_user_mapping"&lt;br /&gt;╔═══════════╤════════╤═══════════╗&lt;br /&gt;║  Column   │  Type  │ Modifiers ║&lt;br /&gt;╟───────────┼────────┼───────────╢&lt;br /&gt;║ umuser    │ oid    │ not null  ║&lt;br /&gt;╟───────────┼────────┼───────────╢&lt;br /&gt;║ umserver  │ oid    │ not null  ║&lt;br /&gt;╟───────────┼────────┼───────────╢&lt;br /&gt;║ umoptions │ text[] │           ║&lt;br /&gt;╚═══════════╧════════╧═══════════╝&lt;br /&gt;&lt;br /&gt;postgres=# \pset linestyle 4&lt;br /&gt;Border line style is 4.&lt;br /&gt;postgres=# \d pg_user_mapping&lt;br /&gt;Table "pg_catalog.pg_user_mapping"&lt;br /&gt;╔═══════════╤════════╤═══════════╗&lt;br /&gt;║  Column   │  Type  │ Modifiers ║&lt;br /&gt;╠═══════════╪════════╪═══════════╣&lt;br /&gt;║ umuser    │ oid    │ not null  ║&lt;br /&gt;╟───────────┼────────┼───────────╢&lt;br /&gt;║ umserver  │ oid    │ not null  ║&lt;br /&gt;╟───────────┼────────┼───────────╢&lt;br /&gt;║ umoptions │ text[] │           ║&lt;br /&gt;╚═══════════╧════════╧═══════════╝&lt;br /&gt;&lt;br /&gt;postgres=# \pset linestyle 5&lt;br /&gt;Border line style is 5.&lt;br /&gt;postgres=# \d pg_user_mapping&lt;br /&gt;Table "pg_catalog.pg_user_mapping"&lt;br /&gt;╔═══════════╦════════╦═══════════╗&lt;br /&gt;║  Column   ║  Type  ║ Modifiers ║&lt;br /&gt;╠═══════════╩════════╩═══════════╣&lt;br /&gt;║ umuser    │ oid    │ not null  ║&lt;br /&gt;╟───────────┼────────┼───────────╢&lt;br /&gt;║ umserver  │ oid    │ not null  ║&lt;br /&gt;╟───────────┼────────┼───────────╢&lt;br /&gt;║ umoptions │ text[] │           ║&lt;br /&gt;╚════════════════════════════════╝&lt;br /&gt;&lt;br /&gt;postgres=# \pset linestyle 6&lt;br /&gt;Border line style is 6.&lt;br /&gt;postgres=# \d pg_user_mapping&lt;br /&gt;Table "pg_catalog.pg_user_mapping"&lt;br /&gt;╔═══════════╦════════╦═══════════╗&lt;br /&gt;║  Column   ║  Type  ║ Modifiers ║&lt;br /&gt;╠═══════════╬════════╬═══════════╣&lt;br /&gt;║ umuser    ║ oid    ║ not null  ║&lt;br /&gt;╟───────────╫────────╫───────────╢&lt;br /&gt;║ umserver  ║ oid    ║ not null  ║&lt;br /&gt;╟───────────╫────────╫───────────╢&lt;br /&gt;║ umoptions ║ text[] ║           ║&lt;br /&gt;╚═══════════╩════════╩═══════════╝&lt;br /&gt;&lt;/pre&gt;Please, test it, use it.&lt;br /&gt;Regards&lt;br /&gt;Pavel Stehule&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-1835034213993508960?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/1835034213993508960/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=1835034213993508960' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1835034213993508960'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1835034213993508960'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/08/epsql-available.html' title='epsql available'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-5670859969956571850</id><published>2009-03-10T01:40:00.000-07:00</published><updated>2009-03-10T02:58:42.740-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='postgresql'/><title type='text'>Experimental psql</title><content type='html'>Hello&lt;br /&gt;&lt;br /&gt;I spent some on psql's face lifting. Some new features are really experimental. I don't expect early adopting it for core. It's more game for now. epsql will be available with release 8.4. What epsql can do?&lt;br /&gt;&lt;h3&gt;UTF8 boxes&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;one new border style, five new border lines styles&lt;/li&gt;&lt;/ul&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_AfangxPpXuY/SbYqBDdLaFI/AAAAAAAAAIM/nqvpgAPyJcY/s1600-h/screenshot2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 210px;" src="http://4.bp.blogspot.com/_AfangxPpXuY/SbYqBDdLaFI/AAAAAAAAAIM/nqvpgAPyJcY/s320/screenshot2.png" alt="" id="BLOGGER_PHOTO_ID_5311479008126003282" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;h3&gt;Stored function's listing&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;should help with debuging&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;postgres=# select test(10);&lt;br /&gt;ERROR:  division by zero&lt;br /&gt;CONTEXT:  PL/pgSQL function "test" line 3 at assignment&lt;br /&gt;&lt;br /&gt;postgres=# \lf test&lt;br /&gt;*** CREATE OR REPLACE FUNCTION public.test(a integer)&lt;br /&gt;***  RETURNS integer&lt;br /&gt;***  LANGUAGE plpgsql&lt;br /&gt;*** AS $function$&lt;br /&gt;1 declare b int;&lt;br /&gt;2 begin&lt;br /&gt;3   b := a/0;&lt;br /&gt;4   return a;&lt;br /&gt;5 end;&lt;br /&gt;*** $function$&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Cursor's support&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;allows using data from database for statement parameters&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-style: italic;"&gt;\fetch&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;postgres=# BEGIN;&lt;br /&gt;BEGIN&lt;br /&gt;postgres=# DECLARE c CURSOR FOR SELECT * FROM pg_database;&lt;br /&gt;DECLARE CURSOR&lt;br /&gt;postgres=# \fetch c \echo :datname :datcollate&lt;br /&gt;template1 cs_CZ.UTF-8&lt;br /&gt;postgres=# \fetch c \echo :datname :datcollate&lt;br /&gt;template0 cs_CZ.UTF-8&lt;br /&gt;postgres=# \fetch c \echo :datname :datcollate&lt;br /&gt;postgres cs_CZ.UTF-8&lt;br /&gt;postgres=# \fetch c \echo :datname :datcollate&lt;br /&gt;ERROR:  (not available)&lt;br /&gt;postgres=# COMMIT;&lt;br /&gt;COMMIT&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-style: italic;"&gt;\fetchall&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;postgres=# CREATE TABLE test1(a int); CREATE TABLE test2(a int);&lt;br /&gt;CREATE TABLE&lt;br /&gt;CREATE TABLE&lt;br /&gt;postgres=# BEGIN;&lt;br /&gt;BEGIN&lt;br /&gt;postgres=# DECLARE c CURSOR FOR SELECT tablename FROM pg_tables WHERE tablename LIKE 'test%';&lt;br /&gt;DECLARE CURSOR&lt;br /&gt;postgres=# \fetchall c DROP TABLE :tablename;&lt;br /&gt;DROP TABLE&lt;br /&gt;DROP TABLE&lt;br /&gt;postgres=# COMMIT;&lt;br /&gt;COMMIT&lt;br /&gt;postgres=#&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt; I invite any ideas, comments.&lt;br /&gt;&lt;br /&gt;Pavel&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-5670859969956571850?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/5670859969956571850/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=5670859969956571850' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/5670859969956571850'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/5670859969956571850'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/03/experimental-psql.html' title='Experimental psql'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_AfangxPpXuY/SbYqBDdLaFI/AAAAAAAAAIM/nqvpgAPyJcY/s72-c/screenshot2.png' height='72' width='72'/><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-3578243718588290736</id><published>2009-02-20T22:45:00.000-08:00</published><updated>2009-02-21T01:50:39.402-08:00</updated><title type='text'>8.3 migration helper</title><content type='html'>8.3 dropped some implicit casts. I didn't watch discus about it, but I am thinking so there are two reasons. First - it shows some programmer's mistakes, second - it warns against wrong written predicates. I can speak, so all it does well. When we migrated older application, we found lot of nonsense: typically numeric data are stored in varchar column or deprecated style (every constant is string literal) like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;any_numeric_column = 'any_number' ... -- deprecated&lt;br /&gt;any_numeric_column = any_number ... -- good style&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Peter Eisentraut wrote &lt;a href="http://petereisentraut.blogspot.com/2008/03/readding-implicit-casts-in-postgresql.html"&gt;module&lt;/a&gt; for 8.3, that enable all disabled implicit casts. This helps with migration, but doesn't help with question, where disabled casts are used in application. I wrote function, that adds warning to theses casts:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION generator_81_casts()&lt;br /&gt;RETURNS void AS $$&lt;br /&gt;DECLARE&lt;br /&gt;  src varchar[] := '{integer,smallint,oid,date,double precision,real,time with time zone, time without time zone, timestamp with time zone, interval,bigint,numeric,timestamp without time zone}';&lt;br /&gt;  fn varchar[] := '{int4out,int2out,oidout,date_out,float8out,float4out,timetz_out,time_out,timestamptz_out,interval_out,int8out,numeric_out,timestamp_out}';&lt;br /&gt;  fn_name varchar;&lt;br /&gt;  fn_msg varchar; fn_body varchar;&lt;br /&gt;BEGIN&lt;br /&gt;  FOR i IN array_lower(src,1)..array_upper(src,1) LOOP&lt;br /&gt;    fn_name := 'aux_cast_func_' || replace(src[i],' ','_') ||'_to_text';&lt;br /&gt;    fn_msg := '''using obsolete implicit casting from ' || src[i] || ' to text''';&lt;br /&gt;    fn_body := 'CREATE OR REPLACE FUNCTION '|| fn_name || '(' || src[i] ||') RETURNS text AS $_$ BEGIN RAISE WARNING '&lt;br /&gt;                  || fn_msg || ';RETURN textin(' || fn[i] || '($1)); END; $_$ LANGUAGE plpgsql IMMUTABLE';&lt;br /&gt;    EXECUTE fn_body;&lt;br /&gt;    -- for 8.1                                                                                                                                                                                 &lt;br /&gt;    --EXECUTE 'UPDATE pg_cast SET castfunc = ''' || fn_name || '''::regproc WHERE castsource = ''' || src[i] || '''::regtype AND casttarget = ''text''::regtype';                              &lt;br /&gt;   DROP CAST 'CREATE CAST (' || src[i] || ' AS text)';&lt;br /&gt;   EXECUTE 'CREATE CAST (' || src[i] || ' AS text) WITH FUNCTION ' || fn_name || '(' || src[i] || ') AS IMPLICIT';&lt;br /&gt;  END LOOP;&lt;br /&gt;  RETURN;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE plpgsql;&lt;br /&gt;SELECT generator_81_casts();&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-3578243718588290736?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/3578243718588290736/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=3578243718588290736' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3578243718588290736'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3578243718588290736'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2009/02/83-migration-helper.html' title='8.3 migration helper'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-4499037772090438559</id><published>2008-12-27T12:14:00.000-08:00</published><updated>2008-12-27T12:57:31.081-08:00</updated><title type='text'>DB2 SQL Procedures on Postgres? Yes, why not?</title><content type='html'>I ported &lt;em&gt;PL/pgPSM&lt;/em&gt; for &lt;em&gt;PostgreSQL 8.4&lt;/em&gt; this week. PL/pgPSM is implementation of SQL/PSM language to PostgreSQL space. SQL/PSM is procedural language from ANSI SQL standard. DB2 or MySQL use it. PL/pgPSM has some features from IBM implementation, but it isn't fully compatible. It isn't possible - PostgreSQL is little bit different than DB2. Relation between PL/pgPSM and SQL PL is like relation between PL/pgSQL and PL/SQL.&lt;h1&gt;Installation&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;download &lt;a href="http://pgfoundry.org/frs/download.php/2045/plpgpsm.tgz"&gt;source code&lt;/a&gt;,&lt;/li&gt;&lt;li&gt;extract it in PL directory in PostgreSQL 8.4 source code tree&lt;/li&gt;&lt;li&gt;compile and install it&lt;pre&gt;[pavel@localhost Desktop]$ mv plpgpsm.tgz ../src/pgsql/src/pl/&lt;br /&gt;[pavel@localhost Desktop]$ cd  ../src/pgsql/src/pl/&lt;br /&gt;[pavel@localhost pl]$ tar xvfz plpgpsm.tgz&lt;br /&gt;plpgpsm/Makefile&lt;br /&gt;plpgpsm/src/&lt;br /&gt;plpgpsm/src/pl_gram.c&lt;br /&gt;plpgpsm/src/pl_exec.c&lt;br /&gt;plpgpsm/src/gram.y&lt;br /&gt;plpgpsm/src/pl_funcs.c&lt;br /&gt;plpgpsm/src/plpgpsm.h&lt;br /&gt;plpgpsm/src/y.tab.h&lt;br /&gt;plpgpsm/src/sql/&lt;br /&gt;plpgpsm/src/sql/plpgpsm.sql&lt;br /&gt;plpgpsm/src/INSTALL.plpgpsm&lt;br /&gt;plpgpsm/src/Makefile&lt;br /&gt;plpgpsm/src/pl_handler.c&lt;br /&gt;plpgpsm/src/plerrcodes.h&lt;br /&gt;plpgpsm/src/scan.l&lt;br /&gt;plpgpsm/src/y.tab.c&lt;br /&gt;plpgpsm/src/pl_comp.c&lt;br /&gt;plpgpsm/src/expected/&lt;br /&gt;plpgpsm/src/expected/plpgpsm.out&lt;br /&gt;plpgpsm/src/pl_gram.h&lt;br /&gt;plpgpsm/src/pl_scan.c&lt;br /&gt;[pavel@localhost pl]$ cd plpgpsm/src/&lt;br /&gt;[pavel@localhost src]$ make all&lt;br /&gt;...&lt;br /&gt;[pavel@localhost src]$ su&lt;br /&gt;Heslo:&lt;br /&gt;[root@localhost src]# make install&lt;br /&gt;/bin/sh ../../../../config/mkinstalldirs '/usr/local/pgsql/lib'&lt;br /&gt;/bin/sh ../../../../config/install-sh -c -m 755  plpgpsm.so '/usr/local/pgsql/lib/plpgpsm.so'&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;register pl handler:&lt;br /&gt;&lt;pre&gt;[pavel@localhost src]$ psql template1&lt;br /&gt;psql (8.4devel)&lt;br /&gt;Type "help" for help.&lt;br /&gt;&lt;br /&gt;template1=# insert into pg_pltemplate&lt;br /&gt;select 'plpgpsm','t'::boolean, 't'::boolean, 'plpgpsm_call_handler','plpgpsm_validator','$libdir/plpgpsm',NULL&lt;br /&gt;from (&lt;br /&gt;select 'plpgpsm'&lt;br /&gt;except all&lt;br /&gt;select tmplname&lt;br /&gt;from pg_pltemplate&lt;br /&gt;) a;&lt;br /&gt;INSERT 0 0&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Check it:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[pavel@localhost src]$ pwd&lt;br /&gt;/home/pavel/src/pgsql/src/pl/plpgpsm/src&lt;br /&gt;[pavel@localhost src]$ make installcheck&lt;br /&gt;make -C ../../../../src/test/regress pg_regress&lt;br /&gt;make[1]: Entering directory `/home/pavel/src/pgsql/src/test/regress'&lt;br /&gt;make[1]: `pg_regress' is up to date.&lt;br /&gt;make[1]: Leaving directory `/home/pavel/src/pgsql/src/test/regress'&lt;br /&gt;../../../../src/test/regress/pg_regress --inputdir=. --psqldir= --dbname=pl_regression --load-language=plpgpsm plpgpsm&lt;br /&gt;(using postmaster on Unix socket, default port)&lt;br /&gt;============== dropping database "pl_regression"      ==============&lt;br /&gt;DROP DATABASE&lt;br /&gt;============== creating database "pl_regression"      ==============&lt;br /&gt;CREATE DATABASE&lt;br /&gt;ALTER DATABASE&lt;br /&gt;============== installing plpgpsm                     ==============&lt;br /&gt;CREATE LANGUAGE&lt;br /&gt;============== running regression test queries        ==============&lt;br /&gt;test plpgpsm              ... ok&lt;br /&gt;&lt;br /&gt;=====================&lt;br /&gt;All 1 tests passed.&lt;br /&gt;=====================&lt;/pre&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;h1&gt;Usage&lt;/h1&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Read some documentation &lt;a href="http://www.sqlpl-guide.com/"&gt;http://www.sqlpl-guide.com/&lt;/a&gt; and &lt;a href="http://www.pgsql.cz/index.php/SQL/PSM_Manual"&gt;http://www.pgsql.cz/index.php/SQL/PSM_Manual&lt;/a&gt;, &lt;a href="http://www.pgsql.cz/index.php/MySQL5:_Gentle_introduction_to_stored_procedures_of_MySQL5"&gt;http://www.pgsql.cz/index.php/MySQL5:_Gentle_introduction_to_stored_procedures_of_MySQL5&lt;/a&gt;  &lt;/li&gt;&lt;li&gt;Play with it and use it :)&lt;pre&gt;postgres=# CREATE OR REPLACE FUNCTION foo2(a integer)&lt;br /&gt;postgres-# RETURNS void AS&lt;br /&gt;postgres-# $$&lt;br /&gt;postgres$#   BEGIN&lt;br /&gt;postgres$#     DECLARE i integer DEFAULT 1;&lt;br /&gt;postgres$#     WHILE i &amp;lt;= a&lt;br /&gt;postgres$#     DO&lt;br /&gt;postgres$#       PRINT i;&lt;br /&gt;postgres$#       SET i = i + 1;&lt;br /&gt;postgres$#     END WHILE;&lt;br /&gt;postgres$#   END&lt;br /&gt;postgres$# $$ LANGUAGE plpgpsm;&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;postgres=# select foo2(3);&lt;br /&gt;NOTICE:  1&lt;br /&gt;NOTICE:  2&lt;br /&gt;NOTICE:  3&lt;br /&gt;foo2 ------&lt;br /&gt;&lt;br /&gt;(1 row)&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-4499037772090438559?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/4499037772090438559/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=4499037772090438559' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4499037772090438559'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4499037772090438559'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2008/12/db2-sql-procedures-on-postgres-yes-why.html' title='DB2 SQL Procedures on Postgres? Yes, why not?'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-6514806987633394946</id><published>2008-11-18T06:21:00.000-08:00</published><updated>2008-11-18T12:35:24.993-08:00</updated><title type='text'>plpgsql and temp. tables</title><content type='html'>I tested speed of three possible styles of work with temp tables inside stored procedures. Usually I preferred checking of existence table to exception's trapping. I didn't expect it, but exception's trapping is the best of all.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create or replace function test1() &lt;br /&gt;returns void as $$&lt;br /&gt;begin &lt;br /&gt;  drop table if exists omega; &lt;br /&gt;  create temp table omega(a integer); &lt;br /&gt;  insert into omega values(10); &lt;br /&gt;  if exists(select * from omega) then end if; &lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;&lt;br /&gt;create or replace function test2() &lt;br /&gt;returns void as $$&lt;br /&gt;begin &lt;br /&gt;  if exists(select * from pg_class where relname='omega' and pg_table_is_visible(oid)) then &lt;br /&gt;    delete from omega; &lt;br /&gt;  else &lt;br /&gt;    create temp table omega(a integer); &lt;br /&gt;  end if; &lt;br /&gt;  insert into omega values(10); &lt;br /&gt;  if exists(select * from omega) then end if; &lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;&lt;br /&gt;create or replace function test3() &lt;br /&gt;returns void as $$&lt;br /&gt;begin &lt;br /&gt;  begin &lt;br /&gt;    delete from omega; &lt;br /&gt;  exception &lt;br /&gt;    when others then &lt;br /&gt;      create temp table omega(a integer); &lt;br /&gt;  end; &lt;br /&gt;  insert into omega values(10); &lt;br /&gt;  if exists(select * from omega) then end if; &lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;test via pgbench&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Test1:&lt;br /&gt;transaction type: Custom query&lt;br /&gt;scaling factor: 1&lt;br /&gt;query mode: simple&lt;br /&gt;number of clients: 10&lt;br /&gt;number of transactions per client: 1000&lt;br /&gt;number of transactions actually processed: 10000/10000&lt;br /&gt;tps = 339.780441 (including connections establishing)&lt;br /&gt;tps = 340.172513 (excluding connections establishing)&lt;br /&gt;&lt;br /&gt;Test2:&lt;br /&gt;scaling factor: 1&lt;br /&gt;query mode: simple&lt;br /&gt;number of clients: 10&lt;br /&gt;number of transactions per client: 1000&lt;br /&gt;number of transactions actually processed: 10000/10000&lt;br /&gt;tps = 1891.021562 (including connections establishing)&lt;br /&gt;tps = 1907.533096 (excluding connections establishing)&lt;br /&gt;&lt;br /&gt;Test3:&lt;br /&gt;transaction type: Custom query&lt;br /&gt;scaling factor: 1&lt;br /&gt;query mode: simple&lt;br /&gt;number of clients: 10&lt;br /&gt;number of transactions per client: 1000&lt;br /&gt;number of transactions actually processed: 10000/10000&lt;br /&gt;tps = 2664.756569 (including connections establishing)&lt;br /&gt;tps = 2698.289177 (excluding connections establishing&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-6514806987633394946?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/6514806987633394946/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=6514806987633394946' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/6514806987633394946'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/6514806987633394946'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2008/11/plpgsql-and-temp-tables.html' title='plpgsql and temp. tables'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-8407945698538321110</id><published>2008-08-29T01:37:00.000-07:00</published><updated>2008-08-29T05:12:57.102-07:00</updated><title type='text'>updatable cursor's test</title><content type='html'>Hello&lt;br /&gt;&lt;br /&gt;I had to update every row of table with an result of external function. It was possibility for testing of some patterns:&lt;br /&gt;&lt;pre&gt;postgres=# create table testcursor(i integer primary key, v integer);&lt;br /&gt;NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "testcursor_pkey" for table "testcursor"&lt;br /&gt;CREATE TABLE&lt;br /&gt;postgres=# insert into testcursor select i, 0 from generate_series(1,100000) g(i);&lt;br /&gt;INSERT 0 100000&lt;br /&gt;postgres=# analyze testcursor;&lt;br /&gt;ANALYZE&lt;br /&gt;&lt;br /&gt;/* external function sample */&lt;br /&gt;create or replace function ext_fce(a integer, b integer)&lt;br /&gt;returns int as $&lt;br /&gt;declare r int;&lt;br /&gt;begin&lt;br /&gt;&lt;span style="font-style: italic;"&gt;/* protect section */&lt;/span&gt;&lt;br /&gt;begin&lt;br /&gt;  r := b;&lt;br /&gt;exception when others then&lt;br /&gt;  r := null;&lt;br /&gt;end;&lt;br /&gt;return r;&lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;&lt;/pre&gt;1. test - standard update statement&lt;br /&gt;&lt;pre&gt;postgres=# update testcursor set v = ext_fce(i, 30);&lt;br /&gt;UPDATE 100000&lt;br /&gt;Time: 4369,246 ms&lt;br /&gt;&lt;/pre&gt;2. test - update with updateable cursors&lt;br /&gt;&lt;pre&gt;create or replace function testc2(_v integer)&lt;br /&gt;returns void as $$&lt;br /&gt;declare&lt;br /&gt;c cursor for select i from testcursor;&lt;br /&gt;_i integer;&lt;br /&gt;begin&lt;br /&gt;open c;&lt;br /&gt;fetch c into _i;&lt;br /&gt;while found loop&lt;br /&gt;  update testcursor set v = ext_fce(_i, _v)&lt;br /&gt;    where current of c;&lt;br /&gt;  fetch c into _i;&lt;br /&gt;end loop;&lt;br /&gt;close c;&lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;postgres=# select testc2(20);&lt;br /&gt;testc2&lt;br /&gt;--------&lt;br /&gt;&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 8434,985 ms&lt;br /&gt;&lt;/pre&gt;3. test - update with PK&lt;br /&gt;&lt;pre&gt;create or replace function testc3(_v integer)&lt;br /&gt;returns void as $$&lt;br /&gt;declare _i integer;&lt;br /&gt;begin&lt;br /&gt;  for _i in select i from testcursor loop&lt;br /&gt;    update testcursor set v = ext_fce(_i,_v) where i = _i;&lt;br /&gt;  end loop;&lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;postgres=# select testc3(30);&lt;br /&gt;testc3&lt;br /&gt;--------&lt;br /&gt;&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;Time: 9959,209 ms&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Result:&lt;/span&gt; Using updateable cursor is about 90% slower than one statement. Usig pk is about 17% slower than updateable cursor. - So updateable cursors has sense and it is well to use it. But every iteration is much slower than one statement's update.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-8407945698538321110?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/8407945698538321110/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=8407945698538321110' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/8407945698538321110'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/8407945698538321110'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2008/08/updatable-cursors-test.html' title='updatable cursor&apos;s test'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-7003122615499482348</id><published>2008-08-27T06:32:00.000-07:00</published><updated>2008-08-27T07:37:31.939-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='8.4'/><category scheme='http://www.blogger.com/atom/ns#' term='postgresql'/><title type='text'>default parameters for PL functions</title><content type='html'>Hello&lt;br /&gt;&lt;br /&gt;I completed one task - defaults for PL functions. Using is simple - it is same as Firebird's 2.x defaults.&lt;br /&gt;&lt;pre&gt;postgres=# create or replace function x1(int = 1,int = 2,int= 3)&lt;br /&gt;        returns int as $$&lt;br /&gt;          select $1+$2+$3;&lt;br /&gt;        $$ language sql;&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;postgres=# select x1();&lt;br /&gt;x1&lt;br /&gt;----&lt;br /&gt;6&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# select x1(10);;&lt;br /&gt;x1&lt;br /&gt;----&lt;br /&gt;15&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# select x1(10,20);&lt;br /&gt;x1&lt;br /&gt;----&lt;br /&gt;33&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# select x1(10,20,30);&lt;br /&gt;x1&lt;br /&gt;----&lt;br /&gt;60&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;This is first step before named parameters feature - and less controversy. Second step will be difficult - there are two opinions about named parameter's syntax: variant a) using Oracle's syntax &lt;code&gt;name =&gt; expression&lt;/code&gt; and variant b) use own syntax based on keyword "AS" &lt;code&gt;expression AS name&lt;/code&gt;. I prefer variant @a - I thing so it's more readable (SQL use AS for labeling). Variant @b is safe from compatibility views. There was discussion on pg_hackers - without any conclusion. So I hope so at least defaults will be committed.&lt;br /&gt;&lt;br /&gt;bye&lt;br /&gt;Pavel&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-7003122615499482348?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/7003122615499482348/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=7003122615499482348' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/7003122615499482348'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/7003122615499482348'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2008/08/default-parameters-for-pl-functions.html' title='default parameters for PL functions'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-7449458148004287481</id><published>2008-08-25T02:54:00.000-07:00</published><updated>2008-08-26T04:39:06.600-07:00</updated><title type='text'>Using cursors for generating cross tables</title><content type='html'>I am working on procedure support in PostgreSQL more than one year. Missing procedures is one disadvantage of PostgreSQL. Because procedures are not called from SELECT statement, then it couldn't respect some rules like functions. Procedures are mainly used for explicit transaction controlling and for generating mutable results (like dynamic record set or stacked record set). Dynamic record set is interesting feature when we nothing know about result set's columns. It's typical for cross tables. Because PostgreSQL doesn't support procedures, we cannot return dynamic (mutable number of columns) queries directly, but we can return dynamic cursor. This method of generating cross tables is inspired by Roland Bauman's &lt;a href="http://rpbouman.blogspot.com/"&gt;http://rpbouman.blogspot.com&lt;/a&gt; blog.&lt;br /&gt;Cross table is based on query's pattern:&lt;br /&gt;&lt;pre&gt;SELECT shop,&lt;br /&gt;SUM(CASE gender WHEN 'f' THEN salary ELSE 0 END) AS f,&lt;br /&gt;SUM(CASE gender WHEN 'm' THEN salary ELSE 0 END) AS m,&lt;br /&gt;SUM(salary) AS total&lt;br /&gt;FROM employees INNER JOIN shops USING (shop_id)&lt;br /&gt;GROUP BY shop&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;for data:&lt;br /&gt;&lt;pre&gt;CREATE TABLE employees (&lt;br /&gt;id serial PRIMARY KEY,&lt;br /&gt;shop_id int,&lt;br /&gt;gender char(1),&lt;br /&gt;name varchar(32),&lt;br /&gt;salary int&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;CREATE TABLE shops (&lt;br /&gt;id serial PRIMARY KEY,&lt;br /&gt;shop varchar(32)&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;INSERT INTO shops(shop) VALUES('Zurich'),('New York'),('London');&lt;br /&gt;&lt;br /&gt;INSERT INTO employees (shop_id, gender, name, salary)&lt;br /&gt;VALUES&lt;br /&gt;(1, 'm', 'Jon Simpson', 4500),&lt;br /&gt;(1, 'f', 'Barbara Breitenmoser',4700),&lt;br /&gt;(2, 'f', 'KirstenRuegg',5600),&lt;br /&gt;(3, 'm', 'Ralp Teller',5100),&lt;br /&gt;(3, 'm', 'Peter Jonson',4700);&lt;br /&gt;&lt;/pre&gt;I write function, that generate necessary SELECT statement and open dynamic cursor.&lt;br /&gt;&lt;pre&gt;CREATE OR REPLACE FUNCTION do_cross_cursor(dimx_name varchar,&lt;br /&gt;    dimx_source varchar, dimy_name varchar,&lt;br /&gt;    dimy_source varchar, expr varchar)&lt;br /&gt;RETURNS refcursor AS $$&lt;br /&gt;DECLARE&lt;br /&gt;col_list text[] := '{}';&lt;br /&gt;query text;&lt;br /&gt;r RECORD;&lt;br /&gt;result refcursor := 'result';&lt;br /&gt;BEGIN&lt;br /&gt;FOR r IN EXECUTE 'SELECT DISTINCT '&lt;br /&gt;  || dimx_name || '::text AS val ' || dimx_source&lt;br /&gt;LOOP&lt;br /&gt;col_list := array_append(col_list, 'SUM(CASE ' || dimx_name&lt;br /&gt; || ' WHEN ' || quote_literal(r.val) || ' THEN ' || expr&lt;br /&gt; || ' ELSE 0 END) AS ' || quote_ident(r.val) || '');&lt;br /&gt;END LOOP;&lt;br /&gt;query := 'SELECT ' || dimy_name || ', '&lt;br /&gt; || array_to_string(col_list, ',')&lt;br /&gt; || ', SUM(' || expr || ') AS Total '&lt;br /&gt; || dimy_source || ' GROUP BY ' || dimy_name;&lt;br /&gt;OPEN result NO SCROLL FOR EXECUTE query;&lt;br /&gt;RETURN result;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE plpgsql STRICT;&lt;br /&gt;&lt;/pre&gt;Because cursors should be used only in transaction, I have to use explicit transaction:&lt;br /&gt;&lt;pre&gt;BEGIN;&lt;br /&gt;SELECT do_cross_cursor('gender', 'FROM employees','shop',&lt;br /&gt;     'FROM employees e JOIN shops s ON s.id = e.shop_id',&lt;br /&gt;     'salary');&lt;br /&gt;FETCH ALL FROM result;&lt;br /&gt;END;&lt;br /&gt;&lt;br /&gt;BEGIN;&lt;br /&gt;SELECT do_cross_cursor('shop', 'FROM shops','gender',&lt;br /&gt;      'FROM employees e JOIN shops s ON s.id = e.shop_id',&lt;br /&gt;      'salary');&lt;br /&gt;FETCH ALL FROM result;&lt;br /&gt;END;&lt;br /&gt;&lt;/pre&gt;There is result:&lt;br /&gt;&lt;pre&gt;postgres=# BEGIN;&lt;br /&gt;BEGIN&lt;br /&gt;postgres=#   SELECT do_cross_cursor('gender', 'FROM employees',&lt;br /&gt;  'shop',&lt;br /&gt;  'FROM employees e JOIN shops s ON s.id = e.shop_id', 'salary');&lt;br /&gt;do_cross_cursor&lt;br /&gt;-----------------&lt;br /&gt;result&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=#   FETCH ALL FROM result;&lt;br /&gt; shop   |  m   |  f   | total&lt;br /&gt;----------+------+------+-------&lt;br /&gt;New York |    0 | 5600 |  5600&lt;br /&gt;Zurich   | 4500 | 4700 |  9200&lt;br /&gt;London   | 9800 |    0 |  9800&lt;br /&gt;(3 rows)&lt;br /&gt;&lt;br /&gt;postgres=# END;BEGIN;&lt;br /&gt;COMMIT&lt;br /&gt;BEGIN&lt;br /&gt;postgres=#   SELECT do_cross_cursor('shop', 'FROM shops','gender',&lt;br /&gt; 'FROM employees e JOIN shops s ON s.id = e.shop_id', 'salary');&lt;br /&gt;do_cross_cursor&lt;br /&gt;-----------------&lt;br /&gt;result&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=#   FETCH ALL FROM result;&lt;br /&gt;gender | New York | Zurich | London | total&lt;br /&gt;--------+----------+--------+--------+-------&lt;br /&gt;m      |        0 |   4500 |   9800 | 14300&lt;br /&gt;f      |     5600 |   4700 |      0 | 10300&lt;br /&gt;(2 rows)&lt;br /&gt;&lt;br /&gt;postgres=# END;&lt;br /&gt;COMMIT&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-7449458148004287481?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/7449458148004287481/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=7449458148004287481' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/7449458148004287481'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/7449458148004287481'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2008/08/using-cursors-for-generating-cross.html' title='Using cursors for generating cross tables'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-4775408221716434205</id><published>2008-06-18T05:32:00.000-07:00</published><updated>2008-06-18T05:43:10.962-07:00</updated><title type='text'>recursive queries only with SQL procedures</title><content type='html'>I tested possibility of recursive queries only with SQL language (and SQL procedures)&lt;br /&gt;&lt;pre&gt;postgres=# select * from rec_src;&lt;br /&gt;id | parent |   v&lt;br /&gt;----+--------+--------&lt;br /&gt; 2 |      1 | rodic&lt;br /&gt; 3 |      1 | rodic&lt;br /&gt; 4 |      2 | dite&lt;br /&gt; 5 |      2 | dite&lt;br /&gt; 6 |      3 | dite&lt;br /&gt; 7 |      6 | vnouce&lt;br /&gt; 1 |      0 | root&lt;br /&gt;(7 rows)&lt;br /&gt;&lt;/pre&gt;It's ugly, but it works :)&lt;pre&gt;create or replace function empty2null(anyarray)&lt;br /&gt;returns anyarray as $$&lt;br /&gt; select case&lt;br /&gt;            when array_upper($1,1) is null&lt;br /&gt;            then null&lt;br /&gt;            else $1 end;&lt;br /&gt;$$ language sql strict immutable;&lt;br /&gt;&lt;br /&gt;create or replace function search_path(int)&lt;br /&gt;returns varchar as $$&lt;br /&gt; select case&lt;br /&gt;            when $1 is null&lt;br /&gt;            then ''&lt;br /&gt;            else search_path((select parent&lt;br /&gt;                                 from rec_src&lt;br /&gt;                                where id = $1)) || '.'|| $1 end;&lt;br /&gt;$$ language sql;&lt;br /&gt;&lt;br /&gt;create or replace function ls(int[], int,&lt;br /&gt;                             out id int, out parent int, out path varchar, out v varchar )&lt;br /&gt;returns setof record as $$&lt;br /&gt; select id, parent, search_path(id), repeat('  ',$2)|| v&lt;br /&gt;    from rec_src&lt;br /&gt;   where parent = any($1)&lt;br /&gt; union all&lt;br /&gt; select id, parent, path, v from ls(empty2null(array(select id&lt;br /&gt;                                                        from rec_src&lt;br /&gt;                                                       where parent = any($1))),&lt;br /&gt;                                    $2 + 1);&lt;br /&gt;$$ language sql volatile strict;&lt;br /&gt;postgres=# select * from ls(array[0], 0) order by path;&lt;br /&gt;id | parent |    path    |      v&lt;br /&gt;----+--------+------------+--------------&lt;br /&gt; 1 |      0 | .0.1       | root&lt;br /&gt; 2 |      1 | .0.1.2     |   rodic&lt;br /&gt; 4 |      2 | .0.1.2.4   |     dite&lt;br /&gt; 5 |      2 | .0.1.2.5   |     dite&lt;br /&gt; 3 |      1 | .0.1.3     |   rodic&lt;br /&gt; 6 |      3 | .0.1.3.6   |     dite&lt;br /&gt; 7 |      6 | .0.1.3.6.7 |       vnouce&lt;br /&gt;(7 rows)&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-4775408221716434205?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/4775408221716434205/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=4775408221716434205' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4775408221716434205'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4775408221716434205'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2008/06/recursive-queries-only-with-sql.html' title='recursive queries only with SQL procedures'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-1565319940352821930</id><published>2008-06-03T01:12:00.000-07:00</published><updated>2009-08-18T05:53:31.940-07:00</updated><title type='text'>EXECUTE USING feature in PostgreSQL 8.4</title><content type='html'>Hello,&lt;br /&gt;&lt;br /&gt;My motivation for implementation USING feature in EXECUTE was simplifycation and securisation of dynamic SQL. I didn't thing about execution plans. I am speaking on plpgsql's lectures about risks related to dynamic SQL (mainly security risks). Prepared statements are safe, but has some gotcha &lt;a href="http://www.depesz.com/index.php/2008/05/10/prepared-statements-gotcha/"&gt;http://www.depesz.com/index.php/2008/05/10/prepared-statements-gotcha/&lt;/a&gt; , so there are some situations, where prepared statements are suboptimal. In plpgsql we have to use dynamic SQL&lt;br /&gt;&lt;pre&gt;DECLARE&lt;br /&gt;a integer;&lt;br /&gt;r integer;&lt;br /&gt;BEGIN&lt;br /&gt;-- prepared statement&lt;br /&gt;SELECT INTO r * FROM test WHERE c = a;&lt;br /&gt;-- classic dynamic SQL witg risk of SQL injection&lt;br /&gt;EXECUTE 'SELECT * FROM test WHERE c = ' || a INTO r;&lt;br /&gt;-- dynamic SQL with USING (secure and with well execution plan)&lt;br /&gt;EXECUTE 'SELECT * FROM test WHERE c = $1' INTO r USING a;&lt;br /&gt;&lt;/pre&gt;When we use USING clause, then dynamic SQL are secure (and readable too) . Every evalaution generate new execution plan with knowleage actual params. It's disadvantage (it's need some time - so don't use it in cycles), and advantage (generated plan will be propably better - not allways (sometimes we must play statistic alchemy game)).&lt;br /&gt;&lt;br /&gt;I unlike SELECT composition. It's bad style:&lt;br /&gt;&lt;pre&gt;CREATE OR REPLACE FUNCTION get_names(age integer, class integer)&lt;br /&gt;RETURNS SETOF persons AS $$&lt;br /&gt;DECLARE sql varchar = 'SELECT * FROM persons ';&lt;br /&gt;BEGIN&lt;br /&gt;IF age IS NOT NULL THEN&lt;br /&gt; sql := sql || 'WHERE age = ' || age;&lt;br /&gt;END IF;&lt;br /&gt;IF class IS NOT NULL THEN&lt;br /&gt; IF strpos(sql, 'WHERE') &lt;&gt; 0 THEN&lt;br /&gt;   sql := sql || ' AND ';&lt;br /&gt; ELSE&lt;br /&gt;   sql := sql || 'WHERE ';&lt;br /&gt; END IF;&lt;br /&gt; sql := sql || ' class = ' || class;&lt;br /&gt;END IF;&lt;br /&gt;RETURN QUERY EXECUTE sql;&lt;br /&gt;END $$ LANGUAGE plpgsql;&lt;br /&gt;&lt;/pre&gt;With &lt;span style="font-style: italic;"&gt;USING&lt;/span&gt; clause I should to write:&lt;br /&gt;&lt;pre&gt;..&lt;br /&gt;BEGIN&lt;br /&gt;RETURN QUERY EXECUTE&lt;br /&gt;  'SELECT * FROM persons&lt;br /&gt;     WHERE ($1 IS NULL or age = $1)&lt;br /&gt;            AND ($2 IS NULL or class = $2)&lt;br /&gt;USING age, class;&lt;br /&gt;END; $$ LANGUAGE plpgsql;&lt;br /&gt;&lt;/pre&gt;Optimaliser do predicat's reduction self.&lt;br /&gt;&lt;br /&gt;Tom Lane demonstarated other example of EXECUTE USING:&lt;br /&gt;&lt;pre&gt;create or replace function foo() returns trigger as $$&lt;br /&gt;declare&lt;br /&gt;t text;&lt;br /&gt;begin&lt;br /&gt;for i in 1 .. tg_argv[0] loop&lt;br /&gt; execute 'select cast ( $1 . ' || tg_argv[i] || ' as text)'&lt;br /&gt;   into t using new;&lt;br /&gt; raise notice '% = %', tg_argv[i], t;&lt;br /&gt;end loop;&lt;br /&gt;return new;&lt;br /&gt;end $$ language plpgsql;&lt;br /&gt;&lt;/pre&gt;This code shows values of all field of NEW record in trigger's body. It wasn't possible before EXECUTE USING in plpgsql.&lt;br /&gt;&lt;br /&gt;Any EXECUTE in plpgsql is door to you system for &lt;span style="font-style: italic;"&gt;SQL injection&lt;/span&gt;. With EXECUTE USING your procedures can be &lt;span style="font-weight: bold;"&gt;100% safe&lt;/span&gt;.&lt;br /&gt;&lt;pre&gt;-- &lt;span style="font-weight: bold;"&gt;very unsafe function&lt;/span&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION foo(_tablename varchar, _parameter varchar)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;DECLARE _r int;&lt;br /&gt;BEGIN&lt;br /&gt; EXECUTE 'SELECT c FROM ' || _tablename || ' WHERE x=''' || _parameter || '''' INTO _r;&lt;br /&gt; RETURN _r;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE plpgsql STRICT;&lt;br /&gt;&lt;br /&gt;-- safe function&lt;br /&gt;CREATE OR REPLACE FUNCTION foo(_tablename varchar, _parameter varchar)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;DECLARE _r int;&lt;br /&gt;BEGIN&lt;br /&gt; EXECUTE 'SELECT c FROM ' || quote_ident(_tablename) || ' WHERE x='&lt;br /&gt;                                || quote_literal(_parameter) || INTO _r;&lt;br /&gt; RETURN _r;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE plpgsql STRICT;&lt;br /&gt;&lt;br /&gt;-- bullet proof function&lt;br /&gt;CREATE OR REPLACE FUNCTION foo(_tablename varchar, _parameter varchar)&lt;br /&gt;RETURNS int AS $$&lt;br /&gt;DECLARE _r int;&lt;br /&gt;BEGIN&lt;br /&gt; EXECUTE 'SELECT c FROM ' || quote_ident(_tablename)::regclass || ' WHERE x=$1' &lt;br /&gt;                                USING _parameter || INTO _r;&lt;br /&gt; RETURN _r;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE plpgsql STRICT;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-1565319940352821930?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/1565319940352821930/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=1565319940352821930' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1565319940352821930'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/1565319940352821930'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2008/06/execute-using-feature-in-postgresql-84.html' title='EXECUTE USING feature in PostgreSQL 8.4'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-4134368970969463709</id><published>2007-12-01T09:29:00.000-08:00</published><updated>2007-12-01T10:03:47.888-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plperl'/><category scheme='http://www.blogger.com/atom/ns#' term='postgresql'/><title type='text'>Using $_SHARED as table cache in plperl</title><content type='html'>Oracle has new &lt;a href="http://www.oracle.com/technology/oramag/oracle/07-sep/o57plsql.html"&gt;function result cache&lt;/a&gt;. It's nice idea. There are some use cases, mostly in www applications. There are nothing similar in PostgreSQL. I found simple similar solution in plperl (with important disadvantage to Oracle solution). It works, but without any pooling mechanisms its not really effective.&lt;br /&gt;&lt;br /&gt;I created some tables:&lt;br /&gt;&lt;pre&gt;CREATE TABLE Books(&lt;br /&gt;id serial PRIMARY KEY,&lt;br /&gt;name VARCHAR(20));&lt;br /&gt;&lt;br /&gt;CREATE TABLE Sale(&lt;br /&gt;book_id integer REFERENCES Books(id),&lt;br /&gt;inserted timestamp DEFAULT(CURRENT_TIMESTAMP)&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;INSERT INTO Books VALUES(1,'Dracula');&lt;br /&gt;INSERT INTO Books VALUES(2,'Nosferatu');&lt;br /&gt;INSERT INTO Books VALUES(3,'Bacula');&lt;br /&gt;&lt;br /&gt;INSERT INTO Sale VALUES(1, '2007-10-11');&lt;br /&gt;INSERT INTO Sale VALUES(2, '2007-10-12');&lt;br /&gt;INSERT INTO Sale VALUES(2, '2007-10-13');&lt;br /&gt;INSERT INTO Sale VALUES(3, '2007-10-10');&lt;br /&gt;&lt;/pre&gt;and I created function Top10Books that returns top ten books.&lt;br /&gt;&lt;pre&gt;-- Top10&lt;br /&gt;CREATE OR REPLACE FUNCTION Top10Books(IN date, OUT ordr integer, OUT name varchar(20))&lt;br /&gt;RETURNS SETOF RECORD&lt;br /&gt;AS $$&lt;br /&gt;BEGIN&lt;br /&gt;ordr := 0;&lt;br /&gt;FOR name IN SELECT b.name&lt;br /&gt;              FROM Books b&lt;br /&gt;                   JOIN&lt;br /&gt;                   Sale s&lt;br /&gt;                   ON b.id = s.book_id&lt;br /&gt;             WHERE s.inserted BETWEEN date_trunc('month', $1)&lt;br /&gt;                                  AND date_trunc('month', $1)&lt;br /&gt;           + interval '1month' - interval '1day'&lt;br /&gt;             GROUP BY b.name&lt;br /&gt;             ORDER BY count(*) DESC&lt;br /&gt;             LIMIT 10&lt;br /&gt;LOOP&lt;br /&gt;ordr := ordr + 1;&lt;br /&gt;RETURN NEXT;&lt;br /&gt;END LOOP;&lt;br /&gt;RETURN;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE plpgsql;&lt;br /&gt;&lt;/pre&gt;One my customer put similar function on every page on some site (with really destructive impact to load on db server. Correct solution is to use some php cache or some similar tool. With plperl I am able cache result in Postgres too:&lt;br /&gt;&lt;pre&gt;CREATE OR REPLACE FUNCTION Top10BooksCached(IN date, IN bool,&lt;br /&gt;       OUT ordr integer, OUT name varchar(20))&lt;br /&gt;RETURNS SETOF RECORD&lt;br /&gt;AS $$&lt;br /&gt;return $_SHARED{tableof_top10book}&lt;br /&gt;    if (defined ($_SHARED{tableof_top10book}) and not (defined($_[1]) and $_[1] eq "t"));&lt;br /&gt;if (not defined($_SHARED{plan_for_top10books}))&lt;br /&gt;{&lt;br /&gt;   $_SHARED{plan_for_top10books} = spi_prepare(&lt;br /&gt;           'SELECT b.name                                                                                                                                                                                                &lt;br /&gt;              FROM Books b                                                                                                                                                                                               &lt;br /&gt;                   JOIN                                                                                                                                                                                                  &lt;br /&gt;                   Sale s                                                                                                                                                                                                &lt;br /&gt;                   ON b.id = s.book_id                                                                                                                                                                                   &lt;br /&gt;             WHERE s.inserted BETWEEN date_trunc(\'month\', $1)                                                                                                                                                          &lt;br /&gt;                                  AND date_trunc(\'month\', $1)&lt;br /&gt;    + interval \'1month\' - interval \'1day\'                                                                                                                &lt;br /&gt;             GROUP BY b.name                                                                                                                                                                                             &lt;br /&gt;             ORDER BY count(*) DESC                                                                                                                                                                                      &lt;br /&gt;             LIMIT 10' , 'DATE');&lt;br /&gt;}&lt;br /&gt;my $row;&lt;br /&gt;my $i = 0;&lt;br /&gt;my $heap;&lt;br /&gt;my $sth = spi_query_prepared($_SHARED{plan_for_top10books}, $_[0]);&lt;br /&gt;while (defined ($row = spi_fetchrow($sth))) {&lt;br /&gt;    push @$heap, {ordr =&gt; ++$i, name =&gt; $row-&gt;{name}}&lt;br /&gt;}&lt;br /&gt;$_SHARED{tableof_top10book} =  $heap ;&lt;br /&gt;return $_SHARED{tableof_top10book};&lt;br /&gt;$$ LANGUAGE plperlu;&lt;br /&gt;&lt;/pre&gt;or with cache expiration&lt;br /&gt;&lt;pre&gt;CREATE OR REPLACE FUNCTION Top10BooksCached(IN date, IN integer,&lt;br /&gt;     OUT ordr integer, OUT name varchar(20))&lt;br /&gt;RETURNS SETOF RECORD&lt;br /&gt;AS $$&lt;br /&gt;return $_SHARED{tableof_top10book}&lt;br /&gt;    if (defined ($_SHARED{tableof_top10book})&lt;br /&gt;            and defined($_SHARED{actualised_top10book})&lt;br /&gt;            and ($_SHARED{actualised_top10book} + $_[1] &gt; time));&lt;br /&gt;if (not defined($_SHARED{plan_for_top10books}))&lt;br /&gt;{&lt;br /&gt;   $_SHARED{plan_for_top10books} = spi_prepare(&lt;br /&gt;           'SELECT b.name                                                                                                                                                                                                &lt;br /&gt;              FROM Books b                                                                                                                                                                                               &lt;br /&gt;                   JOIN                                                                                                                                                                                                  &lt;br /&gt;                   Sale s                                                                                                                                                                                                &lt;br /&gt;                   ON b.id = s.book_id                                                                                                                                                                                   &lt;br /&gt;             WHERE s.inserted BETWEEN date_trunc(\'month\', $1)                                                                                                                                                          &lt;br /&gt;                                  AND date_trunc(\'month\', $1)&lt;br /&gt;    + interval \'1month\' - interval \'1day\'                                                                                                                &lt;br /&gt;             GROUP BY b.name                                                                                                                                                                                             &lt;br /&gt;             ORDER BY count(*) DESC                                                                                                                                                                                      &lt;br /&gt;             LIMIT 10' , 'DATE');&lt;br /&gt;}&lt;br /&gt;my $row;&lt;br /&gt;my $i = 0;&lt;br /&gt;my $heap;&lt;br /&gt;my $sth = spi_query_prepared($_SHARED{plan_for_top10books}, $_[0]);&lt;br /&gt;while (defined ($row = spi_fetchrow($sth))) {&lt;br /&gt;    push @$heap, {ordr =&gt; ++$i, name =&gt; $row-&gt;{name}}&lt;br /&gt;}&lt;br /&gt;$_SHARED{tableof_top10book} =  $heap ;&lt;br /&gt;$_SHARED{actualised_top10book} = time;&lt;br /&gt;return $_SHARED{tableof_top10book};&lt;br /&gt;$$ LANGUAGE plperlu;&lt;br /&gt;&lt;/pre&gt;An usage is simple:&lt;br /&gt;&lt;pre&gt;-- first call is slow&lt;br /&gt;postgres=# select * from Top10BooksCached(current_date, 300);&lt;br /&gt;ordr |   name&lt;br /&gt;------+-----------&lt;br /&gt;1 | Nosferatu&lt;br /&gt;2 | Bacula&lt;br /&gt;3 | Dracula&lt;br /&gt;(3 rows)&lt;br /&gt;&lt;br /&gt;Time: 128,965 ms&lt;br /&gt;-- second call is fast&lt;br /&gt;postgres=# select * from Top10BooksCached(current_date, 300);&lt;br /&gt;ordr |   name&lt;br /&gt;------+-----------&lt;br /&gt;1 | Nosferatu&lt;br /&gt;2 | Bacula&lt;br /&gt;3 | Dracula&lt;br /&gt;(3 rows)&lt;br /&gt;&lt;br /&gt;Time: 11,911 ms&lt;br /&gt;&lt;/pre&gt;Use it carefully! It's not good for big tables, and what more, this function can returns out-of-date values.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-4134368970969463709?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/4134368970969463709/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=4134368970969463709' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4134368970969463709'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/4134368970969463709'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2007/12/using-shared-as-table-cache-in-plperl.html' title='Using $_SHARED as table cache in plperl'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-3623624518439266252</id><published>2007-11-20T20:15:00.000-08:00</published><updated>2007-11-20T22:30:45.900-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plpgsql'/><category scheme='http://www.blogger.com/atom/ns#' term='postgresql'/><title type='text'>Stacked recordset (multirecordset)</title><content type='html'>So I am little bit far now. Multirecorsets are available with current protocol, but some changes in libpq are necessary :(. Procedure's output can be much more dynamic than from SRF functions.  There are not limits. I wrote small regress test. Its based on Roland Bauman's sample (that was be used in &lt;a href="http://www.pgsql.cz/index.php/MySQL5:_Gentle_introduction_to_stored_procedures_of_MySQL5"&gt;http://www.pgsql.cz/index.php/MySQL5:_Gentle_introduction_to_stored_procedures_of_MySQL5&lt;/a&gt;).&lt;br /&gt;&lt;pre&gt;CREATE TYPE gender AS ENUM('m', 'f');&lt;br /&gt;&lt;br /&gt;CREATE TABLE employees (&lt;br /&gt;id serial PRIMARY KEY,&lt;br /&gt;shop_id int,&lt;br /&gt;gender gender,&lt;br /&gt;name varchar(32),&lt;br /&gt;salary int&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;CREATE TABLE shops (&lt;br /&gt;id serial PRIMARY KEY,&lt;br /&gt;shop varchar(32)&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;INSERT INTO shops (shop)&lt;br /&gt;VALUES ('Zurich'), ('New York'), ('London');&lt;br /&gt;&lt;br /&gt;INSERT INTO employees (shop_id, gender, name, salary)&lt;br /&gt;VALUES&lt;br /&gt;(1, 'm', 'Jon Simpson', 4500),&lt;br /&gt;(1, 'f', 'Barbara Breitenmoser', 4700),&lt;br /&gt;(2, 'f', 'Kirsten Ruegg', 5600),&lt;br /&gt;(3, 'm', 'Ralph Teller', 5100),&lt;br /&gt;(3, 'm', 'Peter Jonson', 5200);&lt;br /&gt;&lt;/pre&gt;Procedure crosstab generate and execute SQL like&lt;br /&gt;&lt;pre&gt;SELECT shop,&lt;br /&gt;SUM(CASE gender WHEN 'f' THEN salary ELSE 0 END) AS f,&lt;br /&gt;SUM(CASE gender WHEN 'm' THEN salary ELSE 0 END) AS m,&lt;br /&gt;SUM(salary) AS total&lt;br /&gt;FROM employees e INNER JOIN shops s ON e.shop_id = s.id&lt;br /&gt;GROUP BY shop&lt;br /&gt;&lt;/pre&gt;Source code:&lt;br /&gt;&lt;pre&gt;CREATE OR REPLACE procedure crosstab(dimx_name varchar(32), dimx_source varchar(32),&lt;br /&gt;                                  dimy_name varchar(32), dimy_source varchar(32),&lt;br /&gt;                                  expr varchar(32)) AS&lt;br /&gt;$$&lt;br /&gt;DECLARE&lt;br /&gt;cols_expr varchar;&lt;br /&gt;xtab_expr varchar;&lt;br /&gt;BEGIN&lt;br /&gt; EXECUTE  'SELECT array_to_string(ARRAY(SELECT ''SUM(CASE ' || dimx_name || ' WHEN '''''' || x.'&lt;br /&gt;        || dimx_name || ' || '''''' THEN ' || expr || ' ELSE 0 END) AS "'' || x.'|| dimx_name&lt;br /&gt;        || ' || ''"'' FROM (SELECT DISTINCT '&lt;br /&gt;        || dimx_name || ' ' || dimx_source || ') x),'', '')' INTO cols_expr;&lt;br /&gt; xtab_expr := 'SELECT '|| dimy_name ||', ' || cols_expr || ', SUM(' || expr || ') AS Total '&lt;br /&gt;      || dimy_source || ' GROUP BY '|| dimy_name;&lt;br /&gt;EXECUTE xtab_expr;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE plpgsql;&lt;br /&gt;&lt;/pre&gt;Test output:&lt;br /&gt;&lt;pre&gt;postgres=# CALL crosstab('gender', 'FROM employees','shop','FROM employees e INNER JOIN shops s ON e.shop_id = s.id','salary');&lt;br /&gt;shop   |   m   |  f   | total&lt;br /&gt;---------+-------+------+-------&lt;br /&gt;New York |     0 | 5600 |  5600&lt;br /&gt;Zurich   |  4500 | 4700 |  9200&lt;br /&gt;London   | 10300 |    0 | 10300&lt;br /&gt;(3 rows)&lt;br /&gt;&lt;br /&gt;CALL 0&lt;br /&gt;postgres=# CALL crosstab('shop', 'FROM shops', 'gender','FROM employees e INNER JOIN shops s ON e.shop_id = s.id','salary');&lt;br /&gt;gender | London | New York | Zurich | total&lt;br /&gt;-------+--------+----------+--------+-------&lt;br /&gt;m      |  10300 |        0 |   4500 | 14800&lt;br /&gt;f      |      0 |     5600 |   4700 | 10300&lt;br /&gt;(2 rows)&lt;br /&gt;&lt;br /&gt;CALL 0&lt;span style="font-family:Georgia,serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;And one really multirecordset's sample:&lt;br /&gt;&lt;pre&gt;create or replace procedure free(int) as $$&lt;br /&gt;declare s varchar = 'SELECT 1';&lt;br /&gt;begin&lt;br /&gt; for i in 2..$1 loop&lt;br /&gt;   s := s || ',' || to_char(i,'999');&lt;br /&gt;   execute s;&lt;br /&gt; end loop; &lt;br /&gt;end$$ language plpgsql;&lt;br /&gt;&lt;/pre&gt;Output:&lt;br /&gt;&lt;pre&gt;postgres=# call free(6);&lt;br /&gt;?column? | ?column?&lt;br /&gt;----------+----------&lt;br /&gt;       1 |        2&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;?column? | ?column? | ?column?&lt;br /&gt;----------+----------+----------&lt;br /&gt;       1 |        2 |        3&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;?column? | ?column? | ?column? | ?column?&lt;br /&gt;----------+----------+----------+----------&lt;br /&gt;       1 |        2 |        3 |        4&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;?column? | ?column? | ?column? | ?column? | ?column?&lt;br /&gt;----------+----------+----------+----------+----------&lt;br /&gt;       1 |        2 |        3 |        4 |        5&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;?column? | ?column? | ?column? | ?column? | ?column? | ?column?&lt;br /&gt;----------+----------+----------+----------+----------+----------&lt;br /&gt;       1 |        2 |        3 |        4 |        5 |        6&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;CALL 0&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-3623624518439266252?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/3623624518439266252/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=3623624518439266252' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3623624518439266252'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/3623624518439266252'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2007/11/stacked-recordset-multirecordset.html' title='Stacked recordset (multirecordset)'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8839574367290288724.post-754335286833849713</id><published>2007-11-07T09:25:00.000-08:00</published><updated>2007-11-07T13:49:41.737-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plpgsql'/><category scheme='http://www.blogger.com/atom/ns#' term='postgresql'/><title type='text'>First real procedures on PostgreSQL</title><content type='html'>I am working on an enhancing of plpgsql. I would to add to plpgsql procedures and methods. Methods are flagged functions with different calling convention. Procedures are total different beast. Now I am able to run procedures without parameters. I would to support parameter passing by reference. It allows to take reference for any variable and potentially some interesting features (like string builder or array builder etc). What is best, I need not modify system tables. Procedure is function (from pg_proc perspective) that returns PROCEDURE type (it will be used for exit status).&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;postgres=#&lt;br /&gt;create or replace procedure print()&lt;br /&gt;as $$&lt;br /&gt;begin&lt;br /&gt;raise notice 'ahoj';&lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;CREATE PROCEDURE&lt;br /&gt;&lt;br /&gt;postgres=#&lt;br /&gt;create or replace function test()&lt;br /&gt;returns void as $$&lt;br /&gt;begin&lt;br /&gt;call print();&lt;br /&gt;return;&lt;br /&gt;end;&lt;br /&gt;$$ language plpgsql;&lt;br /&gt;CREATE FUNCTION&lt;br /&gt;&lt;br /&gt;postgres=# select test();&lt;br /&gt;NOTICE:  Found ()&lt;br /&gt;CONTEXT:  PL/pgSQL function "test" line 4456553 at CALL&lt;br /&gt;NOTICE:  OID: 16821&lt;br /&gt;CONTEXT:  PL/pgSQL function "test" line 4456553 at CALL&lt;br /&gt;NOTICE:  ahoj&lt;br /&gt;CONTEXT:  PL/pgSQL function "test" line 4456553 at CALL&lt;br /&gt;test&lt;br /&gt;------&lt;br /&gt;&lt;br /&gt;(1 row)&lt;br /&gt;&lt;br /&gt;postgres=# \df print&lt;br /&gt;                  List of functions&lt;br /&gt;Schema | Name  | Result data type | Argument data types&lt;br /&gt;--------+-------+------------------+---------------------&lt;br /&gt;public | print | procedure        |&lt;br /&gt;(1 row)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There is lot of work still, but first step is done.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8839574367290288724-754335286833849713?l=okbob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://okbob.blogspot.com/feeds/754335286833849713/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8839574367290288724&amp;postID=754335286833849713' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/754335286833849713'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8839574367290288724/posts/default/754335286833849713'/><link rel='alternate' type='text/html' href='http://okbob.blogspot.com/2007/11/first-real-procedures-on-postgresql.html' title='First real procedures on PostgreSQL'/><author><name>Pavel Stěhule</name><uri>http://www.blogger.com/profile/01996484227228696817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://i.iinfo.cz/urs/stehule-112953902372508.jpg'/></author><thr:total>2</thr:total></entry></feed>
