DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Hello YugabyteDB: Running Kong on the Distributed PostgreSQL Database
  • Getting Started With Postgres: Three Free and Easy Ways
  • SQL Server to Postgres Database Migration
  • Quick Pattern-Matching Queries in PostgreSQL and YugabyteDB

Trending

  • AI Agents: A New Era for Integration Professionals
  • Implementing Explainable AI in CRM Using Stream Processing
  • How to Introduce a New API Quickly Using Micronaut
  • Useful System Table Queries in Relational Databases
  1. DZone
  2. Data Engineering
  3. Databases
  4. Implementing PostgreSQL User-Defined Table Functions in YugabyteDB

Implementing PostgreSQL User-Defined Table Functions in YugabyteDB

By 
Bryn Llewellyn user avatar
Bryn Llewellyn
·
Dec. 20, 19 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
19.9K Views

Join the DZone community and get the full member experience.

Join For Free


wooden-table-with-plant

Welcome part two of a three-part series of posts on PostgreSQL’s table functions. These functions can be easily leveraged in a distributed SQL database like YugabyteDB, which is PostgreSQL compatible.

In part one, I gave a brief introduction to PostgreSQL’s table functions. Part three will cover some realistic use cases. I’ll introduce this second post by quoting that paragraph:

A regular language plpgsql user-defined function is implemented using the plain return statement. Its argument can only be a single value. A user-defined table function, in contrast, is implemented using either return next, to return the next explicitly computed row, or return query, to return the entire result set of a select statement. You can define the statement using either static SQL or dynamic SQL. You can also implement a language sql table function by defining the body entirely with a single static SQL select statement.

Language SQL

A view is defined by a single select statement — and a language SQL user-defined table function is also defined by a single select statement. The critical difference is that the table function, like all kinds of functions, is typically defined with formal parameters. You can use one of these at a syntax spot in the defining select statement where a value is needed. In other words, you have a parameterized view. Here’s an example:

SQL
 




x


 
1
create or replace function t_view(the_k in int)
2
  returns table(k int, v varchar)
3
  language sql
4
as $body$
5
  select t.k, t.v from t where t.k = t_view.the_k;
6
$body$;



Test it with:

SQL
 




xxxxxxxxxx
1


 
1
select k, v from t_view(2);



As long as you know the identity of the table (or tables) over which you want a parameterized view, then this kind of table function is the perfect fit. The meaning of the code couldn’t be more transparent. I show the usefulness of such a language SQL table function in the “Parameterized view” use case in my third post.

You may also like: Spring Boot and PostgreSQL

Language Plpgsql With Return Query

The simplest example has just a single return query followed by a select statement. But if this functionality is sufficient for you, you can use a language SQL table function. Here’s a more interesting example. It relies on two tables: table, t, as used above and table s with the same row shape. Here’s a convenient way to create it:

SQL
 




xxxxxxxxxx
1


 
1
create table s as
2
select k, ('not '||v)::varchar(20) as v
3
from t;



Note: Notice the use of the explicit typecast. Without it, the column s.v would have the datatype, text.

SQL
 




xxxxxxxxxx
1
15


 
1
create function t_view(table_name in varchar, the_k in int)
2
  returns table(k int, v varchar)
3
  language plpgsql
4
as $body$
5
begin
6
  case table_name
7
    when 't' then
8
      return query
9
      select t.k, t.v from t where t.k = t_view.the_k;
10
    when 's' then
11
      return query
12
      select s.k, s.v from s where s.k = t_view.the_k;
13
  end case;
14
end;
15
$body$;



Test it with these two queries:

SQL
 




xxxxxxxxxx
1


 
1
select k, v from t_view('t', 2);
2
select k, v from t_view('s', 3);



Note: Without the typecast in the create table as select statement for table s, the query in the second leg of the case statement would cause this error:

SQL
 




xxxxxxxxxx
1


 
1
structure of query does not match function result type
2
Returned type text does not match expected type character varying in column 2.



Note: Though varchar and text have the same functionality, they are formally different data types. This is a feature of PostgreSQL, and therefore of YugabyteDB. It brings opportunities for programmer errors. But, you have no choice but to understand it and program accordingly.

SQL
 




xxxxxxxxxx
1
10


 
1
create or replace function t_view(table_name in varchar, the_k in int)
2
  returns table(k int, v varchar)
