Use the CQL fluent API for SELECT, INSERT, UPDATE, DELETE. Understand transactions and the difference from raw SQL.
SELECT, INSERT, UPDATE, DELETE objectsdb.run() vs cds.run()CAP's query API is what keeps handlers database-agnostic. The same SELECT.from('Books').where(...) works on SQLite in dev and HANA in production. Understanding the API — including its limits — lets you write efficient, portable data access code.
// All books
const all = await SELECT.from('Books')
// With columns
const titles = await SELECT.from('Books').columns('title', 'price')
// With WHERE
const cheap = await SELECT.from('Books').where({ price: { '<=': 20 } })
// Shorthand where
const byId = await SELECT.from('Books', { ID: 'abc-123' })
// One row
const book = await SELECT.one.from('Books').where({ ID: req.data.ID })
const result = await SELECT.from('Books').columns(b => {
b.ID,
b.title.as('bookTitle'),
b.price,
b('stock * price').as('inventory_value')
})
// Operator comparison
SELECT.from('Books').where('price >', 10)
SELECT.from('Books').where('price between', 10, 'and', 50)
// Object style
SELECT.from('Books').where({ price: { '>': 10 }, stock: { '>': 0 } })
// Parameterized (safe from injection)
SELECT.from('Books').where(`title like`, '%CAP%')
// OR conditions
SELECT.from('Books').where(`status = 'ACTIVE' or stock > 100`)
const paginated = await SELECT.from('Books')
.orderBy('price asc', 'title')
.limit(20, 40) // limit 20, skip 40 → page 3
// With columns
const topPriced = await SELECT.from('Books')
.columns('title', 'price')
.orderBy({ price: 'desc' })
.limit(10)
// Inline expand
const orders = await SELECT.from('Orders').columns(o => {
o.ID, o.status,
o.items(i => { i.book_ID, i.quantity, i.price })
})
// Generates: SELECT ... FROM Orders LEFT JOIN OrderItems ON ...
// Single row
await INSERT.into('Books').entries({
ID: cds.utils.uuid(),
title: 'SAP CAP Handbook',
price: 49.99
})
// Multiple rows
await INSERT.into('Books').entries([book1, book2, book3])
// Return inserted row (HANA / PostgreSQL)
const inserted = await INSERT.into('Books').entries(data).returning('*')
// Update by key
await UPDATE('Books', { ID: req.data.ID }).with({ price: 59.99 })
// Update with expression
await UPDATE('Books').set('stock -= 1').where({ ID: bookId })
// Conditional update
await UPDATE('Books')
.set({ status: 'OUT_OF_STOCK' })
.where({ stock: 0 })
// Delete by key
await DELETE.from('Books', { ID: req.data.ID })
// Delete with WHERE
await DELETE.from('Books').where({ status: 'ARCHIVED', updatedAt: { '<': cutoff } })
// Inside a handler: uses the request's transaction automatically
const db = await cds.connect.to('db')
const result = await db.run(SELECT.from('Books'))
// CAP shorthand — same as above when called from within a handler
const result = await cds.run(SELECT.from('Books'))
// Direct execute (no transaction wrapper)
await cds.db.run(`SELECT * FROM Books WHERE price < ?`, [50])
const tx = cds.transaction(req) // attach to request transaction
await tx.run(INSERT.into('AuditLog').entries({ ... }))
await tx.run(UPDATE('Orders').set({ status: 'DONE' }).where({ ID }))
// Both committed when the request handler returns successfully
// Rolled back automatically if handler throws
| Feature | Support |
|---|---|
| Subqueries in WHERE | CQL does not support subqueries in WHERE — use JOIN or multiple queries |
| Database functions (HANA-specific) | Use cds.hana annotations or raw db.run(native SQL) |
| Aggregates in SELECT | Supported: SUM(price), COUNT(*) |
| GROUP BY | Supported via .groupBy('category') |
| Unions | Not in fluent API — use raw SQL |
GET /catalog/ExpensiveBooks that returns books with price > 30, ordered by price desc, max 10updateStock action that uses UPDATE('Books').set('stock -=', qty).where({ ID })author inline and returns title + author nameSELECT.one.from(...) returns a single object or undefined.where() supports both object style and string expressionsINSERT.into().entries() accepts single object or arrayUPDATE().set().where() supports computed expressions like 'stock -=', 1