Profiles / Programming
MB

MQL beginner

Variables, operators, if, loops and your first try/exception: learn MQL step by step.

10 recipes on this page · open in the interactive view

Recipes
  1. Shopping cart total: variables and arithmetic
  2. Grade a class: for loop, list and case
  3. Savings plan: while, repeat, break and continue
  4. Check a sign-up form with boolean operators
  5. Your first try and exception: a bank withdrawal
  6. Split a bill: functions inside functions
  7. Loop over the lines of a JSON order
  8. Your first script with typed parameters
  9. Check typed quantities: try inside a loop
  10. Your first web service, called with execute
executed on MentDB

Shopping cart total: variables and arithmetic

Your first MQL script: a cart holds 3 items at 19.90 with a 10% discount and 4.99 of shipping. The script stores the values in variables, computes the total with the four operators, shows why money needs the exact bd operators, decides with if whether shipping is free, and uses the bi operators for integers of any size.

Pipeline
Script
admin
#1. Store the values in variables;
-> "[price]" 19.90;
-> "[qty]" 3;
-> "[shipping]" 4.99;
#2. The four operators take two numbers each;
-> "[subtotal]" (* [price] [qty]);
-> "[discount]" (/ [subtotal] 10);
-> "[total]" (+ (- [subtotal] [discount]) [shipping]);
#3. Same thing with exact decimal operators;
-> "[bd_subtotal]" (bd* [price] [qty]);
-> "[bd_total]" (bd+ (bd- [bd_subtotal] (bd/ [bd_subtotal] 10)) [shipping]);
#4. Free shipping from 50;
if (>= [bd_subtotal] 50) {-> "[note]" "free shipping: remove the 4.99";} {-> "[note]" "add items to reach 50";};
#5. Integers of any size with bi operators;
-> "[big]" (bi* 987654321987 123456789123);
-> "[back]" (bi/ (bi- (bi+ [big] 7) 7) 123456789123);
concat "Subtotal with *: " [subtotal] [_n_] "Subtotal with bd*: " [bd_subtotal] [_n_] "Total with + - /: " [total] [_n_] "Total with bd+ bd- bd/: " [bd_total] [_n_] "Note: " [note] [_n_] "bi*: " [big] ", then bi+ bi- bi/ give back " [back];
Result
mentdb
Subtotal with *: 59.699999999999996
Subtotal with bd*: 59.70
Total with + - /: 58.72
Total with bd+ bd- bd/: 58.72
Note: free shipping: remove the 4.99
bi*: 121932631355968601347401, then bi+ bi- bi/ give back 987654321987
Go further
  • Very large integers: bi* and bi+ never overflow, try bi* 123456789123 987654321987
  • Round a double for display with math round
executed on MentDB

Grade a class: for loop, list and case

A teacher has five scores out of 20 in a comma-separated list. The script loops over the list with for, reads each score with atom get, gives a mention with case and computes the class average.

Pipeline
Script
admin
#1. A list of scores separated by commas;
-> "[scores]" "15,8,12,19,10";
-> "[n]" (atom size [scores] ",");
-> "[sum]" 0;
-> "[report]" "";
#2. Loop from 1 to n: init, condition, increment, action;
for (-> "[i]" 1) (<= [i] [n]) (++ "[i]") {
	-> "[s]" (atom get [scores] [i] ",");
	-> "[sum]" (+ [sum] [s]);
	#3. case returns the action of the first true condition;
	-> "[mention]" (case (>= [s] 16) {"Very good"} (>= [s] 12) {"Good"} (>= [s] 10) {"Pass"} {"Fail"});
	-> "[report]" (concat [report] "Student " [i] ": " [s] "/20 " [mention] [_n_]);
};
concat [report] "Class average: " (/ [sum] [n]) "/20";
Result
mentdb
Student 1: 15/20 Good
Student 2: 8/20 Fail
Student 3: 12/20 Good
Student 4: 19/20 Very good
Student 5: 10/20 Pass
Class average: 12.8/20
Go further
executed on MentDB

Savings plan: while, repeat, break and continue

