Agent skill

Write Oql Queries

by mendixlabs in mendixlabs/mxcli

Write OQL for Mendix VIEW entities — joins, aggregates, calculated fields, and the syntax the runtime actually accepts.

Apache-2.0Auto-check: notes

Install Write Oql Queries

skills CLI
$ npx skills add mendixlabs/mxcli --skill write-oql-queries -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install mendixlabs/mxcli write-oql-queries --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/mendixlabs/mxcli.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/mendix/write-oql-queries .claude/skills/write-oql-queries && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
write-oql-queries
GitHub stars
128
Token cost
~6.2k tokens
SKILL.md length
2,114 words
Files
2
Skills in repo
75
Repo updated
First seen
Licence
Apache-2.0

At a glance

Write OQL for Mendix VIEW entities — joins, aggregates, calculated fields, and the syntax the runtime actually accepts.

  • Works in 12 steps: VIEW Entity Best Practices (CRITICAL) → Aggregate Functions (MUST BE LOWERCASE) → COUNT Function → …
  • Creating a VIEW entity
  • SKILL.md covers Reference files, Purpose, When to Use This Skill and Critical OQL Syntax Rules, plus 1 more section
  • Calls jq

What it does

Write Oql Queries is an agent skill from mendixlabs/mxcli. Write OQL for Mendix VIEW entities — joins, aggregates, calculated fields, and the syntax the runtime actually accepts. Use when creating a VIEW entity or building a report or analytics query.

Its SKILL.md is about 6.2k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files (for example `reference/patterns.md`).

The repository describes itself as: Mendix cli tool, a headless way to work with Mendix projects. Enables Mendix projects for use with 3rd party agentic coding tools like Claude Code and Copilot. Includes a… The licence is Apache-2.0.

When your agent uses it

  • Creating a VIEW entity
  • Building a report
  • Analytics query

Example prompts

  • “/write-oql-queries”

Workflow steps

12 steps, taken from the step headings in SKILL.md.

  1. VIEW Entity Best Practices (CRITICAL)
  2. Aggregate Functions (MUST BE LOWERCASE)
  3. COUNT Function
  4. DATEPART Function (Comma Syntax)
  5. Enumeration Comparisons (String Literals)
  6. Division Operator (Colon, not Slash)
  7. ORDER BY with Aliases
  8. Operators (Use != not <>)
  9. IN Expression Syntax
  10. Subqueries (Scalar and Correlated)
  11. Association Path Syntax
  12. JOIN Syntax (Association Traversal and ON Clause)

What it can do on your machine

Read from SKILL.md and the folder at commit a924d11. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • jq

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • docs.mendix.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Write Oql Queries loads about 6.2k tokens when it runs. Until then it costs about 53 tokens; SKILL.md has 2,114 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~53
When it runs · the whole SKILL.md, loaded when a task matches
~6.2k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check: notes

The automated check noted patterns worth knowing about, such as sudo or a known installer.

  • NoteMentions a .env fileSKILL.md:598
    # basic query (reads .docker/.env for connection settings)

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from mendixlabs/mxcli at commit a924d11, republished under its Apache-2.0 licence (© mendixlabs). 2,114 words, ~6,205 tokens.

Download SKILL.mdSave it as .claude/skills/write-oql-queries/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.
name
write-oql-queries
description
Write OQL for Mendix VIEW entities — joins, aggregates, calculated fields, and the syntax the runtime actually accepts. Use when creating a VIEW entity or building a report or analytics query.

Skill: Write OQL Queries for Mendix VIEW Entities

Reference files

  • reference/patterns.md — the recurring OQL shapes (aggregation, joins across associations, date bucketing, ranking, filtered counts) and a full worked query, to adapt rather than derive.

Purpose

Generate correct OQL (Object Query Language) queries for Mendix VIEW entities. This skill helps you create VIEW entities with proper OQL syntax that will execute successfully in Mendix runtime.

When to Use This Skill

  • User asks to create a VIEW entity
  • User requests help with OQL queries
  • User wants to create analytics, reports, or aggregated data views
  • User needs to join entities or create calculated fields
  • You encounter OQL syntax errors when creating VIEW entities

Critical OQL Syntax Rules

0. VIEW Entity Best Practices (CRITICAL)

RULE 1: All SELECT columns MUST have explicit AS aliases

Every column in the SELECT clause must have an alias that matches the entity attribute name:

