How to Create PL/pgSQL Functions to Handle Odoo 18 Operations
By Braincuber Team
Published on December 29, 2025
Working directly with your database gives you incredible power to automate tasks, run complex calculations, and generate custom reports. While Odoo 18 provides an excellent Python-based framework for business logic, there are times when executing operations directly at the database level using PL/pgSQL can dramatically improve performance and simplify maintenance.
This comprehensive guide explores how to create powerful PL/pgSQL functions in PostgreSQL to handle Odoo 18 operations. Whether you're cleaning up inactive records, generating custom reports, or automating data transformations, you'll learn practical techniques that work in real-world scenarios.
What You'll Learn:
- Understanding PL/pgSQL and its role in Odoo 18
- Creating data cleanup functions for archiving inactive records
- Building reporting functions that return structured datasets
- Executing functions through pgAdmin and psql
- Best practices for database-level operations in Odoo
Why Use PL/pgSQL with Odoo 18?
Odoo 18 is built on top of PostgreSQL, one of the most powerful open-source relational databases available. While most business logic in Odoo is written in Python, there are compelling reasons to leverage PL/pgSQL for certain operations:
Performance
Execute complex queries directly in the database without Python overhead
Data Integrity
Enforce business rules at the database level for consistency
Automation
Schedule maintenance tasks without Odoo application involvement
Reporting
Generate custom reports with minimal resource consumption
Understanding PL/pgSQL Basics
PL/pgSQL (Procedural Language/PostgreSQL) is PostgreSQL's native procedural programming language. It extends SQL with control structures, variables, and exception handling, making it ideal for creating reusable database functions.
Key Components of a PL/pgSQL Function
| Component | Purpose | Example |
|---|---|---|
| Function Name | Identifies the function for calling | cleanup_old_records() |
| Return Type | Defines what data the function returns | VOID, TABLE, INTEGER |
| Parameters | Input values passed to the function | (days_old INTEGER) |
| Function Body | SQL logic and control statements | BEGIN...END |
| Language | Specifies the procedural language | LANGUAGE plpgsql |
Use Case 1: Archiving Inactive Customers
Let's start with a practical example. Over time, your Odoo database accumulates customer records that are no longer active. These might be customers who haven't placed an order in years. Keeping them active clutters your customer lists and can slow down searches and reports.
Here's a PL/pgSQL function that automatically archives customers who haven't made a purchase in the last two years:
CREATE OR REPLACE FUNCTION archive_inactive_customers()
RETURNS VOID AS $$
BEGIN
UPDATE res_partner
SET active = FALSE
WHERE id IN (
SELECT rp.id
FROM res_partner rp
LEFT JOIN sale_order so ON so.partner_id = rp.id
GROUP BY rp.id
HAVING MAX(so.date_order) IS NULL
OR MAX(so.date_order) < NOW() - INTERVAL '2 years'
)
AND active = TRUE;
END;
$$ LANGUAGE plpgsql;
How This Function Works
Identifies Inactive Customers
The function joins the res_partner table with sale_order using a LEFT JOIN to find all customers and their most recent orders.
Checks Last Order Date
For each customer, it checks if they have no orders at all (MAX(so.date_order) IS NULL) or if their last order was more than 2 years ago.
Archives Matching Records
All customers matching these criteria have their active field set to FALSE, effectively archiving them in Odoo.
Executing the Function
To run this function, simply execute the following SQL command:
SELECT archive_inactive_customers();
Pro Tip: You can modify the INTERVAL value to use different timeframes. For example, change '2 years' to '18 months' or '3 years' based on your business needs.
Use Case 2: Creating a Sales Summary Report
Reporting is another powerful use case for PL/pgSQL functions. Instead of building complex Python code or using the Odoo reporting framework, you can create a function that returns a structured dataset directly from the database.
Here's a function that generates a sales summary showing customer performance based on confirmed orders:
CREATE OR REPLACE FUNCTION generate_sales_summary()
RETURNS TABLE (
customer_name TEXT,
order_count INTEGER,
total_revenue NUMERIC,
avg_order_value NUMERIC
) AS $$
BEGIN
RETURN QUERY
SELECT
rp.name::TEXT AS customer_name,
COUNT(so.id)::INTEGER AS order_count,
COALESCE(SUM(so.amount_total), 0) AS total_revenue,
COALESCE(AVG(so.amount_total), 0) AS avg_order_value
FROM sale_order so
JOIN res_partner rp ON so.partner_id = rp.id
WHERE so.state = 'sale'
GROUP BY rp.name
ORDER BY total_revenue DESC;
END;
$$ LANGUAGE plpgsql;
Function Breakdown
| Column | Type | Description |
|---|---|---|
| customer_name | TEXT | Name of the customer from res_partner |
| order_count | INTEGER | Total number of confirmed orders |
| total_revenue | NUMERIC | Sum of all order totals |
| avg_order_value | NUMERIC | Average value per order |
Running the Report
Execute the function to retrieve the sales summary:
SELECT * FROM generate_sales_summary();
The results will be displayed in a table format showing customer performance metrics. You can export these results to CSV directly from pgAdmin or the psql terminal for further analysis or integration with other tools.
Advanced Example: Parametrized Date Range Report
Let's create a more flexible reporting function that accepts date range parameters:
CREATE OR REPLACE FUNCTION sales_by_date_range(
start_date DATE,
end_date DATE
)
RETURNS TABLE (
order_date DATE,
customer TEXT,
order_reference VARCHAR,
amount NUMERIC
) AS $$
BEGIN
RETURN QUERY
SELECT
so.date_order::DATE AS order_date,
rp.name::TEXT AS customer,
so.name::VARCHAR AS order_reference,
so.amount_total AS amount
FROM sale_order so
JOIN res_partner rp ON so.partner_id = rp.id
WHERE so.state = 'sale'
AND so.date_order >= start_date
AND so.date_order <= end_date
ORDER BY so.date_order DESC;
END;
$$ LANGUAGE plpgsql;
Execute with specific date parameters:
-- Get sales for Q4 2024
SELECT * FROM sales_by_date_range('2024-10-01', '2024-12-31');
Executing PL/pgSQL Functions
Using pgAdmin
- Connect to Your Database: Open pgAdmin and connect to your Odoo PostgreSQL database.
- Open Query Tool: Right-click on your database and select "Query Tool".
- Create the Function: Paste your PL/pgSQL function code and execute it.
- Run the Function: Execute the SELECT statement to run your function.
- Export Results: Right-click on the results grid and choose "Export" to save as CSV, XML, or other formats.
Using psql Command Line
# Connect to database
psql -U odoo -d your_database_name
# Execute function
SELECT * FROM generate_sales_summary();
# Export to CSV
\copy (SELECT * FROM generate_sales_summary()) TO '/path/to/sales_report.csv' CSV HEADER;
Best Practices for PL/pgSQL in Odoo
1. Test in a Development Environment First
Always test your functions on a copy of your database before running them in production. Data manipulation at the database level bypasses Odoo's ORM validation.
2. Use Transactions for Safety
Wrap destructive operations in transactions so you can rollback if something goes wrong. Use BEGIN, COMMIT, and ROLLBACK appropriately.
3. Document Your Functions
Include comments in your function code explaining what it does, when to use it, and any prerequisites or side effects.
4. Monitor Performance
Use EXPLAIN ANALYZE to understand query performance and optimize complex functions that might impact database performance.
5. Coordinate with Odoo's ORM
Remember that direct database changes bypass Odoo's computed fields, triggers, and constraints. Consider the implications before modifying data directly.
Common Use Cases for PL/pgSQL in Odoo
Data Cleanup
Archive old records, remove duplicates, standardize data formats
Custom Reports
Generate complex reports with aggregated data and custom calculations
Bulk Updates
Update thousands of records efficiently without Python overhead
Data Migration
Transform and migrate data between tables or from external sources
Frequently Asked Questions
Can PL/pgSQL functions break Odoo's functionality?
Yes, direct database modifications bypass Odoo's ORM layer, which means computed fields, constraints, and triggers won't execute. Always test functions in a development environment and understand the implications before running them in production.
Do I need special permissions to create PL/pgSQL functions?
Yes, you need database superuser privileges or specific CREATE FUNCTION permissions on your PostgreSQL database. The Odoo application user typically has these permissions, but verify with your database administrator.
How do I schedule PL/pgSQL functions to run automatically?
Use PostgreSQL's pg_cron extension or system-level cron jobs to schedule function execution. You can also create Odoo scheduled actions that call database functions through the ORM.
Can I return complex data structures from PL/pgSQL functions?
Yes, PL/pgSQL functions can return sets of rows using the RETURNS TABLE syntax, single values, or even custom composite types. You can structure your return data in many flexible ways to suit your reporting needs.
How do I debug PL/pgSQL functions?
Use RAISE NOTICE statements to output debugging information, check PostgreSQL logs for errors, and use pgAdmin's built-in debugger for step-through debugging. Start with simple test cases and gradually add complexity.
What's the performance difference between PL/pgSQL and Python for bulk operations?
PL/pgSQL typically performs significantly faster for bulk database operations because it eliminates the overhead of transferring data between the database and application layer. For operations affecting thousands of records, the performance improvement can be dramatic.
Can I call PL/pgSQL functions from within Odoo Python code?
Yes, you can execute PL/pgSQL functions from Odoo using self.env.cr.execute() to run SQL directly. This allows you to combine the benefits of both approaches when needed.
Conclusion
PL/pgSQL functions provide a powerful toolkit for managing Odoo 18 operations at the database level. Whether you're automating data cleanup, generating custom reports, or performing bulk updates, these functions can dramatically improve performance and simplify maintenance tasks.
By working directly with PostgreSQL, you gain precise control over your data and can execute operations that would be cumbersome or slow through Odoo's application layer. The key is understanding when to use database-level operations and when to rely on Odoo's ORM, ensuring you maintain data integrity while maximizing efficiency.
Start with simple functions like the examples in this guide, test thoroughly in development environments, and gradually build more sophisticated automation as you become comfortable with PL/pgSQL. Your Odoo system will thank you with improved performance and easier data management.
Need Expert Database Optimization for Odoo 18?
Our PostgreSQL and Odoo specialists can help you create custom PL/pgSQL functions, optimize database performance, and build sophisticated reporting solutions tailored to your business needs.