You save 120 a month on an account paying 1% a month, but skip August, and you want 1200. The script loops with while until the goal, skips a month with continue, stops at 24 months with break, then shows that repeat runs its block at least once even when the condition is false.

Pipeline
Script
admin
#1. Start empty, goal 1200;
-> "[balance]" 0;
-> "[month]" 0;
-> "[skipped]" 0;
while (< [balance] 1200) {
	++ "[month]";
	#2. Safety guard: never loop forever;
	if (> [month] 24) {break;};
	-> "[balance]" (math round (* [balance] 1.01) 2);
	#3. Month 8 is August: no deposit;
	if (== [month] 8) {++ "[skipped]"; continue;};
	-> "[balance]" (+ [balance] 120);
};
#4. while checks first, repeat checks after the block;
-> "[w]" 0;
while (< [w] 0) {++ "[w]";};
-> "[r]" 0;
repeat (< [r] 0) {++ "[r]";};
concat "Goal reached in month " [month] " with " [balance] ", " [skipped] " month skipped." [_n_] "while with a false condition ran " [w] " time, repeat ran " [r] " time.";
Result
mentdb
Goal reached in month 11 with 1264.38, 1 month skipped.
while with a false condition ran 0 time, repeat ran 1 time.
Go further
  • Count down with -- and a for loop
  • Log each month with log trace and read it back with log show 12
executed on MentDB

Check a sign-up form with boolean operators

A sign-up form sends an e-mail, a nickname, an age, a country and an optional coupon. The script checks each field with the null and empty tests, compares strings and numbers, and combines the checks with and, or, not and xor.

Pipeline
Script
admin
#1. The values sent by the form;
-> "[email]" "ana@example.com";
-> "[nickname]" "";
-> "[age]" "17";
-> "[country]" "FR";
-> "[coupon]" null;
#2. Null and empty tests return 1 or 0;
-> "[has_email]" (is not empty [email]);
-> "[no_nick]" (is empty [nickname]);
-> "[no_coupon]" (is null [coupon]);
-> "[coupon_ok]" (is not null [coupon]);
-> "[nick_missing]" (is null or empty [nickname]);
#3. Compare strings with equal, numbers with == != < >;
-> "[adult]" (>= [age] 18);
-> "[france]" (equal [country] "FR");
-> "[not_us]" (not equal [country] "US");
-> "[age_17]" (and (== [age] 17.0) (!= [age] 18));
#4. Combine the rules;
-> "[can_buy]" (and [has_email] (or [adult] (not [france])));
-> "[one_of]" (xor [no_coupon] [coupon_ok]);
concat "email given: " [has_email] ", nickname null or empty: " [nick_missing] ", nickname empty: " [no_nick] [_n_] "adult: " [adult] ", age is 17: " [age_17] ", from France: " [france] ", not US: " [not_us] [_n_] "coupon: none " [no_coupon] ", given " [coupon_ok] ", exactly one is true: " [one_of] [_n_] "can buy alcohol online: " [can_buy] ", minor in France: " (and (< [age] 18) [france]);
Result
mentdb
email given: 1, nickname null or empty: 1, nickname empty: 1
adult: 0, age is 17: 1, from France: 1, not US: 1
coupon: none 1, given 0, exactly one is true: 1
can buy alcohol online: 0, minor in France: 1
Go further
executed on MentDB

Your first try and exception: a bank withdrawal

A customer with 120 on the account asks for 200. The script raises an exception with a code when the funds are too low, catches it with try, reads the message from the error variable and shows that the balance did not change.

Pipeline
Script
admin
#1. Account and request;
-> "[balance]" 120;
-> "[amount]" 200;
try {
	#2. Raise an error with a code and a message;
	if (> [amount] [balance]) {
		exception (1001) (concat "Insufficient funds: balance " [balance] ", asked " [amount]);
	};
	-> "[balance]" (- [balance] [amount]);
	-> "[status]" "Withdrawal accepted";
} {
	#3. The error text ends with code: message;
	-> "[status]" (concat "Refused, error " (string regexp_extract [err] "([0-9]+): [^\n]*$" 1) " - " (string regexp_extract [err] "[0-9]+: ([^\n]*)$" 1));
} "[err]";
concat [status] [_n_] "Balance still " [balance];
Result
mentdb
Refused, error 1001 - Insufficient funds: balance 120, asked 200
Balance still 120
Go further
  • Change the amount to 50: no error, the balance becomes 70
  • Without try, the same exception stops the script and the editor shows the error