text
-- ❌ WRONG - Missing aliases
create view entity Finance.CashFlowProjection (
  ProjectionDate: datetime,
  ProjectedIncome: decimal,
  ProjectedExpense: decimal
) as (
  select
    fl.ForecastDate,              -- Missing AS alias
    fl.ProjectedIncome,           -- Missing AS alias
    fl.ProjectedExpense           -- Missing AS alias
  from Finance.ForecastLine as fl
);
sql
mdl 1;
-- ✅ CORRECT - All columns have explicit aliases
create view entity Finance.CashFlowProjection (
  ProjectionDate: datetime,
  ProjectedIncome: decimal,
  ProjectedExpense: decimal
) as (
  select
    fl.ForecastDate as ProjectionDate,
    fl.ProjectedIncome as ProjectedIncome,
    fl.ProjectedExpense as ProjectedExpense
  from Finance.ForecastLine as fl
);

RULE 2: ORDER BY requires a LIMIT — prefer letting the consuming query sort

ORDER BY alone is rejected (mxcli check → MDL030; mx check → CE0174). ORDER BY with a LIMIT is valid and builds clean — that is exactly how you express a top-N view. As a default, prefer no ORDER BY/LIMIT so the UI component or microflow can sort and paginate the same view differently; reach for ORDER BY … LIMIT only when the view is intrinsically a top-N.

sql
-- ❌ WRONG - ORDER BY without LIMIT (MDL030 / CE0174)
create view entity Finance.TopCustomers (...) as (
  select c.Name as CustomerName, sum(o.Amount) as TotalSpent
  from Finance.Customer as c
  inner join Finance.Order_Customer/Finance.Order as o
  GROUP by c.Name
  ORDER by TotalSpent desc        -- needs a LIMIT
);

-- ✅ CORRECT (preferred) - let the consuming page/microflow sort
create view entity Finance.CustomerTotals (...) as (
  select c.Name as CustomerName, sum(o.Amount) as TotalSpent
  from Finance.Customer as c
  inner join Finance.Order_Customer/Finance.Order as o
  GROUP by c.Name
);

-- ✅ ALSO VALID - an intrinsic top-N view (ORDER BY paired with LIMIT)
create view entity Finance.TopCustomers (...) as (
  select c.Name as CustomerName, sum(o.Amount) as TotalSpent
  from Finance.Customer as c
  inner join Finance.Order_Customer/Finance.Order as o
  GROUP by c.Name
  ORDER by TotalSpent desc
  LIMIT 100
);

Why these rules matter:

  • Explicit aliases: Required for proper OQL-to-entity attribute mapping in Mendix
  • ORDER BY/LIMIT: Omitting both keeps the view flexible (each page/microflow sorts and paginates as it needs); when you do sort, ORDER BY must be paired with LIMIT (MDL030)

UNION / UNION ALL are supported in view-entity OQL and round-trip cleanly — use them to combine multiple row kinds in one view (e.g. category rows plus group subtotals). Column count and types must line up across branches; ORDER BY (with its LIMIT) applies to the whole unioned result, not a single branch.

sql
mdl 1;
create or modify view entity Ledger.CategoryAndSubtotals (
  Label: string(100), Amount: decimal
) as (
  select c.Name as Label, sum(t.Amount) as Amount
  from Ledger.Category as c
  left join Ledger.Transaction_Category/Ledger.Transaction as t
  group by c.Name
  union all
  select 'TOTAL' as Label, sum(t.Amount) as Amount
  from Ledger.Transaction as t
);
1. Aggregate Functions (MUST BE LOWERCASE)
sql
-- ❌ WRONG - Uppercase will fail
sum(o.Amount)
avg(o.Amount)
max(o.OrderDate)
min(o.Amount)

-- ✅ CORRECT - Lowercase
sum(o.Amount)
avg(o.Amount)
max(o.OrderDate)
min(o.Amount)
2. COUNT Function
sql
-- ❌ WRONG - count(*) not supported in Mendix OQL
count(*)

-- ✅ CORRECT - Count by ID or entity
count(t.ID)           -- Count by ID attribute
count(t)              -- Count entity instances

Counting rows of a view entity: a view entity has no ID, so count(v.ID) over a view does not work. Count a column that is never empty instead — count(v.Name) for a column every row fills. The same goes for a literal: count(1) / sum(1) look harmless but fail on HSQLDB, Studio Pro's default database (see View columns and HSQLDB below).

