Examples
Loan eligibility
Decide based on complex inputs with rules that are hard to read and verify in SQL. Verify the model when authoring the DMN, and run the exact same model in production.
-- One applicant.
SELECT dmn_eval_text(model, 'Eligibility', '{
"Age": 34, "Income": 82000, "Bankrupt": false
}'::jsonb) AS decision
FROM models WHERE name = 'loan';
-- Approved
-- Now check it for everybody.
SELECT a.name, a.age, a.income, a.bankrupt,
dmn_eval_text(m.model, 'Eligibility', jsonb_build_object(
'Age', a.age,
'Income', a.income,
'Bankrupt', a.bankrupt
)) AS decision
FROM applicants a
CROSS JOIN models m
WHERE m.name = 'loan'
ORDER BY a.id;
-- name | age | income | bankrupt | decision
-- ---------------+-----+--------+----------+--------------------------
-- Ada Okafor | 34 | 82000 | f | Approved
-- Bo Zhang | 17 | 0 | f | Denied: underage
-- Chen Ruiz | 29 | 41000 | f | Denied: low income
-- Dara Singh | 45 | 120000 | t | Denied: prior bankruptcy
-- Eli Novak | 22 | 50000 | f | Approved
-- Fay Mbeki | 19 | 49999 | f | Denied: low income
-- Gus Halvorsen | 64 | 68000 | f | Approved
-- Hana Ito | 17 | 95000 | f | Denied: underageRead the complete walkthrough: Loan eligibility
Order pricing
Store each pricing policy as a dated row. One query prices every order under whichever version is in effect on the day, and switches to a new one by itself when its start date arrives—no code deploy.
-- Two versions of the 'retail' policy live in `pricing_policies`, each
-- with the date it takes effect. One query prices every order under the
-- version in effect on a given day, and switches by itself when the
-- promo's start date arrives. No deploy, no UPDATE. As of 1 July:
SELECT o.customer, o.base_price,
round(dmn_eval_numeric(pol.model, 'Total Price', jsonb_build_object(
'Base Price', o.base_price, 'Tax Rate', o.tax_rate)), 2) AS total
FROM orders o
CROSS JOIN LATERAL (
SELECT model FROM pricing_policies
WHERE name = 'retail' AND takes_effect <= DATE '2026-07-01'
ORDER BY takes_effect DESC
LIMIT 1
) pol
ORDER BY o.id;
-- customer | base_price | total
-- -------------------+------------+---------
-- Northwind Traders | 100.00 | 99.00
-- Globex | 2499.99 | 2435.62
-- Initech | 45.50 | 49.14
-- Umbrella Corp | 1000.00 | 900.00
-- Acme Supply | 19.99 | 19.34Read the complete walkthrough: Order pricing
Compliance checks
Keep regulatory requirements in separately auditable business rules rather than burying them in application logic.
-- With a view, always have the correct policy outcome as a column.
AS
SELECT c.id, c.name, c.region, c.data_class,
dmn_eval_text(m.model, 'Handling', jsonb_build_object(
'Region', c.region,
'Data Class', c.data_class
)) AS handling
FROM customers c
CROSS JOIN models m
WHERE m.name = 'compliance';
SELECT name, region, data_class, handling
FROM obligations ORDER BY id;
-- name | region | data_class | handling
-- --------------------+--------+------------+---------------------------
-- Northwind Traders | EU | personal | store in EU, retain 24 ...
-- Globex | US | personal | retain 24 months
-- Initech | US | special | encrypt, restrict acces...
-- Umbrella Corp | EU | special | encrypt, restrict acces...
-- Acme Supply | UK | public | standard handling
-- Soylent Industries | EU | public | standard handling
-- Tyrell Corp | US | public | standard handling
-- Wayne Enterprises | UK | personal | retain 24 months
SELECT handling, count(*), string_agg(name, ', ' ORDER BY id) AS who
FROM obligations
WHERE handling <> 'standard handling'
GROUP BY handling
ORDER BY count(*) DESC, handling;
-- handling | count | who
-- -------------------------------+-------+------------------------
-- encrypt, restrict access, ... | 2 | Initech, Umbrella Corp
-- retain 24 months | 2 | Globex, Wayne Enterp...
-- store in EU, retain 24 months | 1 | Northwind TradersRead the complete walkthrough: Compliance checks
Ticket routing
Automatically visualize the ticket routing decision table with DMN then put the same logic directly into the database. What the documentation site says is what happens because they're the same rules.
-- Change the business requirements in the database and every ticket
-- reroutes automatically.
AS
SELECT t.id, t.subject, t.priority, t.customer_tier,
dmn_eval_text(m.model, 'Queue', jsonb_build_object(
'Priority', t.priority,
'Customer Tier', t.customer_tier
)) AS queue
FROM tickets t
CROSS JOIN models m
WHERE m.name = 'routing';
SELECT queue, count(*), string_agg(subject, '; ' ORDER BY id) AS work
FROM routed_tickets
GROUP BY queue
ORDER BY count(*) DESC, queue;
-- queue | count | work
-- --------+-------+-----------------------------------------
-- pager | 3 | Cannot log in after SSO change; Bill...
-- tier-1 | 3 | Feature request: dark mode; Typo in ...
-- tier-2 | 2 | API latency spike in eu-west; Passwo...