executed on MentDB

Split a bill: functions inside functions

Four friends share a restaurant bill of 187.45. The script first works step by step with one function per line, then nests the calls in one line, and makes the last friend pay the rounding gap so the total stays exact.

Pipeline
Script
admin
#1. A restaurant bill shared by 4 friends;
-> "[bill]" 187.45;
-> "[guests]" 4;
#2. Step by step: one function per line, each result in a variable;
-> "[share]" (/ [bill] [guests]);
-> "[euros]" (math trunc [share]);
-> "[cents]" (math frac_part [share]);
#3. The same work in one line: the innermost parenthesis runs first;
-> "[each]" (math round (/ [bill] [guests]) 2);
#4. The last friend pays what is left, so the total is exact;
-> "[last]" (bd- [bill] (bd* [each] (- [guests] 1)));
-> "[gap]" (bd- [last] [each]);
#5. A function result can be the parameter of another function;
concat "Share: " [share] " = " [euros] " euros + " [cents] [_n_] (- [guests] 1) " friends pay " [each] ", the last one pays " [last] [_n_] "Gap sign: " (math sign [gap]) ", gap in cents: " (string lpad (math round (* [gap] 100) 0) "0" 2) [_n_] (string upper (concat "total checked: " (bd+ (bd* [each] (- [guests] 1)) [last])));
Result
mentdb
Share: 46.8625 = 46 euros + 0.8625
3 friends pay 46.86, the last one pays 46.87
Gap sign: 1, gap in cents: 01
TOTAL CHECKED: 187.45
Go further
  • Read a nested call from the inside out: (math round (/ [bill] [guests]) 2) divides first, then rounds.
  • Replace the last concat by string lpad and string rpad to print aligned columns of a receipt.
executed on MentDB

Loop over the lines of a JSON order

An order arrives as JSON with an array of lines. The script loads it in memory, loops over the array with a for and json select, prints a small receipt, visits the fields of the first line with json parse_obj, writes the total back into the order and frees the memory.

Pipeline
Script
admin
#1. Load an order in memory: a JSON object with an array of lines;
json load "rx_p4l_order" "{\"id\": \"A-102\", \"customer\": \"Ana\", \"lines\": [{\"item\": \"Pen\", \"qty\": 3, \"price\": 1.5}, {\"item\": \"Notebook\", \"qty\": 2, \"price\": 4.9}, {\"item\": \"Bag\", \"qty\": 1, \"price\": 24}]}";
-> "[n]" (json count "rx_p4l_order" "/lines");
#2. A for loop on the array: positions start at 0;
-> "[total]" 0;
-> "[prices]" "";
-> "[receipt]" "";
for (-> "[i]" 0) (< [i] [n]) (++ "[i]") {
	-> "[path]" (concat "/lines[" [i] "]");
	-> "[item]" (json select "rx_p4l_order" (concat [path] "/item"));
	-> "[amount]" (* (json select "rx_p4l_order" (concat [path] "/qty")) (json select "rx_p4l_order" (concat [path] "/price")));
	-> "[total]" (+ [total] [amount]);
	-> "[prices]" (concat [prices] (if (> [i] 0) {", "} {""}) (json select "rx_p4l_order" (concat [path] "/price")));
	-> "[receipt]" (concat [receipt] (string rpad [item] "." 12) " " [amount] [_n_]);
};
#3. json parse_obj visits each field of an object: key and value;
-> "[first]" "";
json parse_obj "rx_p4l_order" "/lines[0]" "[k]" "[v]" {
	-> "[first]" (concat [first] [k] "=" [v] " ");
};
#4. Write the total into the order, read it back, free the memory;
json iobject "rx_p4l_order" "/" "total" [total] NUM;
-> "[saved]" (json select "rx_p4l_order" "/total");
-> "[fields]" (json fields "rx_p4l_order" "/");
json unload "rx_p4l_order";
concat [receipt] "Total: " [saved] " for " [n] " lines" [_n_] "Average unit price: " (math round (math avg (concat "[" [prices] "]")) 2) [_n_] "First line: " [first] [_n_] "Order fields: " (string replace (string replace [fields] [_n_] "") " " "");
Result
mentdb
Pen......... 4.5
Notebook.... 9.8
Bag......... 24
Total: 38.3 for 3 lines
Average unit price: 10.13
First line: item=Pen price=1.5 qty=3
Order fields: ["total","id","lines","customer"]
Go further
  • json parse_array "rx_key" "/lines" "[line]" {...} gives each element of an array in [line], with no index to manage.
  • Always end with json unload on your key: the document stays in the server memory otherwise.