View columns, GROUP BY and HSQLDB (checked: MDL033–MDL038)

Measured on mxbuild 11.13.0 and run --local against HSQLDB and PostgreSQL (ako/mxcli#981). mxcli check reports each:

WriteNotWhy
case when r.A = r.B then true else false end as Samer.A = r.B as Samea comparison is not a select expression: CE0174 (MDL033)
count(r.Name) next to group by r.Seasoncount(r.Season) next to group by r.Seasonaggregating a grouped column (also through datepart(…) or r.Season + 1) is CE0174 (MDL034)
every plain column in the GROUP BY, or aggregatedr.Name next to group by r.Season (or group by r.ID)CE0174 (MDL035)
the select expression equal to a GROUP BY expression (group by datepart(YEAR, r.D) → select datepart(YEAR, r.D))datepart(MONTH, r.D) next to group by datepart(YEAR, r.D)builds, then the database refuses it when the view is read — PostgreSQL 42803, HSQLDB 42574 (MDL036)
count(r.Name), sum(cast(1 as Integer)), sum(case when … then 1 else 0 end)sum(1), count(1), max(0), count('x'), count(true), sum(0.0), sum(1.5)Mendix sends the literal as an untyped parameter; HSQLDB refuses with 42567 "data type cast needed" (MDL037 warning)
cast(1 as Integer) as One, cast('Label' as String) as Kind1 as Onethe view reads fine, but on HSQLDB v.One + 1 returns 11 (string concatenation; PostgreSQL returns 2) and aggregating the column fails (MDL038 note)
r.Name + ' x' as S declared string(200)declared string or string(100)string concatenation is a derived String(200) whatever its operands; anything else is CE6770 (MDL031)
a view attribute over an AutoNumber column declared longdeclared autonumberCE6770; refused under mdl 1;, a warning without the header (MDL-V1-VIEWAUTONUMBER)

A decimal literal as a column (0.0 as Amount) is sent with a cast and is fine; inside an aggregate (sum(0.0)) it fails like the others. avg(1) runs. A bare string label such as 'TOTAL' as Label only draws the MDL038 note — it reads fine as long as nothing aggregates it.

Aggregate Function Return Types
FunctionInput TypeReturnsMDL Declaration
count(expr)anyIntegerattr: integer
sum(expr)IntegerIntegerattr: integer
sum(expr)DecimalDecimalattr: decimal
avg(expr)any numericDecimalattr: decimal
max(expr) / min(expr)IntegerIntegerattr: integer
max(expr) / min(expr)DecimalDecimalattr: decimal
max(expr) / min(expr)DateTimeDateTimeattr: datetime
datepart(part, expr)DateTimeIntegerattr: integer
length(expr)StringIntegerattr: integer

Key rule: count() and avg() have fixed return types. sum(), min(), max() preserve the input type.

3. DATEPART Function (Comma Syntax)
sql
-- ✅ CORRECT - Use comma syntax
datepart(YEAR, t.TransactionDate)
datepart(MONTH, t.TransactionDate)
datepart(QUARTER, t.TransactionDate)
datepart(WEEK, t.TransactionDate)
datepart(DAY, t.TransactionDate)

-- ❌ WRONG - FROM syntax not supported
DATEPART(YEAR from t.TransactionDate)
4. Enumeration Comparisons (String Literals)
sql
-- ❌ WRONG - Qualified enum names
t.TransactionType = Finance.TransactionType.INCOME
t.Status != Finance.TransactionStatus.VOID

-- ✅ CORRECT - Use string literals
t.TransactionType = 'INCOME'
t.Status != 'VOID'
5. Division Operator (Colon, not Slash)
text
-- ❌ WRONG - Using / causes parsing errors
select amount / quantity as price
select (total - discount) * 100.0 / total as percentage

-- ✅ CORRECT - Use : for division
select amount : quantity as price
select (total - discount) * 100.0 : total as percentage
6. ORDER BY with Aliases
text
-- ❌ WRONG - Using expressions in ORDER BY
ORDER by datepart(YEAR, t.TransactionDate) desc

-- ✅ CORRECT - Use column aliases
select
  datepart(YEAR, t.TransactionDate) as OrderYear
from Finance.Transaction as t
ORDER by OrderYear desc
6b. ORDER BY DESC on a nullable column puts the nulls FIRST

ORDER BY <attribute> DESC does not give you the newest rows when some rows leave that attribute empty. Mendix emits the ordering with no null placement, so the database default applies — on PostgreSQL, DESC means NULLS FIRST.

text
-- ❌ MISLEADING - the empty rows come back first, so a top-N is not the top N
select g.Label as Label from Sudoku.Game as g
order by g.DealtAt desc
limit 5

-- ✅ CORRECT when the attribute is optional
select g.Label as Label from Sudoku.Game as g
order by g.DealtAt desc nulls last
limit 5

This is worth knowing because it does not look like a null problem. The result is stable across runs, so it reads as "the ordering is being ignored" rather than "the sort key is empty for some rows" — and the natural next step, falling back to order by id desc, answers a different question (insertion order, which only matches recency when nothing backdates a row).

Check before concluding anything: select count(*) as n from Sudoku.Game where DealtAt = empty.

Measured on Mendix 11.13.0 / PostgreSQL: with values present, ORDER BY on a DateTime is emitted to the database correctly and orders correctly. Null placement is database-specific (SQL Server and Oracle differ), so being explicit is also the portable choice.

7. Operators (Use != not <>)
sql
-- ❌ WRONG - <> causes errors in Mendix
where t.Status <> 'VOIDED'

-- ✅ CORRECT - Use !=
where t.Status != 'VOIDED'

Note: Both != and <> are valid in standard SQL, but Mendix OQL only accepts !=.

8. IN Expression Syntax
sql
-- ✅ IN with value list
where t.Status in ('ACTIVE', 'PENDING', 'REVIEW')

-- ✅ IN with subquery
where t.CustomerId in (
  select c.CustomerId from Shop.Customer as c where c.IsVIP = true
)

-- ✅ Enumeration values use identifiers, not captions
where t.Priority in ('HIGH', 'CRITICAL')  -- Not 'High', 'Critical'
9. Subqueries (Scalar and Correlated)
text
-- ✅ Scalar subquery in SELECT (returns single value)
select
  p.Name as ProductName,
  p.Price - (select avg(p2.Price) from Shop.Product as p2) as DiffFromAvg
from Shop.Product as p

-- ✅ Scalar subquery in WHERE
where p.Price > (select avg(p2.Price) from Shop.Product as p2)

-- ✅ Correlated subquery (references outer query by attribute)
select
  o.OrderNumber as OrderNumber,
  (select count(o2.OrderId) from Shop.Order as o2 where o2.CustomerId = o.CustomerId) as CustomerOrderCount
from Shop.Order as o

-- ✅ Correlated subquery via association (compare to .ID)
select
  p.Name as ProductName,
  (select pr.PriceInEuro from Shop.Price as pr
   where pr/Shop.Price_Product = p.ID
   ORDER by pr.StartDate desc limit 1) as LatestPrice
from Shop.Product as p

-- ❌ WRONG - bare alias without .ID
where pr/Shop.Price_Product = p    -- Doesn't resolve

-- ✅ CORRECT - compare to entity .ID
where pr/Shop.Price_Product = p.ID
10. Association Path Syntax
sql
-- Association paths in OQL use '/' not '.'
-- ✅ CORRECT - slash prefix for association traversal
where l/Library.Loan_Member = m.ID
join l/Library.Loan_Book/Library.Book as b

-- ❌ WRONG - dot instead of slash
where l.Library.Loan_Member = m.ID     -- Error: does not resolve
11. JOIN Syntax (Association Traversal and ON Clause)

Mendix OQL supports both association traversal and SQL-style JOIN ON:

sql
-- ✅ Association traversal (uses Mendix association path)
from Shop.Order as o
inner join o/Shop.Order_Customer/Shop.Customer as c

-- ✅ JOIN ON clause (SQL-style, for any condition)
from Shop.Order as o
inner join Shop.Customer as c on o.CustomerId = c.CustomerId

-- ✅ LEFT OUTER JOIN with ON clause
from Shop.Product as p
left outer join Shop.CompetitorProduct as cp on p.ProductCode = cp.ProductCode

When to use each approach:

  • Association traversal (alias/Module.Association/entity): When joining on a Mendix-defined association
  • JOIN ON (join entity on condition): When joining on arbitrary conditions or non-association fields

Step-by-Step Process

Step 1: Define VIEW Entity Schema

Always include @Position annotation:

sql
/**
 * View entity description
 *
 * @since 1.0.0
 */
@position(300, 500)
create view entity Module.ViewName (
  Attribute1: type,
  Attribute2: type,
  -- ... more attributes
) as (
  -- OQL query goes here
);
Step 1b: Know the two clause orders

Mendix OQL accepts the select list in either position, and mxcli reads both:

text
-- Select-first. Write new views this way; the rest of this skill assumes it.
select c.Name as Name, count(o.ID) as Orders
from Shop.Customer as c
group by c.Name

-- From-first. Same query. This is what STUDIO PRO STORES, so it is what
-- `describe entity` gives you back — copy it, edit it, exec it unchanged.
from Shop.Customer as c
group by c.Name
select c.Name as Name, count(o.ID) as Orders

Note where group by sits: in the from-first order every clause except order by / limit comes before the select list, and the grammar enforces that. from … select … group by … is a parse error, not a variant.

Do not rewrite a described view into select-first just to make it look familiar — the stored text is what MxBuild validates against, and a needless rewrite is a diff for nothing.

Show full SKILL.md (986 more words)Show less
Step 1c: Selecting an id makes an ASSOCIATION, not an attribute

Selecting a persistent entity's ID under an alias gives the view entity an association to that entity. The alias becomes the association's name, and the column is not one of the view entity's attributes — so do not declare one for it:

sql
mdl 1;
create view entity Sales.OrdersVE (
  order_date: DateTime              -- one attribute…
) as (
  from Sales."Order" as o
  select o.ID        as persistent_order   -- …but two columns
       , o.OrderDate as order_date
);

mxcli creates the association member from that column. There is no separate statement for it, and create association with a view entity at either end is refused — Mendix rejects it (CE6771), because the association needs an OqlViewAssociationSource that a plain one does not have.

Two rules:

  • The alias must be free in the module, case-insensitively. It is the association's name, and Mendix reports "Duplicate name 'Meter' in module 'Trends'. Entities, associations and enumerations cannot share names." So as meter beside an entity called Meter fails — name it MeterRef.
  • Reach the target through a join if it is not the FROM entity, and select the id off that alias: join r/Trends.Reading_Meter/Trends.Meter as m … select m.ID as MeterRef.
  • Do not declare the id column as an attribute. MeterRef: Trends.Meter (or Trends.Meter.ID) in the attribute list parses — a bare qualified name is how MDL spells an enumeration type — and would be stored as an enumeration naming an entity: CE1613 at build, or mx check failing to load the project. mxcli refuses it (MDL080). The attribute list holds only the non-id columns.

Consider the flat alternative first. An association costs a second query at runtime — the view returns the foreign key, and the client then fetches the referenced objects in a batched IN (...) per page, materialising real objects in its state. Selecting a string copy instead is one statement, one join, no second retrieve, and the id is still there to look the object up with:

sql
select cast(m.ID as string) as MeterId, m.MeterCode as MeterCode, …

Use the association when you want to bind widgets over it (MeterRef/MeterCode); use the cast when you just need the value.

Step 2: Write SELECT Clause
  • Use lowercase aggregate functions: sum(), avg(), count()
  • Use count(entity.ID) not count(*) — over a view entity (no ID), count a non-null column
  • Never aggregate a literal (sum(1), count(1)): it fails on HSQLDB — count a column, or sum(cast(1 as Integer))
  • Create meaningful aliases for all columns
  • Use : for division operations
Step 3: Write FROM Clause
  • Use table aliases (AS t, AS c, etc.)
  • Navigate associations: Entity_Association/TargetEntity
Step 4: Add JOINs if Needed
sql
-- Association join syntax
inner join Shop.Order_Customer/Shop.Customer as c
left join Shop.Product_Category/Shop.Category as cat
Step 5: Add WHERE Clause
  • Use string literals for enum comparisons: 'value'
  • Use standard comparison operators: =, !=, >, <, >=, <=
Step 6: Add GROUP BY if Using Aggregates
  • Include all non-aggregated columns
  • Use same expressions as SELECT (e.g., datepart())
Step 7: Verify Aliases
  • Ensure ALL SELECT columns have explicit AS aliases
  • Aliases must match entity attribute names exactly
Step 8: Validate Before Executing
bash
mxcli check view.mdl -p app.mpr --references

This catches type mismatches (e.g., declaring long for a count() column that returns integer), missing module references, and OQL syntax errors — before they become MxBuild errors like CE6770 ("View Entity is out of sync with the OQL Query").

Step 9: Final Check
  • Prefer no ORDER BY/LIMIT/OFFSET — let the UI component or microflow sort and paginate
  • If the view is intrinsically a top-N, ORDER BY is allowed only when paired with a LIMIT (MDL030 / CE0174)

Common Mistakes to Avoid

❌ Mistake 1: Uppercase Aggregates
sql
-- WRONG
select sum(amount) from ...

-- CORRECT
select sum(amount) from ...
❌ Mistake 2: Using count(*)
text
-- WRONG
select count(*) from Finance.Transaction

-- CORRECT
select count(t.ID) from Finance.Transaction as t

-- CORRECT over a view entity, which has no ID: count a non-null column
select count(v.Name) from Finance.TransactionSummary as v
❌ Mistake 3: Qualified Enum Names
sql
-- WRONG
where t.Status = Finance.Status.ACTIVE

-- CORRECT
where t.Status = 'ACTIVE'
❌ Mistake 4: Slash for Division
text
-- WRONG
select total / count as average

-- CORRECT
select total : count as average
❌ Mistake 5: Missing Column Aliases
text
-- WRONG
select
  fl.ForecastDate,
  fl.ProjectedIncome
from Finance.ForecastLine as fl

-- CORRECT
select
  fl.ForecastDate as ProjectionDate,
  fl.ProjectedIncome as ProjectedIncome
from Finance.ForecastLine as fl
❌ Mistake 6: Dot Instead of Slash for Association Paths
sql
-- WRONG - dot notation for association
where l.Library.Loan_Member = m.ID

-- CORRECT - slash notation
where l/Library.Loan_Member = m.ID
❌ Mistake 7: Bare Alias in Association Comparison
sql
-- WRONG - comparing association to bare entity alias
where pr/Shop.Price_Product = p

-- CORRECT - compare to entity .ID
where pr/Shop.Price_Product = p.ID
❌ Mistake 8: ORDER BY without a LIMIT in a VIEW
sql
-- WRONG - ORDER BY alone (MDL030 / CE0174)
create view entity Finance.TopItems (...) as (
  select ...
  ORDER by Amount desc          -- needs a LIMIT
);

-- CORRECT (preferred) - let the UI sort/paginate
create view entity Finance.ItemTotals (...) as (
  select ...
  -- no ORDER BY / LIMIT
);

-- ALSO VALID - an intrinsic top-N view
create view entity Finance.TopItems (...) as (
  select ...
  ORDER by Amount desc
  LIMIT 100
);

Testing OQL Queries

Use mxcli oql to test queries against a running Mendix runtime (read-only preview mode):

bash
# basic query (reads .docker/.env for connection settings)
mxcli oql -p app.mpr "select Name, Email from MyModule.Customer"

# json output for piping to jq
mxcli oql -p app.mpr --json "SELECT count(c.ID) FROM MyModule.Order AS c" | jq '.[0]'

# Explicit connection (no project file needed)
mxcli oql --host localhost --port 8090 --token 'AdminPassword1!' "SELECT 1"

# Test a view entity query before embedding it
mxcli oql -p app.mpr "select datepart(YEAR, o.OrderDate) as Year, sum(o.Total) as Revenue from Sales.Order as o GROUP by datepart(YEAR, o.OrderDate)"

The app must be running first: mxcli docker run -p app.mpr --wait

Troubleshooting: If you get "Action not found: preview_execute_oql", the Docker stack needs the -Dmendix.live-preview=enabled JVM flag. Re-initialize with: mxcli docker init -p app.mpr --force, then restart with mxcli docker run -p app.mpr --wait.

Workflow: OQL → VIEW ENTITY
  1. Write and test interactively: mxcli oql -p app.mpr "select ..."
  2. Iterate until the query returns expected results
  3. Embed in a VIEW ENTITY with matching column aliases and attribute types
  4. Validate before executing: mxcli check view.mdl -p app.mpr --references to catch type mismatches (e.g., long vs integer for count())
  5. Apply and rebuild: mxcli exec view.mdl -p app.mpr && mxcli docker run -p app.mpr --fresh --wait

Integration with MDL Linter

The MDL linter checks for common OQL issues:

Rule: consistency/oql-syntax

  • Validates VIEW entity OQL queries
  • Checks for ORDER BY without LIMIT/OFFSET (CE0174)
  • Checks for missing/empty SELECT or FROM clauses

How to Fix Linter Errors:

bash
# lint a file
mendix> lint file 'path/to/file.mdl';

# Common error: ORDER by without limit
# error: view entity X: ORDER by requires limit or OFFSET. Studio Pro error: CE0174
# Fix: add limit clause

References

Summary Checklist

When writing OQL queries for VIEW entities, always verify:

  • CRITICAL: Entity has @Position annotation (e.g., @Position(300, 500))
  • CRITICAL: All SELECT columns have explicit AS aliases matching entity attributes
  • ORDER BY omitted so the UI sorts — or, for a top-N view, ORDER BY paired with a LIMIT (MDL030 rejects ORDER BY without LIMIT)
  • Aggregate functions are lowercase (sum, avg, count, max, min)
  • Using count(entity.ID) not count(*) (a view entity has no ID: count a non-null column)
  • No literal aggregated (sum(1), count(1)) and no bare 1 as X column a consumer will add to — cast it (HSQLDB)
  • With GROUP BY: every non-aggregated column equals a GROUP BY expression, and no aggregate reads a grouped column
  • DATEPART uses comma syntax: datepart(YEAR, field)
  • Enum comparisons use enumeration identifiers, not captions: 'HIGH' not 'High'
  • IN expressions use correct syntax: in ('VAL1', 'VAL2') or in (select ...)
  • Division uses colon: amount : quantity
  • Inequality uses != not <>
  • All non-aggregated columns are in GROUP BY
  • Association paths use / not .: alias/Module.Assoc not alias.Module.Assoc
  • Association comparisons use .ID: pr/Shop.Price_Product = p.ID not = p
  • Association navigation uses correct syntax: Entity_Assoc/Target as alias
  • JOIN ON clauses use comparison operators: on a.Field = b.Field
  • Subqueries are enclosed in parentheses and return appropriate values
  • Validate before executing: Run mxcli check script.mdl -p app.mpr --references to catch type mismatches

Following these rules ensures your OQL queries will parse and execute correctly in Mendix runtime.

© mendixlabs, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 1 other file in .claude/skills/mendix/write-oql-queries of mendixlabs/mxcli.

  • SKILL.md
  • reference/patterns.md

Open the folder on GitHubat commit a924d11

Compare with similar skills

Write Oql Queries next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Write Oql Queries compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Write Oql Queries this skillmendixlabs/mxcli128—~6.2kAutomated safety check: NotesApache-2.0
Ddd Aggregateruvnet/ruflo74k—~774Automated safety check: NotesMIT
ClickHouse Query Performance Validationcomet-ml/opik22k—~2.1kAutomated safety check: PassApache-2.0
Frontend Query Mutationlangflow-ai/langflow156k—~979Automated safety check: PassMIT
Agentdb Queryruvnet/ruflo74k—~947Automated safety check: NotesMIT
Container Queriesthedaviddias/Front-End-Checklist74k—~526Automated safety check: PassMIT

Similar skills

  • Ddd Aggregate

    ruvnet/ruflo

    Scaffold an aggregate root with entity, value objects, repository interface, domain events, and test stubs.

    74k GitHub stars~774 tokensUpdated today
    DevelopmentAuto-check: notes
  • Measures what a ClickHouse query change actually costs, turning a suspicion that a query is slow into numbers a reviewer can act on before it merges.

    22k GitHub stars~2.1k tokensUpdated today
    DatabasesAuto-check passed
  • Frontend Query Mutation

    langflow-ai/langflow

    Guide for implementing Langflow frontend query and mutation patterns with Axios and TanStack React Query v5.

    156k GitHub stars~979 tokensUpdated today
    Frontend & DesignAuto-check passed
  • Agentdb Query

    ruvnet/ruflo

    Query AgentDB through the controller bridge -- semantic routing, hierarchical recall, causal graphs, context synthesis, pattern store/search

    74k GitHub stars~947 tokensUpdated today
    Auto-check: notes
  • Container Queries

    thedaviddias/Front-End-Checklist

    A skill your agent uses when reviewing stylesheets, component styles, and responsive behavior related to Use container queries for component-level responsiveness.

    74k GitHub stars~526 tokensUpdated yesterday
    Frontend & DesignAuto-check passed
  • Query Netdata Agents

    netdata/netdata

    Query or explain direct Netdata Agent APIs and Functions; review direct-query recipes or helpers; troubleshoot bearer authentication.

    81k GitHub stars~2k tokensUpdated today
    DevOps & CloudAuto-check: notes

More from mendixlabs/mxcli

All 75 skills in this repo
  • Mendix Odata Pushdown

    mendixlabs/mxcli

    Push OData query options into the SQL of a Mendix resource served by a read microflow, so $filter, $orderby, $top, $skip, $count and the key lookup reach the database instead of being silently…

    128 GitHub stars~2.5k tokensUpdated today
    Auto-check passed
  • Mendix Vega Charts

    mendixlabs/mxcli

    Chart a Mendix app with Vega-Lite through a pluggable widget that takes the specification and the data as separate properties, so the model emits rows and never assembles a chart payload.

    128 GitHub stars~3k tokensUpdated today
    Auto-check passed
  • Agents

    mendixlabs/mxcli

    Author Mendix AI agent documents in MDL — Model, Knowledge Base, Consumed MCP Service and Agent, with variables, tools and multi-line prompts.

    128 GitHub starsUsed in 1 repo~2.2k tokens
    Auto-check passed
  • Mendix Bulk Oql Dml

    mendixlabs/mxcli

    Run set-based INSERT, UPDATE and DELETE against Mendix entities through OQL statements, which the runtime supports and Studio Pro cannot author.

    128 GitHub stars~1.4k tokensUpdated today
    Auto-check passed
  • Mock REST APIs

    mendixlabs/mxcli

    Stand up an HTTP endpoint you control instead of a live third-party API, and point the Mendix app at it — Prism from an OpenAPI contract, a constant swap, or a forward proxy.

    128 GitHub starsUsed in 1 repo~2.5k tokens
    Auto-check passed
  • REST Client

    mendixlabs/mxcli

    Call external REST APIs from Mendix — the three approaches (inline REST CALL, consumed REST client document, generated from OpenAPI) and how to choose.

    128 GitHub starsUsed in 1 repo~3.9k tokens
    Auto-check passed

Questions about Write Oql Queries

What does Write Oql Queries do?

Write OQL for Mendix VIEW entities — joins, aggregates, calculated fields, and the syntax the runtime actually accepts. Write Oql Queries is an agent skill from mendixlabs/mxcli. Write OQL for Mendix VIEW entities — joins, aggregates, calculated fields, and the syntax the runtime actually accepts.

When should I use Write Oql Queries?

Write Oql Queries fits situations like: creating a VIEW entity; building a report; analytics query.

How do I install Write Oql Queries in Claude Code?

Run `npx skills add mendixlabs/mxcli --skill write-oql-queries -a claude-code`. Or copy the skill folder (.claude/skills/mendix/write-oql-queries in mendixlabs/mxcli) into .claude/skills/write-oql-queries in your project. Claude Code loads it when a task matches its description.

How do I install Write Oql Queries in Codex?

Run `npx skills add mendixlabs/mxcli --skill write-oql-queries -a codex`. Or copy the skill folder (.claude/skills/mendix/write-oql-queries in mendixlabs/mxcli) into .agents/skills/write-oql-queries in your project. Codex loads it when a task matches its description.

Can I use Write Oql Queries in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add mendixlabs/mxcli --skill write-oql-queries -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/write-oql-queries, .gemini/skills/write-oql-queries, .github/skills/write-oql-queries and .opencode/skills/write-oql-queries in your project.

What does Write Oql Queries need to run?

Going by SKILL.md and its folder, Write Oql Queries needs the command-line tools its instructions call (jq).

Does Write Oql Queries access the network?

SKILL.md names 1 domain. As links in the text: docs.mendix.com. This is read from the text; nothing was executed.

Is Write Oql Queries safe to install?

Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.

What licence does Write Oql Queries use?

Write Oql Queries is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Write Oql Queries use?

About 6.2k tokens (SKILL.md is roughly 25k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Write Oql Queries?

Skills that share tags, products or a category with Write Oql Queries: Ddd Aggregate (ruvnet/ruflo, 74k stars), ClickHouse Query Performance Validation (comet-ml/opik, 22k stars), Frontend Query Mutation (langflow-ai/langflow, 156k stars) and Agentdb Query (ruvnet/ruflo, 74k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Write Oql Queries?

mendixlabs (a GitHub organization) maintains it in mendixlabs/mxcli, which has 128 GitHub stars. The repository holds 75 skills in this directory. The repository was last updated on October 7, 2026.

Source: mendixlabs/mxcli on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.