Introduction
Query Language is an essential tool to communicate with a database to perform operations such as retrieving data, inserting data, updating data and deleting data. Query languages are designed to be concise and efficient, allowing users to specify complex operations with minimal effort. There are several types of query languages, including structured query language (SQL), which is the most common query language used in relational databases, and non-relational query languages such as MongoDB's query language. |
Simillarly SAP Cloud Application Programming Model provides a query language called CDS Query Language (in short CQL). In this blog post, I will try to explain my understandings and learnings about CQL. Let's get started!!
Note: this blog post is written for CAP framework with Node.js runtime.
Query Styles
Fluent API Examples
// retrieve data await SELECT.from(Roots).where({ID:'38a13fed-f2d1-45bd-91ec-642053889c92'}); // create data input = {name:'root 6', descr:'descr 6', dfield:'2023-02-11', tfield:'14:18:29', dtfield: '2022-02-11T04:08:29Z',tsfield: '2022-02-11T04:08:29.129Z', nfield: 290,afield: 23891100.123456}; await INSERT.into(Roots).entries(input);
Querying API Examples
// retrieve data await srv.read(Roots).where({ID:'38a13fed-f2d1-45bd-91ec-642053889c92'}); await cds.read(Roots).where({ID:'38a13fed-f2d1-45bd-91ec-642053889c92'}); // create data input = {name:'root 6', descr:'descr 6', dfield:'2023-02-11', tfield:'14:18:29', dtfield:'2022-02-11T04:08:29Z', tsfield:'2022-02-11T04:08:29.129Z', nfield: 290, afield:23891100.123456}; await cds.create(Roots).entries(input); await srv.create(Roots).entries(input);
Note:
// using fully qualified entity names await SELECT.from('cap.cql.db.Roots') // using reflected entity definition const {Roots} = cds.entities; await SELECT.from(Roots)
// tagged template string await SELECT.from `cap.cql.db.Roots` .columns `name, descr as description` // cql column expressions const { Roots } = cds.entities; await SELECT.from (Roots) .columns ('name', 'descr as description') // cqn expression objects await SELECT.from `cap.cql.db.Roots` .columns ({ref:['name']}, {ref:['descr'], as:'description'}) // array of columns await SELECT.from `cap.cql.db.Roots` .columns (['name', 'descr as description']) // projection function await SELECT.from `cap.cql.db.Roots` .columns (r => { r.name, r.descr.as('description') });
Schema Information for Sample Code
As shown in above picture, Roots is the main entity which has 4 child entities. AssocSItems and AssocMItems are associations to Roots with 1-to-1 and 1-to-N cardinality respectively. Simillarly CompSItems and CompMItems are compositions to Roots with 1-to-1 and 1-to-N cardinality respectively. Also Roots2 is another entity with more than one key.
All query examples are explained based on this schema. Also sample code is provided as git repository here: btp-cap-demo-usecases.
Execute queries using Different DB
Executing queries to verify results before incorporating them into code is a common requirement. To facilitate this, the CAP framework offers the cds repl command, which enables live interaction with Node.js APIs and allows for query execution.
Following are the commands that you can use to test the queries provided in later part of this blog post.
git clone https://github.com/SAP-samples/btp-cap-demo-usecases.git cd cap-cql-examples npm install cds deploy --to sqlite cds repl await cds.test() const { Roots, AssocSItems, AssocMItems, CompSItems, CompMItems, Roots2 } = cds.entities
git clone https://github.com/SAP-samples/btp-cap-demo-usecases.git cd cap-cql-examples npm install cds add hana --for hybrid cf login cds deploy --to hana:cap-cql-examples-db --profile hybrid --store-credentials cds repl --profile hybrid await cds.test() const { Roots, AssocSItems, AssocMItems, CompSItems, CompMItems, Roots2 } = cds.entities
git clone https://github.com/SAP-samples/btp-cap-demo-usecases.git cd cap-cql-examples npm install npm add @cap-js/sqlite -D jq '.cds = {requires:{db:"sql"}}' package.json > temp.json && mv temp.json package.json cds repl await cds.test() const { Roots, AssocSItems, AssocMItems, CompSItems, CompMItems, Roots2 } = cds.entitiesNote: You might need to install necessary command line tools like cf cli, jq, git etc.
Retrieve Data: Select Query Examples
SELECT queries are used to retrieve data from one or more tables of database. Let's look at few examples with different variations.
await SELECT.from(Roots);
// one key await SELECT.from(Roots).byKey('38a13fed-f2d1-45bd-91ec-642053889c92'); await SELECT.from(Roots, '38a13fed-f2d1-45bd-91ec-642053889c92'); // more than one key await SELECT.from(Roots2).byKey({first: 101, second:201}); await SELECT.from(Roots2, {first: 101, second:201});
// string field await SELECT.from(Roots).where({name:'root 1'}); await SELECT.from(Roots).where(`name like '%1%'`); await SELECT.from(Roots).where({category:{in:['rc1', 'rc2']}}) await SELECT.from(Roots).where({category:{'not in':['rc1', 'rc2']}}) await SELECT.from(Roots).where({category:['rc1', 'rc2']}) // date field await SELECT.from(Roots).where({dfield:'2022-02-11'}); await SELECT.from(Roots).where(`dfield < '2022-06-11'`); // time field await SELECT.from(Roots).where({tfield:'04:08:29'}); await SELECT.from(Roots).where({tfield:{'>':'14:08:29'}}); // datetime or timestamp field await SELECT.from(Roots).where(`dtfield < '2022-06-01T14:08:29Z'`); await SELECT.from(Roots).where(`tsfield > '2022-10-01T04:08:29.000Z'`); // numeric or amount field await SELECT.from(Roots).where(`nfield between 1 and 500`); await SELECT.from(Roots).where({nfield:{ between: 501, and: 1000}}); await SELECT.from(Roots).where(`afield < 299345.451`); // more than one non-key field await SELECT.from(Roots).where({and: {dfield:'2022-10-01', nfield:123}}); await SELECT.from(Roots).where(`dfield > '2022-10-01' and nfield > 500`); await SELECT.from(Roots) .where({and:{ dfield:{'>': '2022-10-01'}, nfield:{'>':600} }});
await SELECT.from(Roots).where(`nfield > 900 or nfield < 200`); await SELECT.from(Roots).where({or:{ nfield:{'>': 900}, nfield:{'<':200} }});
await SELECT.from(Roots).columns(['name','descr']) await SELECT.from(Roots).columns(r=>{r.name, r.descr}) await SELECT.from(Roots).where({name:{ like: '%root%'}})
// getting few column of associated and composition entity await SELECT.from(Roots) .columns(r=>{r.name, r.descr, r.asitem(asi=>{asi.asi_sfield}) }); await SELECT.from(Roots) .columns(r=>{r.name, r.descr, r.csitem(csi=>{csi.csi_sfield}) }) // getting all columns of associated and composition entity await SELECT.from(Roots) .columns(r=>{r.name, r.descr, r.asitem(asi=>{asi`.*`}) }) await SELECT.from(Roots) .columns(r=>{r.name, r.descr, r.csitem(csi=>{csi`.*`}) }) // getting all columns of root and associated entity await SELECT.from(Roots).columns(r=>{r`.*`,r.asitem(asi=>{asi`.*`}) }) await SELECT.from(Roots).columns(r=>{r`.*`,r.amitems(ami=>{ami`.*`}) }) await SELECT.from(Roots).columns(r=>{r`.*`,r.csitem(csi=>{csi`.*`}) }) await SELECT.from(Roots).columns(r=>{r`.*`,r.cmitems(cmi=>{cmi`.*`}) }) // mix and match await SELECT.from(Roots) .columns(r=>{r.name, r.descr, r.asitem(asi=>{asi.asi_nfield}), r.amitems(ami=>{ami`.*`}) }); await SELECT.from(Roots) .columns(r=>{r.name, r.descr, r.csitem(csi=>{csi`.*`}), r.cmitems(cmi=>{cmi.csm_nfield}) }); //Use filter await SELECT.from `${Roots}[category='rc1']` await SELECT.from `${Roots}[category='rc1' and name like '%root%']` await SELECT.from(Roots) .columns(r=>{ r`.*`, r.amitems`[asm_nfield > 1010]`(ami=>{ami`.*`}) })
SELECT.from(Roots).columns(r=>{r`.*`,r.cmitems(cmi=>{cmi`.*`}) })
In above code snippet, The query is performed on the Roots entity using the SELECT.from method. The columns function is used to specify the desired columns in the query results. In this case, it selects all columns (.*) from the Roots entity. Additionally, it includes related cmitems entities using the nested callback function (cmi=>{cmi.*}), which indicates that all columns (.*) from the related cmitems entities are included in the query results.//get one entry await SELECT.one.from(Roots).where({category:'rc1'}); await SELECT.one.from(Roots) .columns(r=>{r`.*`,r.amitems(ami=>{ami`.*`}) }); //get distinct entry await SELECT.distinct.from(Roots) .columns('category').where({nfield:{'>':500}});
// count function await SELECT.from(Roots).columns(`count(*) as totalrows`); // case statement await SELECT.one.from(Roots) .columns(`count(*) as totalcount`, `sum(CASE WHEN nfield < 500 THEN 1 END) as lowcount`, `sum(CASE WHEN nfield >= 500 THEN 1 END) as highcount`); // aggregate functions: average, minimum, maximun etc await SELECT.one.from(Roots).columns(`avg(nfield) as average`); await SELECT.one.from(Roots).columns(`min(nfield) as minimum`); await SELECT.one.from(Roots).columns(`max(nfield) as maximum`); await SELECT.from(Roots).columns(`ceiling(afield) as afield`); // string function: concat, toupper, tolower await SELECT.from(Roots) .columns(`concat(name, ' ',descr) as name`, `length(name) as charcount` ); await SELECT.from(Roots) .columns(`toupper(name) as name`, `tolower(descr) as descr`) await SELECT.from(Roots) .columns(`year(dfield) as year`, `month(dfield) as month`, `day(dfield) as day` ); await SELECT.from(Roots) .columns(`hour(tfield) as hour`, `minute(tfield) as minute`, `second(tfield) as second` );Let's look at one of the above queries:
await SELECT.from(Roots).columns(`name`).orderBy('name desc'); await SELECT.from(Roots) .columns(`category`, `count(name) as itemscount`) .groupBy('category'); await SELECT.from(Roots) .columns('category','name','descr') .orderBy('category desc', 'name asc');
// Only 2 entries are fetched await SELECT.from(Roots).limit(2).where(`name like '%root%'`) // Only 2 rows are fetched after 3 rows (offset) await SELECT.from(Roots).limit(2,3).where(`name like '%root%'`)
Note:
await SELECT.from(Roots).columns('name',`asitem.asi_sfield`) await SELECT.from(Roots).columns(r=>{r.name, r.descr, r.asitem.asi_sfield }) await SELECT.from(Roots).columns(r=>{r.name, r.descr, r.amitems.asm_sfield }) await SELECT.from(Roots).where(`asitem.asi_nfield > 100`) cat = ['rc1', 'rc3']; await SELECT.from(Roots) .where `category in ${cat} and amitems.asm_nfield > 1000`
await SELECT.from(CompMItems)
.columns(cmi=>{cmi`.*`,cmi.root(r=>r`.*`)})
.where({root_ID:'38a13fed-f2d1-45bd-91ec-642053889c92'})
.orderBy('csm_nfield desc');
Create Data: Insert Query Examples
Insert queries are used to create entry in tables. Let's look at few examples below:
// create one entry onerowdata = {name:'root 6', descr:'descr 6', dfield: '2022-04-12', tfield:'04:08:29', dtfield: '2022-02-11T04:08:29Z', tsfield:'2022-02-11T04:08:29.129Z', nfield: 200, afield:'23891100.123456',category: 'rc3'}; await INSERT.into(Roots).entries(onerowdata); // create one entry with associated entry onerowdata = {name:'root 7', descr:'descr 7', dfield: '2023-04-12',tfield: '14:08:29', dtfield: '2023-02-11T04:08:29Z',tsfield: '2022-02-11T04:08:29.129Z', nfield: 200,afield: '23891100.123456',category: 'rc3', asitem_ID:'585dee53-69e5-47d9-b90d-945253d4af0d'}; await INSERT.into(Roots).entries(onerowdata); one = {name:'root 7', descr:'descr 7', dfield:'2023-04-12', tfield:'14:08:29', dtfield:'2023-02-11T04:08:29Z', tsfield:'2022-02-11T04:08:29.129Z', nfield:200, afield: '23891100.123456',category: 'rc3', amitems:[ {ID:'110f6a9f-b647-4e63-bdb2-03ec724d0d2b'}, {ID: '71bd1b1d-ca9c-403e-b435-99e58e8373c0'} ]}; await INSERT.into(Roots).entries(one); // create one entry with composition entry one = {name:'root 8', descr:'descr 8', dfield:'2023-04-12', tfield:'14:08:29', dtfield: '2023-02-11T04:08:29Z',tsfield: '2022-02-11T04:08:29.129Z', nfield: 200,afield: '23891100.123456',category: 'rc3', csitem:{csi_sfield:'csi field 7', csi_nfield:'700'} }; await INSERT.into(Roots).entries(one); one = {name:'root 9', descr:'descr 9', dfield:'2023-01-12', tfield:'10:58:29', dtfield: '2023-12-11T04:08:29Z',tsfield: '2022-09-11T04:08:29.129Z', nfield: 400,afield: '13892100.723456',category: 'rc2', cmitems:[ {csm_sfield:'csm field 01', csm_nfield:'100'}, {csm_sfield:'csm field 02', csm_nfield:'200'} ]}; await INSERT.into(Roots).entries(one); // create multiple entry multi = [ {name:'root 20', descr:'descr 20', dfield:'2023-01-10', tfield:'18:08:29', dtfield: '2023-02-11T04:08:29Z',tsfield: '2023-02-11T04:08:29.129Z', nfield: 400,afield: '23891100.123456',category: 'rc3'}, {name:'root 21', descr:'descr 21', dfield:'2023-04-12', tfield:'16:08:29', dtfield: '2023-02-11T04:08:29Z',tsfield: '2023-01-11T04:08:29.129Z', nfield: 300,afield: '23891100.123456',category: 'rc3'}]; await INSERT.into(Roots).entries(multi);
// using columns and values await INSERT.into(Roots) .columns('name','descr','dfield','tfield','dtfield', 'tsfield','nfield','afield','category') .values('root 10','descr 10','2022-04-12','04:08:29','2022-02-11T04:08:29Z', '2022-02-11T04:08:29.129Z',200,23890.456,'rc3'); // using columns and rows await INSERT.into(Roots) .columns('name','descr','dfield','tfield','dtfield', 'tsfield','nfield','afield','category') .rows(['root 11','descr 11','2023-04-12','04:08:29','2022-02-11T04:08:29Z', '2022-02-11T04:08:29.129Z',200,23890.456,'rc3'], ['root 12','descr 12','2023-04-12','04:08:29','2022-02-11T04:08:29Z', '2022-02-11T04:08:29.129Z',200,23890.456,'rc2'], ['root 13','descr 13','2023-01-12','04:08:29','2022-02-11T04:08:29Z', '2022-02-11T04:08:29.129Z',300,23890.456,'rc1']);
Create or Modify Data: Upsert Query Examples
Upsert queries are used to create or modify entries in tables. To modify, all key fields need to be part of the data. Let's look at few examples below:
// create entry await UPSERT.into(Roots) .entries({name:'root 6', descr:'descr 6', dfield:'2022-04-12', tfield:'04:08:29', dtfield: '2022-02-11T04:08:29Z', tsfield: '2022-02-11T04:08:29.129Z', nfield: 200, afield: '23891100.123456', category: 'rc3'}); // create entry with association await UPSERT.into(Roots) .entries({name:'root 7', descr:'descr 7', dfield:'2023-04-12', tfield:'14:08:29', dtfield:'2023-02-11T04:08:29Z', tsfield:'2022-02-11T04:08:29.129Z', nfield:200, afield:'23891100.123456', category:'rc3', asitem_ID:'585dee53-69e5-47d9-b90d-945253d4af0d'}); // modify entry await UPSERT.into(Roots) .entries({name:'root 11',ID:'7891fd0d-5924-471a-89c4-519f7df071b8'}); await UPSERT.into(Roots) .entries({name:'root 66', descr:'descr 6', dfield:'2022-04-12', tfield:'04:08:29', dtfield:'2022-02-11T04:08:29Z', tsfield: '2022-02-11T04:08:29.129Z', nfield:200, afield:'23891100.123456',category:'rc2', ID:'7891fd0d-5924-471a-89c4-519f7df071b87'}) // modify root entry with creation of composed entry: Not working await UPSERT.into(Roots) .entries({ID: '7891fd0d-5924-471a-89c4-519f7df071b8', descr: 'descr 1111', amitems: [{asm_sfield: 'asm string 1111',asm_nfield: 1001, root_ID: '7891fd0d-5924-471a-89c4-519f7df071b8'}] })
Note:
Upsert for CAP Node.js does not (unlike CAP Java) support deep payloads.
Update Data: Update Query Examples
Update queries are used to modify data in a table. Here are a few examples:
// by single key await UPDATE.entity(Roots, 'f11aacb1-ab35-40da-918b-589cdd387ad1') .set({name:'root 333'}); await UPDATE.entity(Roots, 'f11aacb1-ab35-40da-918b-589cdd387ad1') .set({name:'root 332',dfield: '2021-12-20'}); await UPDATE.entity(Roots, 'f11aacb1-ab35-40da-918b-589cdd387ad1') .set({nfield: {'-=': 89}}); await UPDATE.entity(Roots, 'f11aacb1-ab35-40da-918b-589cdd387ad1') .set(`nfield = nfield * 2`); // by multiple key await UPDATE.entity(Roots2,{first:101,second:201}).set({name:'r2 name1 extra'}); // by cqn expression cqnExpression = {xpr: [{ref:['descr']}, '||', {val:'Additional Description'}]}; await UPDATE.entity(Roots2,{first:102,second:202}) .with({descr: cqnExpression}) // by functions with template string await UPDATE.entity(Roots, 'f11aacb1-ab35-40da-918b-589cdd387ad1') .set(`descr = concat('descr',' add')`); await UPDATE.entity(Roots2, {first:102,second:202}) .set(`descr = concat('descr',' add'), name = 'root 33'`); // by where clause await UPDATE.entity(Roots) .set({nfield: {'-=': 89}}) .where({nfield:{'>':500}}); await UPDATE.entity(Roots2) .set({name: 'r2 name2 new'}) .where(`descr like '%Add%' and first = 102`);
UPDATE.entity(Roots, 'f11aacb1-ab35-40da-918b-589cdd387ad1').set(`nfield = nfield * 2`)
The UPDATE.entity method is used to update the Roots entity with the specified ID 'f11aacb1-ab35-40da-918b-589cdd387ad1'. The .set() method is used to specify the changes to be made in the update operation. In this case, it sets the nfield column to be multiplied by 2. You can look at different examples as provided above.Note:
Delete queries remove data from tables. Take a look at the examples below:Delete Data: Delete Query Examples
// using one or more key await DELETE.from(Roots,'445b1357-90b2-4a2c-a60d-7f7e40d46f12'); await DELETE.from(Roots2,{first:101, second:201}); // using where condition await DELETE.from(Roots).where({category:'rc1'}); await DELETE.from(Roots2).where({first:101}) await DELETE.from(AssocMItems).where({asm_nfield:{'>=':1014}});
In this blog post, We explored how CQL can be used to execute database operations such as retrieving data, inserting data, updating data and deleting data.Conclusion
More information about cloud application programming model (CAP) can be found here. You can follow my profile to get notification of the next blog post on CAP. Please feel free to provide any feedback you have in the comments section below and ask your questions about the topic in sap community using this link. |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
27 | |
25 | |
19 | |
14 | |
13 | |
11 | |
10 | |
9 | |
7 | |
6 |