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: underage

Read 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.34

Read 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.
CREATE VIEW obligations 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 Traders

Read 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.
CREATE VIEW routed_tickets 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...

Read the complete walkthrough: Ticket routing