executed on MentDB

Your first script with typed parameters

Create a small greeting script that takes a first name and the hour of the day, each checked by a type rule. Execute it twice, ask the server for its parameters, then delete it.

Pipeline
Script
admin
#1. Create a script: method post, name, activity log off, 1 attempt in the stack;
script create post "tuto.p4l.hello.greet" false 1
	(param
		(var "[name]" {type is_varchar [name] 30} "First name, 30 chars max" is_null:false is_empty:false "ana")
		(var "[hour]" {type is_integer [hour] 2} "Hour of the day, 0 to 23" is_null:false is_empty:false "9")
	;)
	"Say hello at the right time of the day"
{
	-> "[word]" (case (< [hour] 12) {"Good morning"} (< [hour] 18) {"Good afternoon"} {"Good evening"});
	concat [word] ", " (string capitalize_words [name]) "!";
} "A greeting sentence";
#2. Run it: the method is now the end of its name;
-> "[r1]" (execute "tuto.p4l.hello.greet.post" "[name]" "ana" "[hour]" 9);
#The parameters are given in the order of the definition;
-> "[r2]" (execute "tuto.p4l.hello.greet.post" "[name]" "léo martin" "[hour]" 20);
#3. The server knows the script and its parameters;
-> "[exists]" (script exist "tuto.p4l.hello.greet.post");
json load "rx_p4l_params" (script show params "tuto.p4l.hello.greet.post");
-> "[p1]" (concat (json select "rx_p4l_params" "/[0]/name") ": " (json select "rx_p4l_params" "/[0]/desc") ", checked by " (json select "rx_p4l_params" "/[0]/type"));
-> "[p2]" (concat (json select "rx_p4l_params" "/[1]/name") ": " (json select "rx_p4l_params" "/[1]/desc") ", checked by " (json select "rx_p4l_params" "/[1]/type"));
json unload "rx_p4l_params";
#4. A tutorial script is deleted at the end;
script delete "tuto.p4l.hello.greet.post";
concat [r1] [_n_] [r2] [_n_] "exists: " [exists] ", parameters: " [p1] " | " [p2] [_n_] "after delete, exists: " (script exist "tuto.p4l.hello.greet.post");
Result
mentdb
Good morning, Ana!
Good evening, Léo Martin!
exists: 1, parameters: [name]: First name, 30 chars max, checked by type is_varchar [name] 30; | [hour]: Hour of the day, 0 to 23, checked by type is_integer [hour] 2;
after delete, exists: 0
Go further
  • The values must be given in the order of the definition: [name] then [hour], or the server refuses the call.
  • Try execute "tuto.p4l.hello.greet.post" "[name]" "ana" "[hour]" "nine" before the delete: the type rule type is_integer rejects it.
executed on MentDB

Check typed quantities: try inside a loop

A stock form sends five quantities, some of them wrong. Each value is checked inside its own try, so one bad value raises a coded exception without stopping the loop; a server error (a text given to math round) is caught the same way.