3
  language plpgsql
4
as $body$
5
begin
6
  return query execute
7
  'select t.k, t.v from "'||table_name||'" as t where t.k = $1'
8
  using t_view.the_k;
9
end;
10
$body$;



Test it with the same queries that you used to test the static SQL variant. You’ll see that both variants produce the same results.

Notice that one of the function’s formal parameters is the source table’s name. I convert this, following standard good practice, into an injection-proof SQL identifier by surrounding it with double quotes. I’m relying on the fact that this create table statement:

SQL
 




xxxxxxxxxx
1


 
1
create table t(k int primary key, v varchar(20));



and this one:

SQL
 




xxxxxxxxxx
1


 
1
create table "t"(k int primary key, v varchar(20));



have the same effect. Any reference to the table, whose name is lower-case-t, can be made either with the unquoted identifier t, taken case-insensitively to denote the lower-case name, or the double quoted identifier “t”, taken case-sensitively.

Note: this rule is the exact opposite from the Oracle Database rule. There, too, unquoted identifiers are taken case-insensitively; but there, they’re taken to denote the upper-case name!

Language Plpgsql With Return Next

This flavor of the table function is the most general. In the limit, you can compute the set of values for each returned row explicitly. Here’s a trivial example to show the syntax:

SQL
 




xxxxxxxxxx
1
13


 
1
create function some_computed_rows()
2
  returns table(k t.k%type, v t.v%type)
3
  language plpgsql
4
as $body$
5
begin
6
  k := 7; v := 'seven';
7
  return next;
8
  k := 8; v := 'eight';
9
  return next;
10
  k := 9; v := 'nine';
11
  return next;
12
end;
13
$body$;



Of course, you can use it, as I’ve shown in the other examples:

SQL
 




xxxxxxxxxx
1


 
1
insert into t(k, v)
2
select k, v from f();



In more typical uses, you can’t express what you want using just a single select statement but, rather, you assemble the returned results from one or more queries, using some procedural logic to combine these. As an exercise, you could replicate the behavior of a single select statement that uses a window function by implementing a table function that establishes the window of interest explicitly within the body of a for loop.

Of course, you should always use pure SQL when this is possible. But, there’s a class of requirements where you can’t do this. The canonical example in this class is provided by a stock price history table — in other words, a graph of price against time — where you want to find a so-called “W” pattern (a local minimum followed by a local maximum followed by another local minimum, all within a defined time interval).

I use a language plpgsql with return next table function, whose return table has just a single,  varchar column, in the “Pretty printed ad hoc reports for administrators” use case described in my third post. There, many of the rows (like headings and rule-offs) are produced, each with their own dedicated return next statement. But, others are computed in a cursor for loop to embed query results in the report.

PostgreSQL Table Functions Are not Pipelined

YugabyteDB currently uses the PostgreSQL 11.2 source code. Look in the docs for this version for this section: “43.6.1.2. RETURN NEXT and RETURN QUERY”. And look for the note that starts thus:

The current implementation of RETURN NEXT and RETURN QUERY stores the entire result set before returning from the function…

The same note is found in the PostgreSQL docs for Version 12.1.

This means that a table function doesn’t behave a row source, as does a regular permanent, temporary table, or view, delivering rows only as they’re needed. Rather, the entire result set that the table function defines is materialized before any of its rows can be consumed. If the result set is huge, this can imply spilling to disc — bringing corresponding performance problems.

This stands in contrast to the behavior of Oracle database’s so-called pipelined table functions, where the results are delivered on-demand, row-source style.

The consequence of this current behavior is that PostgreSQL table functions are most useful when they deliver small, bounded result sets.

Conclusion

This post has explained how to implement user-defined table functions, in all variants. But it used only bare-bones examples that indicate generalized classes of use-case.


Further Reading

Spring Boot and PostgreSQL.

Database sql PostgreSQL YugabyteDB Spring Framework Use case

Published at DZone with permission of Bryn Llewellyn. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Hello YugabyteDB: Running Kong on the Distributed PostgreSQL Database
  • Getting Started With Postgres: Three Free and Easy Ways
  • SQL Server to Postgres Database Migration
  • Quick Pattern-Matching Queries in PostgreSQL and YugabyteDB

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!