Views, Functions, and Stored Procedures

My journey from duplicated query nightmares to reusable database logic


Table of Contents


Introduction: The Copy-Paste Query Problem

My blog admin dashboard needed the same "published posts with author and comment count" query in seven different places: homepage, author profile pages, category listing, search results, RSS feed generator, sitemap builder, and the admin panel itself.

I copied the query seven times. Within two weeks, I had seven slightly different versions. The homepage query had a bug fix that I'd applied to only three of the others. The admin panel was using LEFT JOIN where it needed INNER JOIN. One version was missing the status = 'published' filter entirely.

Sound familiar?

The solution was to move that shared logic into the database itself using views and functions. Now I define the query once. All seven places reference the view. Fix the view once, everyone benefits.


Views: Named, Reusable Queries

A view is a stored SELECT statement that behaves like a table. It doesn't store dataβ€”it runs the query each time it's accessed.

Creating a View

Now every part of the application uses this:

Modifying Views

Updatable Views

Simple views (no aggregates, no JOINs, no DISTINCT, one base table) are automatically updatable:

Views with SECURITY DEFINER


Materialised Views: Cached Query Results

A materialised view stores the query results on disk. Perfect for expensive aggregations that don't need real-time accuracy:

Refreshing Materialised Views

The data becomes stale immediately; you must refresh manually or via a schedule:

Scheduling Refreshes


PostgreSQL Functions (User-Defined Functions)

Functions let you encapsulate logic, accept parameters, and return values. They're executed with SELECT.

Simple SQL Function

Function That Returns a Table

Function Volatility

Marker
Meaning
Can Be Cached?

IMMUTABLE

Same inputs always return same result; no DB access

Yes, aggressively

STABLE

Same inputs return same result within a transaction

Yes, within query

VOLATILE (default)

May return different results each call

Never

Use the most restrictive volatility that's accurateβ€”it allows PostgreSQL to optimise better.

Functions with OUT Parameters


Stored Procedures

Stored procedures (PostgreSQL 11+) are similar to functions but:

  • Called with CALL instead of SELECT

  • Can manage their own transactions (COMMIT/ROLLBACK inside them)

  • Cannot return values directly (use INOUT parameters instead)


Triggers: Automatic Database Reactions

Triggers automatically execute a function when specific events occur on a table:

Types of Triggers

spinner

Auto-Update updated_at Timestamp

Audit Log Trigger

Validation Trigger


PL/pgSQL: Writing Procedural Code in PostgreSQL

PL/pgSQL is PostgreSQL's procedural language for writing complex database logic:

Variables and Control Flow

Looping

Exception Handling


Common Patterns and Use Cases

Soft Delete Pattern

Slug Generation


Performance Considerations

Object
Performance Notes

Regular view

Query re-executed each time; no storage overhead

Materialised view

Fast reads; needs explicit refresh; uses storage

SQL function (IMMUTABLE)

Can be inlined by planner; very fast

PL/pgSQL function

Cannot be inlined; small overhead per call

Trigger

Adds overhead to every DML operation; keep logic minimal

Trigger + complex logic

Profile carefully; complex triggers can make writes slow


Managing Database Code

Keep your database code version-controlled like application code:


What I Learned About Database Logic

Moving query logic into views and functions transformed my blog codebase. The seven copy-pasted queries became one published_posts view. When I needed to add a featured flag filter, I changed it in one placeβ€”the viewβ€”not seven.

When to put logic in the database:

  • βœ… Business rules that must be enforced regardless of which application accesses the DB

  • βœ… Complex queries reused across many places

  • βœ… Computed columns (reading time, slug, audit timestamps)

  • βœ… Referential integrity enforcement

When to keep logic in the application:

  • βœ… Business workflows that need external API calls

  • βœ… Complex conditional logic that changes frequently

  • βœ… Logic that needs to be unit-tested easily

  • βœ… User-facing validation with detailed error messages

Golden rules:

  • βœ… Views for shared, parameterless queries

  • βœ… Functions for parameterised, reusable logic

  • βœ… Stored procedures for multi-step transactional workflows

  • βœ… Triggers for automatic audit logging and derived column maintenance

  • ❌ Avoid heavy business logic in triggersβ€”it's hard to debug


Next Steps

With powerful database logic in place, the next priority is protecting your data: Database Security.


Part of the Database 101 Series

Last updated