Pipeline
Script
admin
#1. Quantities typed in a stock form, one per product;
-> "[inputs]" "12|abc|-4|7.5|30";
-> "[n]" (atom size [inputs] "|");
-> "[stock]" 0;
-> "[ok]" 0;
-> "[report]" "";
for (-> "[i]" 1) (<= [i] [n]) (++ "[i]") {
	-> "[q]" (atom get [inputs] [i] "|");
	#2. One try per value: an error stops this value only, the loop goes on;
	try {
		if (not (type is_number [q])) {exception (2001) (concat "'" [q] "' is not a number");};
		if (< (math sign [q]) 1) {exception (2002) (concat [q] " must be positive");};
		if (not (type is_integer [q] 4)) {exception (2003) (concat [q] " is not a whole number");};
		-> "[stock]" (+ [stock] [q]);
		++ "[ok]";
		-> "[report]" (concat [report] "line " [i] ": +" [q] [_n_]);
	} {
		#3. The catch block reads the code and the message at the end of the error;
		-> "[code]" (string regexp_extract [err] "([0-9]+): [^\n]*$" 1);
		-> "[msg]" (string regexp_extract [err] "[0-9]+: ([^\n]*)$" 1);
		-> "[report]" (concat [report] "line " [i] ": error " [code] ", " [msg] [_n_]);
	} "[err]";
};
#4. An error raised by the server itself is caught the same way;
try {
	-> "[x]" (math round "twelve" 2);
} {
	-> "[server]" (string trim (string regexp_extract [err] "\[\[\[ (.*) \]\]\]" 1));
} "[err]";
concat [report] [ok] "/" [n] " lines accepted, stock +" [stock] [_n_] "Server error caught: " [server];
Result
mentdb
line 1: +12
line 2: error 2001, 'abc' is not a number
line 3: error 2002, -4 must be positive
line 4: error 2003, 7.5 is not a whole number
line 5: +30
2/5 lines accepted, stock +42
Server error caught: Sorry, an error appears (For input string: "twelve").
Go further
  • Put the try around the whole loop instead: the first bad value then stops everything, compare both outputs.
  • Use the code in the catch with switch to answer 400 for an input error and 500 for a server error.
executed on MentDB

Your first web service, called with execute

Write a GET web service that returns the weather of a city as JSON, call it from MQL with execute exactly as the web server would, read one field of the answer, and print how a browser or another program calls it over HTTP.

Pipeline
Script
admin
#1. A web service is a script with a method: get to read data;
script create get "tuto.p4l.weather.city" false 1
	(param
		(var "[city]" {type is_enum [city] "paris,lyon,saint-denis"} "City code" is_null:false is_empty:false "paris")
	;)
	"Weather of a city, as a JSON object"
{
	-> "[temp]" (switch ([city]) ("paris") {12.5} ("lyon") {10} {27.8});
	json load "rx_p4l_ws" "{}";
	json iobject "rx_p4l_ws" "/" "city" (string capitalize_words [city]) STR;
	json iobject "rx_p4l_ws" "/" "celsius" [temp] NUM;
	json iobject "rx_p4l_ws" "/" "fahrenheit" (math round (+ (* [temp] 1.8) 32) 1) NUM;
	json iobject "rx_p4l_ws" "/" "date" (date sysdate) STR;
	-> "[answer]" (json doc "rx_p4l_ws");
	json unload "rx_p4l_ws";
	[answer];
} "JSON with city, celsius, fahrenheit and date";
#2. Call it from MQL with execute, like the web server does;
-> "[json]" (execute "tuto.p4l.weather.city.get" "[city]" "saint-denis");
#3. The answer is a JSON string: load it to read one field;
json load "rx_p4l_answer" [json];
-> "[f]" (json select "rx_p4l_answer" "/fahrenheit");
-> "[c]" (json select "rx_p4l_answer" "/city");
json unload "rx_p4l_answer";
#4. How a browser or another program calls the same service over HTTP;
-> "[http]" (script generate url "tuto.p4l.weather.city.get");
script delete "tuto.p4l.weather.city.get";
concat "Answer: " (string replace (string replace [json] [_n_] "") "  " "") [_n_] [c] " is at " [f] " F" [_n_] "HTTP call:" [_n_] [http];
Result
mentdb
Answer: {"date": "2026-11-01","celsius": 27.8,"city": "Saint-Denis","fahrenheit": 82.0}
Saint-Denis is at 82.0 F
HTTP call:
Method
  get
