Skip to content
academia.sh

Lesson 06 / 18

Built-in Functions

String, number, and date functions that run per row; the distinction between character and byte length, integer division, and how date functions are the area farthest from the standard.

Contents

The previous lesson used three constructs that convert null values: COALESCE, NULLIF, and CASE. All three are scalar functions — they take a single row’s values and produce a single value, they do not change the result set’s row count, they only transform column values.

This lesson builds the rest of the same family: built-in functions that work on text, numbers, and dates. What is instructive about the family is not only what they do, but how variable their naming is. Built-in functions are the area where standard SQL and the engine dialect diverge most visibly.

The block below sets up the schema and sample data; every query in this lesson runs against the library.db file created in this block.

rm -f library.db
sqlite3 library.db <<'SQL'
CREATE TABLE branch (branch_id INTEGER PRIMARY KEY, name TEXT NOT NULL, city TEXT NOT NULL);
CREATE TABLE book (book_id INTEGER PRIMARY KEY, title TEXT NOT NULL, author TEXT NOT NULL,
                    publication_year INTEGER, branch_id INTEGER REFERENCES branch(branch_id));
CREATE TABLE member (member_id INTEGER PRIMARY KEY, first_name TEXT NOT NULL, last_name TEXT NOT NULL,
                  email TEXT, registered_at TEXT NOT NULL);
CREATE TABLE loan (loan_id INTEGER PRIMARY KEY, book_id INTEGER NOT NULL REFERENCES book(book_id),
                    member_id INTEGER NOT NULL REFERENCES member(member_id), pickup_date TEXT NOT NULL,
                    return_date TEXT);
INSERT INTO branch VALUES (1,'Central','Ankara'),(2,'Bahcelievler','Ankara'),
  (3,'Kadikoy','Istanbul'),(4,'Konak','Izmir');
INSERT INTO book VALUES (1,'Blindness','José Saramago',1995,1),(2,'The Disconnected','Oğuz Atay',1972,1),
  (3,'The Book of Sand','Jorge Luis Borges',1975,2),(4,'Yaban','Yakup Kadri',1932,2),
  (5,'Silent House','Orhan Pamuk',1983,3),(6,'Motherland Hotel','Yusuf Atılgan',NULL,3),
  (7,'Tehlikeli Oyunlar','Oğuz Atay',1973,NULL);
INSERT INTO member VALUES (1,'Alice','Kane','[email protected]','2023-02-14'),
  (2,'Ben','Ortiz','[email protected]','2023-05-30'),(3,'Clara','Diaz',NULL,'2024-01-09'),
  (4,'Derek','Voss','[email protected]','2024-03-22'),(5,'Grace','Kim',NULL,'2024-11-05'),
  (6,'Owen','Park','[email protected]','2025-01-18');
INSERT INTO loan VALUES (1,1,1,'2025-01-10','2025-01-24'),(2,2,1,'2025-02-02','2025-02-20'),
  (3,1,2,'2025-02-11',NULL),(4,3,3,'2025-03-01','2025-03-15'),(5,4,3,'2025-03-18','2025-04-02'),
  (6,1,4,'2025-04-05','2025-04-19'),(7,5,4,'2025-04-21',NULL),(8,2,5,'2025-05-02','2025-05-30'),
  (9,7,1,'2025-05-14','2025-05-28'),(10,3,5,'2025-06-03',NULL),(11,6,2,'2025-06-11','2025-06-25'),
  (12,4,4,'2025-06-20','2025-07-04');
SQL

Length: Characters or Bytes

A function that gives text’s length has to be clear about what it measures. The distinction built in the How Computers Work course appears directly here: a character can occupy more than one byte.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT LENGTH('Körlük') AS characters, LENGTH(CAST('Körlük' AS BLOB)) AS bytes;
SQL
characters  bytes
----------  -----
6           8    

The six-character word takes eight bytes: each of the letters ö and ü takes two bytes. Whether the length function counts characters or bytes depends on the engine; some engines have two separately named functions, others look at whether the type is text or binary data. In a field-limit check, this distinction turns directly into a bug — a check that measures in characters but stores in bytes overflows earlier than expected on Turkish text.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT title, LENGTH(title) AS length FROM book ORDER BY length DESC, title;
SQL
title              length
-----------------  ------
Tehlikeli Oyunlar  17    
Motherland Hotel   16    
The Book of Sand   16    
The Disconnected   16    
Silent House       12    
Blindness          9     
Yaban              5     

Cutting, Searching, Replacing

To take a piece of text, a substring function is used. The standard syntax and the common syntax diverge here.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT SUBSTRING('2024-01-09' FROM 1 FOR 4) AS year;
SQL
Parse error near line 3: near "FROM": syntax error
  SELECT SUBSTRING('2024-01-09' FROM 1 FOR 4) AS year;
                  error here ---^

The standard’s SUBSTRING(… FROM … FOR …) syntax is not recognized by this engine. The syntax the engine accepts is the form that separates arguments with commas.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT first_name, SUBSTR(registered_at,1,4) AS registration_year FROM member ORDER BY member_id;
SQL
first_name  registration_year
----------  -----------------
Alice       2023             
Ben         2023             
Clara       2024             
Derek       2024             
Grace       2024             
Owen        2025             

The name of the function that finds a substring’s position also depends on the engine. Instead of the standard’s POSITION(… IN …) syntax, this engine has a separately named function.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT email, INSTR(email,'@') AS position FROM member WHERE email IS NOT NULL ORDER BY member_id;
SQL
email               position
------------------  --------
[email protected]  6       
[email protected]    4       
[email protected]  6       
[email protected]   5       

