MENU
Built In Functions and Operators
Most of what a PHP page does to a value (rounding a price, trimming a name, grouping orders by month) can happen in the query instead, before the rows cross the network. The examples use SELECT to show each result, but every expression works equally in WHERE, ORDER BY, GROUP BY, UPDATE and generated columns (Section 3.4.8). All output comes from MySQL 9.7.2 against the shop database. Several functions from older tutorials are gone, and each removal is named where it matters.3.7.1 Arithmetic, Bitwise, and Assignment Operators
The arithmetic operators are +, -, *, /, DIV (integer division) and % or MOD. On exact operands / returns a DECIMAL with four more decimal places than the dividend. Division by zero gives NULL and warning 1365 in a SELECT, but an error in a strict-mode INSERT or UPDATE. The bitwise operators &, |, ^, ~, << and >> work on 64-bit unsigned integers.
Figure 3.6: MySQL operator precedence, from tightest (top left) to
loosest (bottom right)Assignment uses := or, inside SET only, =. Anywhere else =
compares.SET @vat := 0.06;
SELECT price * stock AS value, stock DIV 12 AS boxes, stock % 12 AS loose,
ROUND(price * (1 + @vat), 2) AS gross, 2 + 3 * 4 AS prec, 7 / 2 AS q, 1 / 0 AS z,
6 & 3 AS b_and, 6 | 3 AS b_or, 6 ^ 3 AS b_xor, 1 << 4 AS shl, -7 DIV 2 AS idiv
FROM products WHERE sku = 'AC-STK-01';
SELECT stock - 10 FROM products WHERE sku = 'BK-SQL-02';
+--------+-------+-------+-------+------+--------+------+-------+------+-------+-----+------+
| value | boxes | loose | gross | prec | q | z | b_and | b_or | b_xor | shl | idiv |
+--------+-------+-------+-------+------+--------+------+-------+------+-------+-----+------+
| 748.50 | 12 | 6 | 5.29 | 14 | 3.5000 | NULL | 2 | 7 | 5 | 16 | -3 |
+--------+-------+-------+-------+------+--------+------+-------+------+-------+-----+------+
Warning (Code 1365): Division by 0
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(`shop`.`products`.`stock` - 10)'
3.7.2 Comparison Operators, IN, and BETWEEN
Comparisons (=, <> or !=, <, <=, >, >=) return 1, 0 or NULL; <=> is the NULL-safe equal, and IS [NOT] TRUE, FALSE or UNKNOWN tests a truth value. x BETWEEN a AND b means x >= a AND x <= b, IN tests a list, and row constructors compare left to right, the way a composite index sorts. The logical operators are AND, OR, NOT and XOR.SELECT id, rating, body IS NULL AS no_text, rating <=> 4 AS is4,
rating BETWEEN 3 AND 4 AS mid, product_id IN (1, 6) AS in_list,
(product_id, rating) > (2, 3) AS row_gt, (rating > 3) XOR (body IS NULL) AS one_of,
customer_id NOT IN (3, NULL) AS not_in_null
FROM reviews WHERE customer_id IN (1, 2);
+----+--------+---------+-----+-----+---------+--------+--------+-------------+
| id | rating | no_text | is4 | mid | in_list | row_gt | one_of | not_in_null |
+----+--------+---------+-----+-----+---------+--------+--------+-------------+
| 1 | 5 | 0 | 0 | 0 | 1 | 0 | 1 | NULL |
| 5 | 4 | 1 | 1 | 1 | 1 | 1 | 0 | NULL |
| 2 | 4 | 0 | 1 | 1 | 0 | 1 | 1 | NULL |
| 3 | 3 | 0 | 0 | 1 | 0 | 1 | 0 | NULL |
+----+--------+---------+-----+-----+---------+--------+--------+-------------+
3.7.3 LIKE and Wildcards
LIKE matches a whole string against a pattern where % is any run of characters (even none) and _ exactly one. Backslash escapes a literal % or _, and ESCAPE picks another escape character. Case sensitivity follows the collation.SELECT sku, title FROM products
WHERE title LIKE '%sql%' OR sku LIKE 'AC-___-01';
SELECT 'Mug' LIKE 'mug' AS ci, 'Mug' LIKE 'mug' COLLATE utf8mb4_0900_as_cs AS cs,
'50% off' LIKE '50\% %' AS esc, '50% off' LIKE '50!%%' ESCAPE '!' AS esc2,
'abc ' LIKE 'abc' AS trail, 'BK-PHP-01' NOT LIKE 'BK-%' AS nl;
+-----------+------------------------+
| sku | title |
+-----------+------------------------+
| BK-SQL-01 | SQL Queries That Scale |
| AC-MUG-01 | Semicolon Coffee Mug |
| AC-STK-01 | Stack Sticker Pack |
+-----------+------------------------+
+----+------+-----+------+-------+----+
| ci | cs | esc | esc2 | trail | nl |
+----+------+-----+------+-------+----+
| 1 | 0 | 1 | 1 | 0 | 0 |
+----+------+-----+------+-------+----+
3.7.4 Regular Expressions with REGEXP_LIKE and Its Companions
Since MySQL 8.0 regular expressions run on the Unicode-aware ICU library. REGEXP and RLIKE are synonyms for REGEXP_LIKE(), and three companions return where (REGEXP_INSTR), what (REGEXP_SUBSTR) or a rewritten string (REGEXP_REPLACE). A regex matches anywhere unless anchored with ^ and $, and a match type of 'c' (case-sensitive), 'i', 'm' (multiline) or 'n' overrides the collation.SELECT sku, REGEXP_SUBSTR(sku, '[A-Z]{2,3}', 1, 2) AS topic,
REGEXP_INSTR(title, ' ') AS first_space, REGEXP_REPLACE(title, '\\s+', '_') AS snake
FROM products WHERE sku REGEXP '^BK-(SQL|LNX)-';
SELECT REGEXP_LIKE('CamelCase', 'camelcase') AS ci,
REGEXP_LIKE('CamelCase', 'camelcase', 'c') AS cs,
REGEXP_SUBSTR('Order 9 of 2026', '\\d+', 1, 2) AS second_num,
REGEXP_LIKE('1+2', '1\+2') AS one_slash, REGEXP_LIKE('1+2', '1\\+2') AS two_slash;
CREATE TABLE sku_rule (sku VARCHAR(20)
CHECK (REGEXP_LIKE(sku, '^[A-Z]{2}-[A-Z]{2,3}-[0-9]{2}$', 'c')));
INSERT INTO sku_rule VALUES ('BK-PHP-01'), ('bk-php-1');
+-----------+-------+-------------+---------------------------+
| sku | topic | first_space | snake |
+-----------+-------+-------------+---------------------------+
| BK-SQL-01 | SQL | 4 | SQL_Queries_That_Scale |
| BK-SQL-02 | SQL | 9 | Indexing_Deep_Dive |
| BK-LNX-01 | LNX | 4 | The_Linux_Server_Handbook |
+-----------+-------+-------------+---------------------------+
+----+----+------------+-----------+-----------+
| ci | cs | second_num | one_slash | two_slash |
+----+----+------------+-----------+-----------+
| 1 | 0 | 2026 | 0 | 1 |
+----+----+------------+-----------+-----------+
ERROR 3819 (HY000): Check constraint 'sku_rule_chk_1' is violated.
3.7.5 String Functions
String positions start at 1. LENGTH() counts bytes and CHAR_LENGTH() characters, which differ once text leaves ASCII. The workhorses are CONCAT(), CONCAT_WS() (with a separator, skipping NULLs), LOWER(), UPPER(), TRIM(), LPAD(), LEFT(), SUBSTRING(), SUBSTRING_INDEX(), LOCATE(), REPLACE() and FIELD(). The first query builds a URL slug from each title.SELECT title,
TRIM(BOTH '-' FROM REGEXP_REPLACE(LOWER(title), '[^a-z0-9]+', '-')) AS slug
FROM products WHERE category_id = 2;
SELECT CONCAT_WS(' / ', name, NULL, country) AS label,
SUBSTRING_INDEX(email, '@', -1) AS domain, CONCAT('C-', LPAD(id, 5, '0')) AS acct,
UPPER(LEFT(name, 3)) AS code, CONCAT(name, NULL) AS cat,
LENGTH('Café') AS bytes, CHAR_LENGTH('Café') AS chars
FROM customers WHERE id = 3;
+----------------------------+----------------------------+
| title | slug |
+----------------------------+----------------------------+
| Modern PHP in Practice | modern-php-in-practice |
| Laravel From the Ground Up | laravel-from-the-ground-up |
| The Linux Server Handbook | the-linux-server-handbook |
+----------------------------+----------------------------+
+---------------+-------------+---------+------+------+-------+-------+
| label | domain | acct | code | cat | bytes | chars |
+---------------+-------------+---------+------+------+-------+-------+
| Chen Wei / MY | example.com | C-00003 | CHE | NULL | 5 | 4 |
+---------------+-------------+---------+------+------+-------+-------+
3.7.6 Numeric and Rounding Functions
ROUND(x, d) rounds to d places (a negative d rounds left of the point), TRUNCATE() cuts, FLOOR() and CEIL() go down and up, and ABS(), MOD(), POW(), SQRT() and RAND() do what their names say. FORMAT(x, d, locale) returns a display string. CAST(expr AS type) or CONVERT(expr, type) converts to SIGNED, UNSIGNED, DECIMAL(m,d), DOUBLE, FLOAT, CHAR, BINARY, DATE, DATETIME, TIME, YEAR or JSON.SELECT FORMAT(SUM(price * stock), 2) AS inventory,
FORMAT(SUM(price * stock), 2, 'de_DE') AS de, ROUND(SUM(price * stock), -2) AS hundreds,
ROUND(2.5) AS r_dec, ROUND(2.5E0) AS r_dbl, TRUNCATE(4.99, 1) AS t, FLOOR(-4.2) AS fl
FROM products;
SELECT 0.1 + 0.2 = 0.3 AS dec_eq, 0.1E0 + 0.2E0 AS dbl_sum,
CAST(stock AS SIGNED) - 10 AS sold, CAST('39.90' AS DECIMAL(6,2)) * 3 AS dec3,
CAST(2.99 AS UNSIGNED) AS u, CAST(price AS CHAR) AS txt,
CONVERT('2026-09-23', DATE) + INTERVAL 1 DAY AS tmrw
FROM products WHERE sku = 'BK-SQL-02';
+-----------+----------+----------+-------+-------+-----+----+
| inventory | de | hundreds | r_dec | r_dbl | t | fl |
+-----------+----------+----------+-------+-------+-----+----+
| 4,528.00 | 4.528,00 | 4500 | 3 | 2 | 4.9 | -5 |
+-----------+----------+----------+-------+-------+-----+----+
+--------+---------------------+------+--------+---+-------+------------+
| dec_eq | dbl_sum | sold | dec3 | u | txt | tmrw |
+--------+---------------------+------+--------+---+-------+------------+
| 1 | 0.30000000000000004 | -10 | 119.70 | 3 | 29.00 | 2026-09-24 |
+--------+---------------------+------+--------+---+-------+------------+
3.7.7 Date and Time Functions and Format Specifiers
NOW() is fixed when the statement starts, while SYSDATE() reads the clock at each call. DATE_ADD(), DATE_SUB() and + INTERVAL n unit do arithmetic, DATEDIFF() counts days and TIMESTAMPDIFF(unit, a, b) any unit. DATE_FORMAT() and STR_TO_DATE() share specifiers: %Y year, %m and %b month, %d and %D day ("11th"), %H and %l hour, %i minutes, %p AM/PM, %a weekday.SELECT DATE_FORMAT(ordered_at, '%Y-%m') AS month, COUNT(*) AS orders,
GROUP_CONCAT(DISTINCT YEARWEEK(ordered_at, 3)) AS iso_weeks,
MIN(DATE(ordered_at) - INTERVAL WEEKDAY(ordered_at) DAY) AS first_monday,
DATE_FORMAT(MIN(ordered_at), '%a %D %b, %l:%i %p') AS first_order
FROM orders WHERE ordered_at < '2026-06-01' GROUP BY month ORDER BY month;
+---------+--------+---------------+--------------+-----------------------+
| month | orders | iso_weeks | first_monday | first_order |
+---------+--------+---------------+--------------+-----------------------+
| 2026-03 | 2 | 202610,202611 | 2026-03-02 | Mon 2nd Mar, 10:15 AM |
| 2026-04 | 1 | 202615 | 2026-04-06 | Sat 11th Apr, 7:30 AM |
| 2026-05 | 2 | 202619,202621 | 2026-05-04 | Tue 5th May, 12:00 PM |
+---------+--------+---------------+--------------+-----------------------+
3.7.8 COALESCE, IFNULL, NULLIF, GREATEST, and LEAST
These flow-control functions sit beside CASE and IF() (Section 3.5.12). COALESCE() returns its first non-NULL argument and IFNULL(a, b) is the two-argument form. NULLIF(a, b) is NULL when a = b, which turns a zero divisor into a safe NULL. GREATEST() and LEAST() clamp, and INTERVAL(n, b1, b2, ...) counts the sorted bounds n has reached, a quick banding tool.SELECT p.sku, IFNULL(ROUND(AVG(r.rating), 1), 'none') AS avg,
COALESCE(LEFT(MAX(r.body), 14), p.attributes->>'$.format', 'n/a') AS blurb,
ROUND(p.price / NULLIF(p.stock, 0), 2) AS per_unit,
GREATEST(CAST(p.stock AS SIGNED) - 20, 0) AS surplus, LEAST(p.price, 30) AS capped,
INTERVAL(p.price, 10, 30, 45) AS band, GREATEST(p.price, NULL) AS g_null,
GREATEST(10, '9') AS g_mixed
FROM products AS p LEFT JOIN reviews AS r ON r.product_id = p.id
WHERE p.id IN (3, 6, 8) GROUP BY p.id;
+-----------+------+----------------+----------+---------+--------+------+--------+---------+
| sku | avg | blurb | per_unit | surplus | capped | band | g_null | g_mixed |
+-----------+------+----------------+----------+---------+--------+------+--------+---------+
| BK-SQL-02 | 3.0 | Good on B-tree | NULL | 0 | 29.00 | 1 | NULL | 9 |
| BK-LNX-01 | 4.0 | paperback | 4.67 | 0 | 30.00 | 2 | NULL | 9 |
| AC-STK-01 | none | n/a | 0.03 | 130 | 4.99 | 0 | NULL | 9 |
+-----------+------+----------------+----------+---------+--------+------+--------+---------+
3.7.9 Hashing, Encryption, Compression, UUIDs, and Server Information
The list has shrunk. PASSWORD() went in MySQL 8.0.11, and ENCRYPT(), ENCODE(), DECODE(), DES_ENCRYPT() and DES_DECRYPT() in 8.0.3. MD5() and SHA1() (with its alias SHA()) were deprecated in 9.4.0 and moved out of the server in 9.6.0, so 9.7 LTS has only SHA2() built in; an administrator can restore the old two with INSTALL COMPONENT 'file://component_classic_hashing'. AES_ENCRYPT() defaults to aes-128-ecb; set block_encryption_mode to CBC and pass a random IV.SELECT MD5('ana@example.com');
SET block_encryption_mode = 'aes-256-cbc';
SET @key = UNHEX(SHA2('passphrase kept outside the database', 256)), @iv = RANDOM_BYTES(16);
SET @ct = AES_ENCRYPT('4111 1111 1111 1111', @key, @iv);
SELECT LEFT(SHA2('ana@example.com', 256), 16) AS sha256_start, LENGTH(@ct) AS ct_bytes,
CAST(AES_DECRYPT(@ct, @key, @iv) AS CHAR) AS plain,
AES_DECRYPT(@ct, UNHEX(SHA2('wrong', 256)), @iv) AS wrong_key,
LENGTH(COMPRESS(REPEAT('shop ', 1000))) AS packed_5000;
INSERT INTO customers (email, name, country) VALUES ('ivy@example.com', 'Ivy Tan', 'SG');
SELECT LAST_INSERT_ID() AS new_id, VERSION() AS version, DATABASE() AS db,
CURRENT_USER() AS me, UUID() AS uuid;
ERROR 1305 (42000): FUNCTION shop.MD5 does not exist
+------------------+----------+---------------------+-----------+-------------+
| sha256_start | ct_bytes | plain | wrong_key | packed_5000 |
+------------------+----------+---------------------+-----------+-------------+
| 8e43ca37701228e7 | 32 | 4111 1111 1111 1111 | NULL | 42 |
+------------------+----------+---------------------+-----------+-------------+
+--------+---------+---------+----------------+--------------------------------------+
| new_id | version | db | me | uuid |
+--------+---------+---------+----------------+--------------------------------------+
| 9 | 9.7.2 | shop | root@localhost | 3181c3fc-b724-11f1-98d6-00155d5f9c3f |
+--------+---------+---------+----------------+--------------------------------------+