Header
  x-token = (empty or not define the first time)
  x-user = admin (required the first time)
  x-password = ***** (required the first time)
URL
  https://localhost:9999/api/tuto/p4l/weather/city?x-token=&x-user=&city=
Go further
  • The URL path is the script name: tuto.p4l.weather.city.get is served at /api/tuto/p4l/weather/city with the GET method.
  • Call it with a city outside the enum, "nice", and catch the refusal with try.

Also in Programming

MD
MQL developer
Atom lists, data types, constants, translations, session variables and error patterns.
10 recipes
BW
Backend & web service developer
Typed scripts, execute / call / include, generated calls and URLs, parameters and logs.
10 recipes
DT
Date & time developer
Calendars, business days, time zones, ISO and epoch, durations, quarters and week numbers.
10 recipes
SE
String & encoding developer
Cleaning, case styles, regex, URL/HTML/Base64 encodings, number bases and encryption.
10 recipes
DV
Data validation developer
IBAN, BIC, SIRET, Luhn, email, IP, UUID, JSON and date checks gathered in a validation report.
10 recipes
JD
JSON & document store developer
In-memory JSON documents, persistent nodes and sequences: build, walk, sort, clean up.
10 recipes
SD
Script developer
Full script lifecycle in the MQL editor: typed parameters, read back, generate, patch, deploy, copy, grant, delete.
10 recipes
EM
Execution modes
execute, call, include, sub_include, parallel, stack: which environment, which checks, which logs.
11 recipes
WS
Web service developer
A small REST resource with get, post, put and delete scripts: validation, JSON answers, codes, grants, URLs and API docs.
10 recipes
AD
Async developer
Asynchronous processing with the stack: push runs now or later, priorities and parallel limits, errors and replays, flows and cleanup.
10 recipes
SD
Scheduler developer
Cron jobs and delayed runs: add, resume, pause, update and remove jobs, prove they ran, and postpone stack runs with a delay condition.
10 recipes
ED
Event developer
Event-driven scripts: file watchers that push each file event to the stack, validation, archiving, errors and safe restarts.
10 recipes
CC
Configuration & connections
Parameters (private, described, locked, deployed), environment variables, sessions and connections used by your scripts.
12 recipes
VD
Versioning & deployment
Group your scripts, commit, compare, roll back, export, import and package a release.
10 recipes
DE
Debugging & error handling
Coded errors with try/exception, logs with correlation ids, timing, metrics and defensive checks.
10 recipes
SD
SQL developer in MQL
Connections, DDL and DML with sql encode, transactions, cursors, parse, and exports to JSON, CSV, Excel and PDF on a real local database. Run the recipes in order: the first one creates the tuto_db_h2 database.
10 recipes
CD
CRUD & data API developer
Generate SCRUD scripts from a table, patch and install them, write typed read and write APIs, check data quality and manage connections per environment. Uses the tuto_db_h2 database of the SQL developer recipes; the last recipe removes it.
10 recipes
WA
Web application developer
Create a web app, its host name, pages, ajax forms, menu and grants, then remove it all.
10 recipes
AI
Algorithms in MQL
Sorting, searching, recursion, data structures and number algorithms: MQL is a real programming language.
10 recipes
CF
Control flow
if, if force, case, switch, mode, for, while, repeat, break, continue, parallel and synchronized on real cases.
10 recipes
TM
The MQL language in depth
Data types and null, booleans, comments, variables and scope, eval and mql blocks, coded errors and try.
10 recipes
SU
Screen & UI developer (sajax)
Build the screens of MentDB web apps in MQL: Bootstrap grids, forms and every control, ajax buttons, alerts, refreshes and charts.
10 recipes