Position starts at 1. String positions in SQL are one-based; the zero-based indexing habit from the Programming Fundamentals course does not apply here, and it is a frequent source of an off-by-one mistake.

Replacement and trimming functions are less variable.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT title, REPLACE(title,'Hotel','Inn') AS replaced FROM book
WHERE title LIKE '%Hotel%';
SELECT '[' || TRIM('   Blindness   ') || ']' AS trimmed;
SQL
title             replaced      
----------------  --------------
Motherland Hotel  Motherland Inn
trimmed    
-----------
[Blindness]

The trim function removes whitespace from both ends by default; there are also forms that state which characters to trim and that trim from only one end.

Number Functions and Division

Most numeric functions are uniform, but division behavior stands apart.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT ABS(-7) AS absolute, ROUND(2.555, 2) AS rounded,
       17 % 5 AS remainder, 17 / 5 AS quotient, 17.0 / 5 AS decimal_quotient;
SQL
absolute  rounded  remainder  quotient  decimal_quotient
--------  -------  ---------  --------  ----------------
7         2.56     2          3         3.4             

17 / 5 produced 3, 17.0 / 5 produced 3.4. What determines whether the division is an integer or a decimal is the type of the operands. This behavior depends on the engine: some engines produce a decimal for the division of two integers too. The portable syntax is to cast at least one operand to a decimal type before dividing.

The remainder operator can also be found in two forms, and the forms may not produce the same type.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT 17 % 5 AS with_operator, MOD(17,5) AS with_function;
SQL
with_operator  with_function
-------------  -------------
2              2.0          

The same number came back once as an integer and once as a decimal. This difference matters if the result is going to enter a comparison or a grouping.

There is a point worth watching in rounding too: decimal numbers cannot be represented exactly in binary — the conclusion of the floating-point lesson in the How Computers Work course holds here as well. In monetary calculations, trust is placed not in the rounding function, but in a decimal type.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT title, publication_year, publication_year % 10 AS last_digit FROM book
WHERE publication_year IS NOT NULL ORDER BY book_id;
SQL
title              publication_year  last_digit
-----------------  ----------------  ----------
Blindness          1995              5         
The Disconnected   1972              2         
The Book of Sand   1975              5         
Yaban              1932              2         
Silent House       1983              3         
Tehlikeli Oyunlar  1973              3         

Date Functions

Date is the area where the standard is applied the least. The standard defines a predicate-like syntax for extracting a part from a date, and an interval type for adding a duration to a date. Neither is present in every engine.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT EXTRACT(YEAR FROM pickup_date) AS year FROM loan;
SQL
Parse error near line 3: near "FROM": syntax error
  SELECT EXTRACT(YEAR FROM pickup_date) AS year FROM loan;
                      ^--- error here
sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT pickup_date + INTERVAL '14' DAY AS due_date FROM loan;
SQL
Parse error near line 3: near "DAY": syntax error
  SELECT pickup_date + INTERVAL '14' DAY AS due_date FROM loan;
                       error here ---^

Neither standard syntax worked on this engine. The equivalents the engine offers carry different names.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT loan_id, pickup_date,
       strftime('%Y', pickup_date) AS year,
       date(pickup_date,'+14 days')  AS due_date
FROM loan WHERE loan_id <= 4 ORDER BY loan_id;
SQL
loan_id  pickup_date  year  due_date  
-------  -----------  ----  ----------
1        2025-01-10   2025  2025-01-24
2        2025-02-02   2025  2025-02-16
3        2025-02-11   2025  2025-02-25
4        2025-03-01   2025  2025-03-15

Taking the difference between two dates in days also requires engine-specific syntax.

sqlite3 library.db <<'SQL'
.headers on
.mode column
SELECT loan_id, pickup_date, return_date,
       CAST(julianday(return_date) - julianday(pickup_date) AS INTEGER) AS days
FROM loan WHERE return_date IS NOT NULL AND loan_id <= 5 ORDER BY loan_id;
SQL
loan_id  pickup_date  return_date  days
-------  -----------  -----------  ----
1        2025-01-10   2025-01-24   14  
2        2025-02-02   2025-02-20   18  
4        2025-03-01   2025-03-15   14  
5        2025-03-18   2025-04-02   15  

The third loan record is not in the list: because its return date is null, the difference cannot be computed, and the condition eliminated it. The null value rule holds here too.

The engine-dependence of date functions has a practical consequence: once date arithmetic gets embedded inside a query, the query becomes unportable. When portability is required, date arithmetic either moves to the application layer or the engine-specific part gets gathered in a single place. Whether a date column carries a time zone also depends on the engine and the type; a day computation done on a column without a time zone can drift by a day at a time zone boundary.

Summary

  • Scalar functions run per row and do not change the result set’s row count.
  • Whether the length function counts characters or bytes depends on the engine; the two diverge on non-ASCII text.
  • String positions are one-based; the standard syntax for substring and position functions is not present in every engine.
  • The type of the operands determines whether division is an integer or a decimal, and this behavior depends on the engine.
  • Date functions are the area that diverges from the standard the most; the syntax for extracting a part and adding a duration changes from engine to engine.
  • The null value rule holds in date arithmetic too: a difference with one blank end cannot be computed.

Next Step

Up to this point, every query has read from a single table. Yet in a normalized schema, meaningful questions do not fit into a single table: the answer to “which member borrowed which book” is spread across three tables, because the loan table holds only ids. The next topic builds the clause that joins tables, and its first lesson takes up the intersection of matching rows — the inner join.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close