Complete reference for SQLite core and scalar functions covering abs, coalesce, typeof, length, replace, nullif, and all other built-in scalar functions. Every function includes syntax, real SQL examples, and SQLite-specific quirk warnings. Updated for SQLite 3.52.
SQLite core scalar functions are the foundational built-in functions available in every SQLite build. They operate on individual values and return a single result. Because SQLite uses dynamic typing (called type affinity), many core functions behave differently than their counterparts in PostgreSQL or MariaDB — for example, typeof() returns a storage class string rather than a SQL type name.
abs
SQLite 3.0.0 (original release)→ INTEGER or REAL
Returns the absolute value of the numeric argument X, returning NULL if X is NULL and 0.0 if X is a string or blob that cannot be converted to a numeric value.
Signature
abs(X)
Parameters
Parameter
Type
Description
X
any
The numeric value whose absolute value is to be returned.
Examples
Basic absolute value of a negative integer
sql
SELECTabs(-5);
→5
Absolute value of a positive real
sql
SELECTabs(3.14);
→3.14
Absolute value of a negative real
sql
SELECTabs(-3.14);
→3.14
NULL propagates through abs()
sql
SELECTabs(NULL);
→NULL
Non-numeric strings return 0.0
sql
SELECTabs('not a number');
→0.0
⚠Anti-Pattern— Using abs() in a WHERE clause instead of BETWEEN
Writing WHERE abs(price - target) < 10 forces SQLite to compute abs() for every row, preventing any index on the price column from being used. This causes a full table scan even when a perfectly good index exists.
✓ Instead: Use a range predicate: WHERE price BETWEEN target - 10 AND target + 10 so the query planner can use an index on price.
Passing the minimum 64-bit integer -9223372036854775808 to abs() throws an integer overflow error because there is no positive 64-bit equivalent. The random() function deliberately avoids generating this value so its output can always be safely passed to abs().
In PostgreSQL, abs() raises an error for numeric overflow too, but the overflow boundary differs by type. In SQLite, the only dangerous value is the single 64-bit minimum integer -9223372036854775808; passing a non-numeric string returns 0.0 instead of an error, unlike PostgreSQL which raises a cast error.
Returns the number of database rows changed, inserted, or deleted by the most recently completed INSERT, DELETE, or UPDATE statement, exclusive of changes in lower-level triggers.
⚠Anti-Pattern— Ignoring changes() after DELETE or UPDATE to verify affected rows
After a DELETE or UPDATE, developers often skip calling changes() and assume the operation succeeded on the expected rows. When a WHERE clause has a typo or a bound parameter is NULL, zero rows are affected with no error raised — the bug is completely silent.
✓ Instead: Always call changes() immediately after a mutating statement and check that the count matches your expectation before committing.
changes() only counts rows modified by the outermost statement, not by any triggers that statement fires. If you need to include trigger-induced changes, use total_changes() instead.
→Count of clearance products whose price was reduced
◆SQLite Note
PostgreSQL uses GET DIAGNOSTICS ROW_COUNT or the libpq PQcmdTuples() API to retrieve affected row counts — there is no equivalent scalar SQL function. SQLite's changes() is a first-class scalar function callable anywhere in a query.
Returns a string composed of characters having the Unicode code point values of the integers X1 through XN, respectively.
Signature
char(X1,X2,...,XN)
Parameters
Parameter
Type
Description
X1,X2,...,XN
INTEGER
One or more integer Unicode code point values to convert into characters.
Examples
Single ASCII character from code point
sql
SELECTchar(65);
→A
Multiple code points form a string
sql
SELECTchar(72, 101, 108, 108, 111);
→Hello
Unicode character outside the ASCII range
sql
SELECTchar(9829);
→♥
Emoji from a hexadecimal code point literal
sql
SELECTchar(0x1F600);
→😀
Verify UTF-8 byte output for ASCII code points
sql
SELECThex(char(65, 66, 67));
→414243
⚠Anti-Pattern— Building multi-character strings by concatenating char() calls in a loop
Calling char() inside a loop and assembling a string character by character with the || operator is inefficient and fragile — each concatenation creates a new string object, and the intent is obscured by the boilerplate.
✓ Instead: If you need to assemble a string from a set of code points stored in a table, use group_concat(char(codepoint)) in a single aggregate query.
char() is the inverse of unicode(). It accepts any number of arguments and is useful for injecting special characters such as tabs (char(9)), newlines (char(10)), or non-ASCII glyphs into string expressions without needing escape sequences.
example
SELECTgroup_concat(char(unicode(c) +1), '') FROM (SELECTsubstr(word,n,1) AScFROMwords, generate_series(1,length(word)) WHEREid=1) WHEREcBETWEEN'a'AND'z';
→Caesar-shifts lowercase letters by one position
◆SQLite Note
PostgreSQL uses chr(N) (single argument only) for the same purpose. SQLite's char() is variadic and can produce a multi-character string in one call, whereas PostgreSQL chr() always returns a single character.
coalesce(X,Y) is equivalent to ifnull(X,Y) for two args
sql
SELECTcoalesce(NULL, 1) =ifnull(NULL, 1);
→1
⚠Anti-Pattern— Using coalesce() to silently hide NULL data bugs
Wrapping a column in coalesce(column, 0) or coalesce(column, '') masks NULLs in query results without fixing why the NULLs exist in the first place. Downstream aggregations then operate on invented values, producing subtly wrong totals and averages that are hard to debug.
✓ Instead: Fix the root cause by adding a NOT NULL constraint with a DEFAULT at the schema level, reserving coalesce() for the rare case where NULL genuinely means 'use this fallback'.
SQLite evaluates coalesce() arguments left to right and stops as soon as it finds a non-NULL value, so expensive subqueries placed later in the argument list are skipped when an earlier argument is non-NULL.
→Uses nickname if set, falls back to first_name, then 'Anonymous'
◆SQLite Note
The behaviour and signature of coalesce() are the same in SQLite and PostgreSQL — both require at least 2 arguments and return the first non-NULL value. However, PostgreSQL also offers the COALESCE expression as part of standard SQL syntax with identical semantics.
Returns 1 if string Y matches glob pattern X using case-sensitive matching, equivalent to the expression 'Y GLOB X' but with the arguments reversed.
Signature
glob(X,Y)
Parameters
Parameter
Type
Description
X
TEXT
The glob pattern to match against (uses * for any sequence and ? for any single character).
Y
TEXT
The string to test against the pattern.
Examples
Pattern matches the filename
sql
SELECTglob('*.txt', 'readme.txt');
→1
Pattern does not match
sql
SELECTglob('*.txt', 'readme.md');
→0
Character class in glob pattern
sql
SELECTglob('*[Ss]QLite*', 'SQLite3');
→1
? matches exactly one character; 'my' is only two
sql
SELECTglob('???.db', 'my.db');
→0
Use glob() as a WHERE predicate
sql
SELECTnameFROMfilesWHEREglob('*.json', name);
→(rows where name ends in .json)
⚠Anti-Pattern— Using GLOB when case-insensitive matching is needed
Unlike LIKE, the GLOB operator is always case-sensitive in SQLite — GLOB 'A*' will never match the string 'apple'. Developers migrating from other databases or mixing GLOB with LIKE often miss this, producing queries that silently return zero rows.
✓ Instead: Use LIKE for case-insensitive pattern matching, or normalize both sides with upper()/lower() if you specifically need GLOB's wildcard semantics.
In the GLOB operator you write 'Y GLOB X', but in the glob() function the order is glob(X, Y) — pattern first, string second. The function form exists so you can override the GLOB operator by replacing glob() with sqlite3_create_function().
→All image file attachments (case-sensitive match on lowercased names)
◆SQLite Note
PostgreSQL has no built-in glob() function; it uses LIKE or SIMILAR TO for pattern matching, or the ~ operator with POSIX regular expressions. SQLite's GLOB is always case-sensitive, whereas LIKE is case-insensitive for ASCII letters by default.
SQLite 3.32.0 (2020-05-22); two-argument form and if() alias in 3.48.0; N-argument form in 3.49.0→ any
Evaluates pairs of Boolean/value arguments and returns the value associated with the first true Boolean; if no Boolean is true, returns the last argument (odd count) or NULL (even count).
Signatures
iif(B1,V1,...)
if(B1,V1,...)
Parameters
Parameter
Type
Description
B1,V1,...
any
Alternating Boolean condition and value pairs; an optional trailing argument serves as the else-value.
Two-argument form: returns x when true, else NULL (added in SQLite 3.48.0)
sql
SELECTif(x>0, x) FROMt;
→(x or NULL)
⚠Anti-Pattern— Nesting iif() three or more levels deep
Deeply nested iif(cond1, iif(cond2, iif(cond3, a, b), c), d) is functionally valid but nearly impossible to read, review, or modify without introducing a logic error in the nesting order. SQLite evaluates both branches eagerly in some contexts, so errors in any branch can surface unexpectedly.
✓ Instead: Replace multi-level nesting with CASE WHEN ... THEN ... WHEN ... THEN ... ELSE ... END, which scales cleanly to any number of conditions.
Value arguments that correspond to false Booleans are never evaluated, so expensive subqueries or functions in later branches incur no cost when an earlier condition is true. This makes iif() more efficient than equivalent CASE expressions in theory, though they compile to identical bytecode for the three-argument form.
PostgreSQL has no iif() or if() function; the equivalent is a CASE expression: CASE WHEN B THEN V1 ELSE V2 END. SQLite's iif() is syntactic sugar for exactly that CASE expression and generates identical bytecode. The two-argument form and the if() alias were added in SQLite 3.48.0 (2025-01-14); support for more than three arguments was added in 3.49.0 (2025-02-06).
Returns a copy of its first non-NULL argument, or NULL if both arguments are NULL; equivalent to coalesce() with exactly two arguments.
Signature
ifnull(X,Y)
Parameters
Parameter
Type
Description
X
any
The primary value; returned as-is if it is not NULL.
Y
any
The fallback value returned when X is NULL.
Examples
Returns the fallback when X is NULL
sql
SELECTifnull(NULL, 'default');
→default
Returns X when X is not NULL
sql
SELECTifnull('value', 'default');
→value
Returns NULL when both arguments are NULL
sql
SELECTifnull(NULL, NULL);
→NULL
Replace NULLs with empty string in a column
sql
SELECTifnull(middle_name, '') FROMpersons;
→(middle name or empty string)
Column-level NULL substitution
sql
SELECTifnull(price, list_price) FROMproducts;
→(price or list_price)
⚠Anti-Pattern— Reaching for ifnull() when more than two fallback values are needed
ifnull(a, b) only accepts exactly two arguments. Developers sometimes write nested ifnull(ifnull(a, b), c) to handle a chain of fallbacks, which becomes unreadable quickly and is easy to get wrong when the nesting order matters.
✓ Instead: Use coalesce(a, b, c, ...) which accepts any number of arguments and returns the first non-NULL, making the intent immediately clear.
ifnull(X,Y) is exactly equivalent to coalesce(X,Y). Use ifnull() when you have exactly two operands for clarity; switch to coalesce() when you need a chain of three or more fallback values.
PostgreSQL uses COALESCE(X,Y) for the same purpose and has no ifnull() function. SQLite supports both ifnull() and coalesce() with identical semantics. MySQL also has IFNULL(), so code using ifnull() is not portable to PostgreSQL without substitution.
⚠Anti-Pattern— Relying on last_insert_rowid() after multiple statements in a transaction
last_insert_rowid() returns the rowid of the most recent successful INSERT on the current database connection. If any INSERT — including a trigger-fired INSERT — runs between your target insert and your call to last_insert_rowid(), the value you get back will be wrong.
✓ Instead: Call last_insert_rowid() immediately after the single INSERT you care about, before any other statement executes, or use RETURNING rowid in the INSERT statement itself (SQLite 3.35+).
last_insert_rowid() returns the rowid of the last successful INSERT on the connection, even if that INSERT was later rolled back. If you need the rowid of a committed row, read it before rolling back.
→Links the new order_item to the order just inserted using its rowid
◆SQLite Note
PostgreSQL uses RETURNING id or the sequence function lastval() / currval() to retrieve auto-generated primary keys. SQLite has no RETURNING clause before version 3.35.0 (2021-03-12); last_insert_rowid() is the traditional way to retrieve the new rowid and is connection-scoped, not statement-scoped.
Returns its first argument X unchanged while providing the query planner with a hint that X is a Boolean true with probability Y (a floating-point constant between 0.0 and 1.0).
Signature
likelihood(X,Y)
Parameters
Parameter
Type
Description
X
any
The value or expression to return unchanged; typically a Boolean condition.
Y
REAL
A compile-time floating-point constant between 0.0 and 1.0 representing the probability that X is true.
Examples
Hints to the planner that x > 0 is true 90% of the time
⚠Anti-Pattern— Using likelihood() with inaccurate probability values to force a query plan
Passing a probability to likelihood() that does not reflect reality — for example, likelihood(status = 'active', 0.01) when 90% of rows are active — misleads the query planner into choosing a worse plan. SQLite trusts the hint unconditionally and will pick a full table scan or bad join order as a result.
✓ Instead: Only use likelihood() when you have measured the actual selectivity of the expression; for most cases let the query planner use its own statistics or run ANALYZE to refresh them.
likelihood() is a no-op at runtime — the code generator removes it entirely from the bytecode. It exists solely to carry a probability annotation to the query planner. Use likely(X) and unlikely(X) as convenient shorthands for likelihood(X,0.9375) and likelihood(X,0.0625).
→Same rows as without the hint, but planner knows 92% of events are pageviews
◆SQLite Note
PostgreSQL has no likelihood() function. The closest equivalent is the statistics stored by ANALYZE and adjustable via ALTER TABLE ... ALTER COLUMN ... SET STATISTICS, but there is no inline SQL-level probability hint function. SQLite's likelihood() has zero runtime overhead because it is compiled away.
⚠Anti-Pattern— Applying likely() to multiple conditions hoping to speed up the entire query
Wrapping several WHERE conditions in likely() at once adds noise that confuses the query planner without providing any benefit. When every condition is marked likely, the hints cancel each other out and the planner ignores them.
✓ Instead: Use likely() sparingly — only on the single condition that is genuinely true for the vast majority of rows — and rely on ANALYZE-updated statistics for everything else.
Misapplying likely() on a predicate that is actually rarely true can degrade query performance. Only use it when ANALYZE cannot capture selectivity, such as on functions or expressions over non-indexed columns.
example
SELECT*FROMusersWHERElikely(is_active=1);
→Same as WHERE is_active = 1 — hints to planner that most rows are active
◆SQLite Note
PostgreSQL has no likely() function. SQLite's likely() is a no-op at runtime — it is compiled away and incurs zero CPU overhead during query execution.
Loads an SQLite extension from the shared library file named X using entry point Y (or the default entry point if Y is omitted), returning NULL on success and raising an exception on failure.
Signatures
load_extension(X)
load_extension(X,Y)
Parameters
Parameter
Type
Description
X
TEXT
Path to the shared library (.so / .dll / .dylib) to load.
Y
TEXT
Optional entry point function name in the shared library; defaults to a name derived from the library filename.
Extension loading must be enabled before this call works
sql
-- Enable extension loading first (C API or CLI):-- sqlite3_enable_load_extension(db, 1);SELECTload_extension('./ext.so');
→NULL
⚠Anti-Pattern— Leaving extension loading permanently enabled in production connections
Extension loading is disabled by default for security reasons. Calling load_extension() on a connection where it has not been explicitly enabled raises an error at runtime. Applications that leave extension loading enabled in production expose a local code execution vector if users can influence the path argument.
✓ Instead: Enable extensions only on trusted internal connections immediately before loading, then disable the capability again with sqlite3_enable_load_extension(db, 0) to limit the attack surface.
Extension loading is disabled by default for security reasons. It must be enabled via sqlite3_enable_load_extension() in the C API, the .load dot-command in the CLI, or a similar mechanism in your language binding. An extension that tries to modify or delete existing SQL functions must be loaded via the C API instead.
example
SELECTload_extension('./spellfix.so');
→NULL — extension loaded; spellfix1 virtual table now available
◆SQLite Note
PostgreSQL uses CREATE EXTENSION to install server-side extensions from a registry; there is no dynamic runtime loading function. SQLite's load_extension() loads shared libraries at runtime from the filesystem, which is a fundamentally different — and more flexible but less sandboxed — mechanism.
⚙ Availability: Disabled by default; requires sqlite3_enable_load_extension() to be called first (or SQLITE_LOAD_EXTENSION compile-time flag).
Returns the argument with the maximum value from two or more arguments, returning NULL if any argument is NULL; operates as an aggregate function when given a single argument.
Signature
max(X,Y,...)
Parameters
Parameter
Type
Description
X,Y,...
any
Two or more values to compare; the greatest value is returned.
Examples
Maximum of three integers
sql
SELECTmax(1, 2, 3);
→3
Maximum of real values
sql
SELECTmax(3.14, 2.72, 1.41);
→3.14
String comparison uses BINARY collation
sql
SELECTmax('banana', 'apple', 'cherry');
→cherry
Any NULL argument causes the result to be NULL
sql
SELECTmax(10, NULL);
→NULL
Row-level max across columns (scalar form)
sql
SELECTmax(a, b, c) FROMt;
→(row-wise maximum)
⚠Anti-Pattern— Using scalar max(a, b) in a loop to find the maximum across rows
The scalar two-argument max(a, b) compares two expressions within the same row. Developers sometimes use it in subqueries or application loops to find the maximum across rows, forcing multiple round-trips when a single aggregate MAX() over the full result set would be faster and clearer.
✓ Instead: Use aggregate MAX(column) in a single SELECT to find the maximum value across rows; reserve the scalar max(a, b) form only for comparing two expressions within the same row.
max() with two or more arguments is the scalar greatest() function. max() with a single argument is the aggregate MAX(). PostgreSQL names these separately: greatest() for the scalar form and max() only for aggregation. In SQLite, both share the same name but differ by argument count.
→Uses price if above minimum, otherwise floors at min_price
◆SQLite Note
PostgreSQL separates greatest(X,Y,...) (scalar) from max(X) (aggregate). SQLite overloads the name max(): one argument triggers aggregate mode, two or more arguments trigger scalar mode. Unlike PostgreSQL's greatest(), SQLite's scalar max() returns NULL if any argument is NULL, whereas PostgreSQL's greatest() skips NULLs.
Returns the argument with the minimum value from two or more arguments; operates as an aggregate function when given a single argument.
Signature
min(X,Y,...)
Parameters
Parameter
Type
Description
X,Y,...
any
Two or more values to compare; the smallest value is returned.
Examples
Minimum of four integers
sql
SELECTmin(5, 3, 8, 1);
→1
Minimum of real values
sql
SELECTmin(3.14, 2.72, 1.41);
→1.41
Lexicographic minimum with BINARY collation
sql
SELECTmin('banana', 'apple', 'cherry');
→apple
NULL in any argument propagates NULL out
sql
SELECTmin(a, NULL);
→NULL
Row-level minimum across two columns
sql
SELECTmin(start_date, end_date) FROMevents;
→(earlier of the two dates per row)
⚠Anti-Pattern— Nesting scalar min() calls to find the minimum of three or more values
The scalar min(a, b) in SQLite accepts exactly two arguments, so finding the minimum of three values requires nesting: min(a, min(b, c)). This is error-prone and the syntax is visually identical to aggregate MIN(), making the code confusing to readers.
✓ Instead: For more than two values, use a VALUES clause and aggregate MIN(): SELECT MIN(v) FROM (VALUES (a),(b),(c)) t(v), which is clear and scales to any number of candidates.
SQLite's scalar min() returns NULL if any argument is NULL. PostgreSQL's least() ignores NULLs and returns the minimum of the non-NULL arguments. Wrap arguments in ifnull() if you need PostgreSQL-style NULL-skipping behaviour.
→Order only what stock allows, capped at reorder quantity
◆SQLite Note
PostgreSQL names the scalar form least(X,Y,...) and the aggregate form min(X). SQLite uses min() for both, disambiguated by argument count. PostgreSQL's least() skips NULLs; SQLite's scalar min() does not — any NULL argument returns NULL.
Returns its first argument if the two arguments are different, or NULL if they are equal.
Signature
nullif(X,Y)
Parameters
Parameter
Type
Description
X
any
The value to return when X and Y are not equal.
Y
any
The value to compare against X; if equal to X, NULL is returned.
Examples
Arguments differ, so X is returned
sql
SELECTnullif(5, 3);
→5
Arguments are equal, so NULL is returned
sql
SELECTnullif(5, 5);
→NULL
Useful for converting empty strings to NULL
sql
SELECTnullif('', '');
→NULL
Classic use: avoid division by zero
sql
SELECT10/nullif(divisor, 0) FROMt;
→(10/divisor or NULL)
Replace a sentinel value with NULL
sql
SELECTnullif(status, 'active') FROMaccounts;
→(NULL for active, status otherwise)
⚠Anti-Pattern— Confusing nullif(a, b) with coalesce(a, b)
nullif(a, b) returns NULL when a equals b, and returns a otherwise — the opposite of what many developers expect. It is frequently confused with coalesce(a, b) which returns the first non-NULL value. Using nullif when coalesce was intended silently replaces matching values with NULL instead of providing a fallback.
✓ Instead: Use nullif() only for the specific purpose of converting a sentinel value (like 0 or '') to NULL so that aggregate functions will skip it; use coalesce() when you want a default fallback for NULLs.
COALESCE returns a value when it finds a non-NULL; nullif() does the reverse — it returns NULL when the arguments are equal. The classic safe-division pattern is 10 / nullif(denominator, 0), which yields NULL instead of a divide-by-zero error.
→NULL instead of division-by-zero when cohort has no rows
◆SQLite Note
nullif() behaves identically in SQLite and PostgreSQL. Both return NULL when the two arguments are equal and X otherwise. The function uses the same collation resolution rules as other binary comparison functions in SQLite.
Returns the text of an SQL literal representing the value of X, with strings surrounded by single-quotes (interior quotes escaped) and BLOBs encoded as hexadecimal X'...' literals.
Signature
quote(X)
Parameters
Parameter
Type
Description
X
any
The value to be rendered as a safe SQL literal string.
Examples
String value wrapped in single quotes
sql
SELECTquote('hello');
→'hello'
Interior single quotes are escaped by doubling
sql
SELECTquote('it''s');
→'it''s'
Integer literals need no quoting
sql
SELECTquote(42);
→42
Real literals need no quoting
sql
SELECTquote(3.14);
→3.14
BLOB is rendered as a hex literal
sql
SELECTquote(x'DEADBEEF');
→X'DEADBEEF'
⚠Anti-Pattern— Using quote() to format values for display in the UI
quote() returns SQL-literal representations: strings are single-quoted with internal quotes escaped, blobs are prefixed with X''. These formats are correct for embedding values back into SQL strings but look wrong and confusing when shown to end users in reports or interfaces.
✓ Instead: Use quote() exclusively for generating SQL text (e.g., for export scripts or dynamic SQL); use plain string formatting or CAST for human-readable display output.
quote() is used internally by the .dump CLI command to produce INSERT statements that correctly re-insert all data types, including BLOBs and strings with special characters. Use unistr_quote() instead if you also need control characters (U+0001–U+001F) to be escaped.
example
SELECT'INSERT INTO backup VALUES('||quote(name) ||','||quote(value) ||');'FROMconfig;
→"INSERT INTO backup VALUES('app_name','MyApp');" — SQL-safe export
◆SQLite Note
PostgreSQL uses the quote_literal() and quote_ident() functions for similar escaping tasks. SQLite's quote() handles all scalar storage classes in one function and encodes BLOBs as X'...' hex literals, whereas PostgreSQL's quote_literal() only handles text and uses $ quoting for BYTEA differently.
⚠Anti-Pattern— Calling random() in a loop to generate a sequence of random values
Executing SELECT random() in a loop from application code generates one random number per round-trip, making the code slow and its intent unclear. Each call is also independent, offering no guarantee of uniqueness across rows.
✓ Instead: Use ORDER BY random() in a single query to shuffle rows, or use randomblob(N) to generate a bulk random byte sequence in one call.
Unlike many languages where random() returns a float in [0.0, 1.0), SQLite's random() returns a 64-bit signed integer. Divide by 9223372036854775807.0 and take the absolute value to obtain a uniform float in [0.0, 1.0]. The PRNG has used Chacha20 since SQLite 3.40.0.
→10 randomly sampled survey responses for QA review
◆SQLite Note
PostgreSQL's random() returns a double precision float in [0.0, 1.0). SQLite's random() returns a 64-bit signed integer in [-9223372036854775807, +9223372036854775807]. The two functions have completely different return ranges and types, so code using random() is not directly portable between the two databases.
⚠Anti-Pattern— Storing randomblob() output directly in a TEXT column without hex encoding
randomblob(16) produces a raw binary BLOB. Storing it directly in a TEXT column causes implicit coercion that can corrupt the value or produce unreadable data. Comparing two such BLOBs with = works at the byte level, but displaying or transmitting them as text is error-prone.
✓ Instead: Wrap the result in hex() or lower(hex()) to store it as a readable hexadecimal string, or use the hex-formatted UUID pattern: lower(hex(randomblob(4)))||'-'||... for RFC 4122 style UUIDs.
The SQLite documentation itself suggests hex(randomblob(16)) or lower(hex(randomblob(16))) as a simple way to generate a globally unique 128-bit identifier. For a proper UUID v4 format, use the uuid extension available in the CLI and many SQLite builds.
→Inserts a 32-character hex API token derived from 16 random bytes
◆SQLite Note
PostgreSQL provides gen_random_bytes(N) from the pgcrypto extension and gen_random_uuid() for UUID generation. SQLite has no built-in UUID function in the core, but randomblob(16) combined with hex() provides a functionally equivalent random identifier. PostgreSQL returns BYTEA; SQLite returns BLOB.
Returns the floating-point value X rounded to Y digits to the right of the decimal point; if Y is omitted or negative, it defaults to 0.
Signatures
round(X)
round(X,Y)
Parameters
Parameter
Type
Description
X
REAL
The number to be rounded.
Y
INTEGER
Optional number of decimal places; defaults to 0 if omitted or negative.
Examples
Round to zero decimal places
sql
SELECTround(3.14159);
→3.0
Round to two decimal places
sql
SELECTround(3.14159, 2);
→3.14
Half-integer rounds away from zero
sql
SELECTround(3.5);
→4.0
Rounds to 3 (away from zero, not banker's rounding)
sql
SELECTround(2.5);
→3.0
Negative Y treated as 0; does not round to hundreds place
sql
SELECTround(12345.678, -2);
→12346.0
⚠Anti-Pattern— Expecting round() to return an INTEGER
round(2.5) returns 3.0 in SQLite, not the integer 3. Code that then inserts this into an INTEGER column or compares it with = to an integer constant works by accident due to type affinity coercion, but explicit CAST checks or strict-mode tables will see the value as REAL and behave unexpectedly.
✓ Instead: Apply CAST(round(x) AS INTEGER) explicitly when you need a true integer result, and be aware that SQLite uses 'round half away from zero' rather than the banker's rounding used by some other languages.
Unlike PostgreSQL where round() preserves the numeric type (returning INTEGER for round(3) and NUMERIC for round(3.5, 0)), SQLite's round() always returns a REAL value. The result of round(3) is 3.0, not 3. Cast to INTEGER explicitly if you need an integer result.
→Average rating rounded to one decimal place per product
◆SQLite Note
PostgreSQL's round(X) returns a value of the same numeric type as X, so round(3.5) returns 4 as a NUMERIC, not 4.0 as a REAL. SQLite's round() always returns REAL. Additionally, PostgreSQL supports round(X, Y) with negative Y to round to the left of the decimal point; SQLite treats negative Y as 0 (rounds to whole number only).
Returns -1, 0, or +1 if the numeric argument X is negative, zero, or positive respectively; returns NULL if X is NULL, a BLOB, or a string that cannot be losslessly converted to a number.
Signature
sign(X)
Parameters
Parameter
Type
Description
X
any
A numeric value or expression whose sign is to be determined.
Examples
Negative number returns -1
sql
SELECTsign(-42);
→-1
Zero returns 0
sql
SELECTsign(0);
→0
Positive number returns 1
sql
SELECTsign(3.14);
→1
NULL propagates
sql
SELECTsign(NULL);
→NULL
Non-numeric string returns NULL
sql
SELECTsign('not a number');
→NULL
⚠Anti-Pattern— Using sign() for NULL-safe equality comparisons instead of IS
Some developers use sign(a - b) = 0 as a NULL-safe equality check, but if either a or b is NULL, sign() returns NULL, which is falsy — so the comparison silently excludes NULL-containing rows rather than treating them as equal to each other.
✓ Instead: Use the IS operator (a IS b) for NULL-safe equality in SQLite; it returns 1 when both sides are NULL, and 0 when only one is, without any arithmetic gymnastics.
sign('3') returns 1 because '3' can be losslessly converted to the integer 3. However sign('3.5abc') returns NULL because that string cannot be losslessly converted. This differs from abs(), which returns 0.0 for any non-convertible string rather than NULL.
→1 for credits, -1 for debits, 0 for zero-amount adjustments
◆SQLite Note
PostgreSQL has a sign() function with the same semantics for numeric types. The key SQLite difference is that sign() returns NULL for strings that cannot be losslessly converted to a number, whereas PostgreSQL raises a type error. SQLite's sign() was added in version 3.35.0 together with the math functions.
SQLite 3.0.0 (original release, but compile-time only)→ TEXT
Returns a string that is the Soundex encoding of string X; returns '?000' if the argument is NULL or contains no ASCII alphabetic characters.
Signature
soundex(X)
Parameters
Parameter
Type
Description
X
TEXT
The string whose Soundex phonetic code is to be computed.
Examples
Soundex code for 'Robert'
sql
SELECTsoundex('Robert');
→R163
'Robert' and 'Rupert' share the same Soundex code
sql
SELECTsoundex('Rupert');
→R163
Common surname example
sql
SELECTsoundex('Smith');
→S530
Empty string returns the default code
sql
SELECTsoundex('');
→?000
NULL also returns the default code
sql
SELECTsoundex(NULL);
→?000
⚠Anti-Pattern— Treating SOUNDEX codes as language-independent phonetic matching
The soundex() function implements the original Soundex algorithm designed specifically for English surnames of European origin. Applying it to names from Arabic, Chinese, Hindi, or other non-Latin scripts produces meaningless or identical codes, giving false positives and missing true matches.
✓ Instead: For multilingual phonetic matching, load the ICU or unicode61 tokenizer extension, or implement language-specific normalization in application code before storing names; reserve soundex() for legacy English-only name deduplication tasks.
soundex() is not compiled into SQLite by default. It is only available when the library is built with the -DSQLITE_SOUNDEX compile-time option. Check sqlite_compileoption_used('SOUNDEX') to determine if it is available in the current build.
→Finds 'Smith', 'Smyth', 'Smithe' — phonetically similar last names
◆SQLite Note
PostgreSQL offers soundex() via the fuzzystrmatch extension (CREATE EXTENSION fuzzystrmatch). In SQLite, soundex() is a compile-time option (SQLITE_SOUNDEX) built directly into the library rather than loaded as a separate extension. Both implement the classic American Soundex algorithm.
⚙ Availability: Only available when SQLite is compiled with -DSQLITE_SOUNDEX.
Returns the N-th compile-time option used to build SQLite as a string, or NULL if N is out of range.
Signature
sqlite_compileoption_get(N)
Parameters
Parameter
Type
Description
N
INTEGER
Zero-based index of the compile-time option to retrieve.
Examples
First compile-time option (example output)
sql
SELECTsqlite_compileoption_get(0);
→COMPILER=gcc-14.2.0
Second compile-time option
sql
SELECTsqlite_compileoption_get(1);
→DEFAULT_AUTOVACUUM
Out-of-range index returns NULL
sql
SELECTsqlite_compileoption_get(999);
→NULL
List all compile-time options using a recursive CTE
sql
WITHRECURSIVEopts(n,o) AS (
SELECT0, sqlite_compileoption_get(0)
UNIONALLSELECTn+1, sqlite_compileoption_get(n+1)
FROMoptsWHEREoISNOTNULL
)
SELECToFROMoptsWHEREoISNOTNULL;
→(all compile-time options)
Equivalent pragma for listing all compile options
sql
PRAGMAcompile_options;
→(table of all options)
⚠Anti-Pattern— Iterating sqlite_compileoption_get() with a hardcoded upper bound
The number of compile-time options varies across SQLite versions and builds. Using a fixed loop limit like FOR i IN 0..100 risks missing options in a feature-rich build or wasting iterations on an empty return once the list is exhausted — sqlite_compileoption_get() returns NULL for out-of-range indices, but the exact count is not exposed directly.
✓ Instead: Loop until sqlite_compileoption_get(i) returns NULL, or query the pragma_compile_options virtual table with SELECT * FROM pragma_compile_options for a cleaner, set-based approach.
PRAGMA compile_options is usually easier to use than iterating sqlite_compileoption_get(N) manually. The function form is useful when you need to access options programmatically in a subquery or CTE.
example
SELECTsqlite_compileoption_get(/* see description */);
→See examples above for real-world usage
◆SQLite Note
PostgreSQL has no equivalent function; compile-time options are embedded in the server binary and surfaced via pg_config or the pg_config() function. SQLite's compile-time options directly affect which features are available at runtime and are queryable through both a function and a pragma.
⚠Anti-Pattern— Using sqlite_compileoption_used() at runtime to gate production feature paths
Checking compile options at runtime to decide whether to use a feature (e.g., FTS5 or JSON1) ties business logic to build configuration. If the library is replaced or recompiled without that option, the feature silently disappears with no schema or migration guard.
✓ Instead: Test for feature availability once at application startup, assert the required options are present, and fail fast with a clear error message rather than silently degrading at runtime.
Wrap calls to optional functions such as soundex() or sqlite_offset() inside an iif(sqlite_compileoption_used('SOUNDEX'), ..., NULL) to write code that degrades gracefully on builds that omit the feature.
example
SELECTsqlite_compileoption_used(/* see description */);
→See examples above for real-world usage
◆SQLite Note
PostgreSQL has no equivalent single-function capability check. PostgreSQL features are controlled at extension installation time (CREATE EXTENSION) rather than compile time. In SQLite, many features are baked in at compile time and cannot be added at runtime, making this function important for writing portable SQLite code.
Returns the byte offset in the database file for the beginning of the record from which the value of column X would be read, or NULL if X is not a column in an ordinary table.
Signature
sqlite_offset(X)
Parameters
Parameter
Type
Description
X
any
A reference to a table column; the function returns the on-disk byte offset of that column's record.
Examples
Byte offset of the first row's name column record
sql
SELECTsqlite_offset(name) FROMusersLIMIT1;
→4096
Offset of a specific row's record
sql
SELECTsqlite_offset(rowid) FROMtWHEREid=42;
→8192
Not a column reference — returns NULL
sql
SELECTsqlite_offset(1+1);
→NULL
Show the storage offset for every row's name field
⚠Anti-Pattern— Using sqlite_offset() on a column sourced from a view or subquery
sqlite_offset(X) returns the byte offset of column X within the raw database file, but it only works when X is a direct column reference from a real base table in the current query. Applying it to a column sourced from a view, subquery, or expression silently returns NULL, which looks like a valid 'no offset' result.
✓ Instead: Use sqlite_offset() only in simple SELECT statements that directly reference a real table column without any intervening view or subquery layer, and always verify the result is non-NULL before using it for blob I/O operations.
sqlite_offset() is primarily a debugging and low-level tool. Its main practical use is to locate a row's storage position before opening it with the sqlite3_blob_open() C API for incremental reading or writing of large BLOB values without loading them fully into memory.
example
SELECTsqlite_offset(/* see description */);
→See examples above for real-world usage
◆SQLite Note
PostgreSQL has no equivalent function. This is a SQLite-specific introspection tool for the underlying B-tree file format. It only works when SQLite is compiled with -DSQLITE_ENABLE_OFFSET_SQL_FUNC, which is not the default.
⚙ Availability: Only available when SQLite is compiled with -DSQLITE_ENABLE_OFFSET_SQL_FUNC.
Returns a string identifying the specific source code version used to build the SQLite library, consisting of the check-in date/time followed by the SHA3-256 hash of that check-in.
Signature
sqlite_source_id()
Examples
Date, time, and SHA3-256 hash of the source check-in
sql
SELECTsqlite_source_id();
→2026-03-06 16:01:44 557aeb43869d3585...
Extract just the build date
sql
SELECTsubstr(sqlite_source_id(), 1, 10);
→2026-03-06
Total length: 19 char date-time + 1 space + 64 char hash
sql
SELECTlength(sqlite_source_id());
→79
Deterministic: same value within a session
sql
SELECTsqlite_source_id() =sqlite_source_id();
→1
Combine version and source ID for full build identification
sql
SELECTsqlite_version(), sqlite_source_id();
→3.52.0 | 2026-03-06 16:01:44...
⚠Anti-Pattern— Parsing sqlite_source_id() output to extract the version number
sqlite_source_id() returns a string like '2024-01-01 00:00:00 abc123...' — a date plus a source hash. The format is not guaranteed to be stable across releases. Developers who split on spaces to grab the version number will get a date, not a version, and will break if the format ever changes.
✓ Instead: Use sqlite_version() to get the version string in the stable 'X.Y.Z' format, or check the sqlite_version pragma if you need a comparable integer.
While sqlite_version() gives the release version string like '3.52.0', sqlite_source_id() gives the SHA3-256 hash of the exact source tree used, which is useful for filing bug reports or verifying that a specific bug fix is included in the build.
example
SELECTsqlite_source_id(/* see description */);
→See examples above for real-world usage
◆SQLite Note
PostgreSQL uses version() to return a combined version and build info string, and has no separate source-ID function. SQLite keeps these separate: sqlite_version() for the human-readable release number and sqlite_source_id() for the exact source revision hash.
⚠Anti-Pattern— Lexicographically comparing sqlite_version() strings to check for a minimum version
sqlite_version() returns a string like '3.45.1'. Comparing it with > or < gives wrong results: '3.9.0' > '3.45.1' is true under string comparison because '9' > '4', meaning old versions appear newer than they are.
✓ Instead: Convert the version to a comparable integer by splitting on '.' and computing major*1000000 + minor*1000 + patch, or use sqlite_compileoption_used() to test for the specific feature you actually need.
Comparing version strings lexicographically only works reliably when all components have the same number of digits. For robust feature detection, use sqlite_compileoption_used() to check specific capabilities rather than comparing version numbers.
PostgreSQL uses version() which returns a longer string including the OS and compiler. SQLite's sqlite_version() returns only the version number (e.g. '3.52.0'). There is no PostgreSQL equivalent to SQLite's sqlite_source_id() for the exact source hash.
⚠Anti-Pattern— Using total_changes() as a cross-connection audit counter
total_changes() counts all INSERT, UPDATE, and DELETE operations accumulated since the current database connection was opened. It is per-connection: changes made by other connections to the same database file are not reflected. Using it as a global audit counter in a multi-connection or WAL-mode setup produces systematically wrong counts.
✓ Instead: For cross-connection change tracking, use a trigger that writes to an audit table, or use the SQLite update hook API in your driver to capture every change on the specific connection you care about.
Unlike changes() which excludes lower-level trigger changes, total_changes() counts every row modification since the connection opened, including those caused by triggers. Reset by opening a new connection.
→Cumulative row changes across both DML statements
◆SQLite Note
PostgreSQL has no equivalent cumulative change-count function accessible as a scalar SQL expression. The PostgreSQL C API provides PQcmdTuples() for the most recent command only. SQLite's total_changes() is useful for progress monitoring and auditing within a long-lived connection.
Returns a string indicating the storage class of expression X: one of 'null', 'integer', 'real', 'text', or 'blob'.
Signature
typeof(X)
Parameters
Parameter
Type
Description
X
any
The expression whose storage class is to be determined.
Examples
The null storage class
sql
SELECTtypeof(NULL);
→null
Integer literal
sql
SELECTtypeof(42);
→integer
Real (floating-point) literal
sql
SELECTtypeof(3.14);
→real
Text string
sql
SELECTtypeof('hello');
→text
Binary BLOB literal
sql
SELECTtypeof(x'DEADBEEF');
→blob
⚠Anti-Pattern— Using typeof() for type-checking in application logic instead of enforcing schema constraints
Calling typeof(column) = 'integer' in a WHERE clause or application layer to filter out wrongly typed values treats a schema enforcement problem as a query-time workaround. Because SQLite uses dynamic typing, any column can hold any type, so typeof() checks become necessary everywhere if schema constraints are missing.
✓ Instead: Define columns with appropriate type affinity and add CHECK constraints at the schema level to reject badly typed inserts at write time, removing the need for typeof() guards in every SELECT.
typeof() returns one of exactly five values: 'null', 'integer', 'real', 'text', or 'blob'. These are storage classes, not declared column types. Because SQLite uses dynamic typing, a column declared as TEXT can store integers, and typeof() will return 'integer' for those rows.
→Distribution of storage classes in a schema-less config table
◆SQLite Note
PostgreSQL uses pg_typeof(X) to return a data type name such as 'integer' or 'text'. SQLite's typeof() returns one of only five storage class names regardless of the column's declared type. This reflects SQLite's fundamental dynamic typing (type affinity) model, which is very different from PostgreSQL's strict static type system.
Returns a BLOB that is the decoding of hexadecimal string X, ignoring characters in Y if provided; returns NULL if X contains non-hex characters not in Y, or if hex digits are not paired.
Signatures
unhex(X)
unhex(X,Y)
Parameters
Parameter
Type
Description
X
TEXT
A hexadecimal string to decode into binary; digits may be upper or lower case.
Y
TEXT
Optional string of separator characters to ignore when present between hex digit pairs (e.g., '-' for UUID notation).
⚠Anti-Pattern— Passing unhex() output directly into a TEXT context expecting a readable string
unhex('48656C6C6F') returns the raw binary bytes for 'Hello', not the ASCII string 'Hello'. Inserting the result into a TEXT column or concatenating it with || can produce garbled output because SQLite will store the blob bytes rather than interpreting them as UTF-8 text.
✓ Instead: If you want to decode a hex-encoded UTF-8 string, cast the result explicitly: CAST(unhex(hex_value) AS TEXT), and verify the source hex truly encodes valid UTF-8 before inserting into a text column.
The two-argument form unhex(uuid_string, '-') is ideal for converting UUID strings to compact 16-byte BLOBs for storage. The Y argument lists separator characters to silently skip, so hyphens between UUID groups are ignored without any prior string manipulation.
example
SELECTunhex(/* see description */);
→See examples above for real-world usage
◆SQLite Note
PostgreSQL has no unhex() function; the equivalent is decode(string, 'hex') from the encode/decode functions, which returns BYTEA. SQLite's unhex() was added in version 3.41.0 (2023-02-21) and is therefore absent from older SQLite installations. The Y separator argument has no PostgreSQL equivalent.
Returns the numeric Unicode code point of the first character of string X.
Signature
unicode(X)
Parameters
Parameter
Type
Description
X
TEXT
A string whose first character's Unicode code point is to be returned.
Examples
ASCII code point for uppercase A
sql
SELECTunicode('A');
→65
Only the first character is examined
sql
SELECTunicode('Hello');
→72
Unicode code point for the heart symbol
sql
SELECTunicode('♥');
→9829
Code point for a 4-byte UTF-8 emoji
sql
SELECTunicode('😀');
→128512
Advance a character by incrementing its code point
sql
SELECTchar(unicode('a') +1);
→b
⚠Anti-Pattern— Using unicode() to validate that an entire string contains valid Unicode
unicode(X) returns the numeric code point of only the first character of X. Passing a multi-character string does not validate the rest of the characters — it silently ignores everything after the first code point. Developers sometimes use it as a Unicode validity check, but it only ever inspects one character.
✓ Instead: To check an entire string for valid Unicode content, use a regular expression check or application-layer validation; use unicode() only when you specifically need the integer code point of the first character.
unicode(X) and char(N) are inverses: unicode(char(N)) = N for any valid code point, and char(unicode(X)) = the first character of X. Use them together to perform arithmetic on characters.
→Unicode code point for each character in a text column
◆SQLite Note
PostgreSQL uses ascii(X) to return the code point of the first character (limited to Latin-1 range in older versions) and unicode is available as ascii() for multi-byte characters in modern versions. SQLite's unicode() works correctly with all Unicode code points including supplementary characters above U+FFFF.
Interprets backslash Unicode escape sequences in string X and returns a new string with those escapes converted into the actual Unicode characters they represent.
Signature
unistr(X)
Parameters
Parameter
Type
Description
X
TEXT
A string potentially containing backslash-encoded Unicode escapes (\XXXX, \+XXXXXX, \uXXXX, \UXXXXXXXX); \\ is a literal backslash.
Examples
\uXXXX escape for Unicode BMP character
sql
SELECTunistr('\u0041');
→A
Escape within a string
sql
SELECTunistr('Hello\u0020World');
→Hello World
\UXXXXXXXX for supplementary characters
sql
SELECTunistr('\U0001F600');
→😀
Double backslash produces a literal backslash
sql
SELECTunistr('back\\slash');
→back\slash
\XXXX form (4 hex digits without u prefix)
sql
SELECTunistr('\2665');
→♥
⚠Anti-Pattern— Passing raw user-supplied input directly to unistr() without sanitization
unistr() processes \uXXXX and \UXXXXXXXX Unicode escape sequences embedded in strings. If user-supplied input is passed directly to unistr() without sanitization, a malicious \u0000 null character or an invalid surrogate pair escape can produce unexpected behavior in downstream text handling or C string APIs.
✓ Instead: Validate and sanitize user input before passing it to unistr(), or use unistr() only on compile-time string literals and developer-controlled data, never on raw user input.
The SQLite CLI's .dump command uses unistr() to safely encode strings containing control characters (U+0001–U+001F) into portable SQL that can be re-executed without terminal rendering issues. unistr_quote() wraps this into a quoting function suitable for inclusion in SQL literals.
example
SELECTunistr(/* see description */);
→See examples above for real-world usage
◆SQLite Note
unistr() was added in SQLite 3.50.0 (2025-05-29) specifically to match the unistr() function in PostgreSQL, SQL Server, and Oracle. The PostgreSQL version accepts the same \uXXXX and \UXXXXXXXX escape sequences. Code using unistr() should be largely portable between SQLite 3.50.0+ and PostgreSQL.
Like quote(X) but also escapes control characters (U+0001–U+001F) and backslashes using JSON-style backslash escapes, wrapping the result in unistr(...) when escaping was needed.
Signature
unistr_quote(X)
Parameters
Parameter
Type
Description
X
any
The value to serialize as a safe SQL literal, with control characters additionally escaped.
Examples
Plain string: identical to quote()
sql
SELECTunistr_quote('hello');
→'hello'
Integer: identical to quote()
sql
SELECTunistr_quote(42);
→42
Newline (U+000A) is escaped as \n
sql
SELECTunistr_quote('line1'||char(10) ||'line2');
→unistr('line1\nline2')
Control character U+0001 is escaped
sql
SELECTunistr_quote(char(1));
→unistr('\u0001')
BLOB: identical to quote()
sql
SELECTunistr_quote(x'DEADBEEF');
→X'DEADBEEF'
⚠Anti-Pattern— Confusing unistr_quote() output with quote() output and mixing them in SQL
unistr_quote() escapes non-ASCII control characters as \uXXXX sequences suitable for round-tripping through unistr(), whereas quote() produces standard SQL single-quoted literals with escaped apostrophes. The two formats are not interchangeable: pasting unistr_quote() output into a SQL statement without wrapping it in unistr() will store the escape sequences as literal text.
✓ Instead: Use unistr_quote() only when you intend the value to be decoded by unistr() later; use quote() when you need a SQL-safe literal for dynamic statement construction.
unistr_quote() is preferred over quote() when generating SQL scripts that may be displayed on terminals that interpret ANSI escape codes, because embedded control characters (like ESC, U+001B) would otherwise be rendered as terminal commands. The .dump CLI command uses unistr_quote() for this reason.
example
SELECTunistr_quote(/* see description */);
→See examples above for real-world usage
◆SQLite Note
PostgreSQL has no unistr_quote() function. The closest PostgreSQL equivalent is quote_literal() combined with regexp_replace() to escape control characters. SQLite's unistr_quote() was added in 3.50.0 alongside unistr() to support safe serialization of strings containing control characters in .dump output.
⚠Anti-Pattern— Applying unlikely() to a condition that actually filters out the majority of rows
unlikely(X) is a hint that expression X is false for most rows. If you apply it to a condition that is actually true for the majority of rows, the query planner will choose a strategy optimized for a highly selective filter — such as an index scan — when a full table scan would actually be faster.
✓ Instead: Use unlikely() only after profiling confirms the condition is rarely true; for conditions that eliminate most rows, use likely() or no hint at all and let ANALYZE provide accurate statistics.
unlikely() is best applied to predicates that filter out rare exception rows — for example, error conditions, soft-deleted records, or administrative flags. It encourages the planner to scan first and filter later rather than seeking by index on a very selective predicate.
→Same rows — planner assumes errors are rare (6.25% probability)
◆SQLite Note
PostgreSQL has no unlikely() function. SQLite's unlikely() is a no-op at runtime with zero CPU overhead; it exists solely to pass a selectivity hint to the query planner during compilation.
⚠Anti-Pattern— Using zeroblob() to pre-allocate space and then immediately overwriting it in the same statement
The purpose of zeroblob(N) is to reserve a blob of N bytes in the database so that incremental I/O can fill it later via the sqlite3_blob_open / sqlite3_blob_write C API or the equivalent driver method. Writing zeroblob() and then immediately replacing the value in the same transaction negates the benefit and wastes write I/O.
✓ Instead: Use zeroblob(N) only when you plan to write the blob incrementally using the incremental blob I/O API; for blobs you already have in memory, insert the blob value directly in the INSERT statement.
SQLite stores zeroblob() data very compactly in the file format — it does not write N actual zero bytes. The zero bytes are materialized on read. The intended workflow is: INSERT with zeroblob(N) to reserve space, capture the rowid, then write content incrementally using sqlite3_blob_open() and sqlite3_blob_write() in the C API.
→Reserves a 1 MB placeholder BLOB efficiently (no 1 MB allocation in memory)
◆SQLite Note
PostgreSQL has no zeroblob() function. The PostgreSQL equivalent for pre-allocating large object space is the lo_create() / lo_open() large object API, which is fundamentally different. In PostgreSQL, BYTEA columns store actual bytes in-place. SQLite's zeroblob() is efficient precisely because of SQLite's on-disk format treating zero-byte BLOBs as a special case.
Returns the Unix timestamp (integer seconds since 1970-01-01 00:00:00 UTC) for the given datetime value. When called with no arguments, returns the current time as a Unix timestamp. Equivalent to CAST(strftime('%s', ...) AS INTEGER) but faster because it avoids string formatting. Modifiers such as 'localtime', 'utc', '+N days', 'start of month', etc. are supported. Added in SQLite 3.38.0.
Signatures
unixepoch() → INTEGER
unixepoch(time_value) → INTEGER
unixepoch(time_value, modifier, ...) → INTEGER
Parameters
Parameter
Type
Description
time_value
TEXT | INTEGER | REAL
Optional datetime value in any SQLite-accepted format. Omit to use current time.
modifier
TEXT
Optional time modifier(s) such as '+1 day', 'start of month', 'localtime', 'utc'.
Examples
Current UTC time as Unix timestamp
sql
SELECTunixepoch();
→1741910400
Convert a specific datetime string to Unix timestamp
sql
SELECTunixepoch('2024-01-15 12:00:00');
→1705320000
Use modifiers to offset the time value
sql
SELECTunixepoch('now', '-30 days');
→(current timestamp minus 30 days)
Start of the current day in UTC
sql
SELECTunixepoch('now', 'start of day');
→(Unix timestamp for midnight today UTC)
Same result; unixepoch is faster as it avoids string formatting
sql
-- Compare unixepoch vs strftime('%s',...)SELECTunixepoch('2024-06-01') ASfast,
CAST(strftime('%s','2024-06-01') ASINTEGER) ASslow;
→fast: 1717200000
slow: 1717200000
⚠Anti-Pattern— Using unixepoch() in SQLite Versions Before 3.38.0
unixepoch() was added in SQLite 3.38.0 (released 2023-02-22). Using it on an older SQLite build will fail with 'no such function: unixepoch'.
✓ Instead: Check sqlite_version() >= '3.38.0' before using unixepoch(). On older versions, use CAST(strftime('%s', time_value) AS INTEGER) as an equivalent fallback.
unixepoch() was added specifically as a faster alternative to CAST(strftime('%s', ...) AS INTEGER). For high-frequency timestamp operations or large-scale datetime comparisons, unixepoch() provides a measurable performance improvement and produces an integer directly without a string parsing step.
SQLite-specific function added as a faster alternative to CAST(strftime('%s',...) AS INTEGER). Not available in PostgreSQL; use EXTRACT(EPOCH FROM ...) instead.
Returns a string representing the duration between two datetime values in ISO 8601 duration format: 'PYYYYYMMDDTHHhMMmSS.SSSs'. The result is positive if datetime1 is later than datetime2, and negative if datetime1 is earlier. The components (years, months, days, hours, minutes, seconds) precisely represent the calendar difference. Added in SQLite 3.43.0.
Signature
timediff(datetime1, datetime2) → TEXT
Parameters
Parameter
Type
Description
datetime1
TEXT | INTEGER | REAL
The end datetime (minuend). Can be any SQLite datetime value or 'now'.
datetime2
TEXT | INTEGER | REAL
The start datetime (subtrahend). Can be any SQLite datetime value or 'now'.
Examples
2 months and 14 days from Jan 1 to Mar 15
sql
SELECTtimediff('2024-03-15', '2024-01-01');
→'+0000-02-14T00H00M00.000s'
Negative when datetime1 is earlier than datetime2
sql
SELECTtimediff('2024-01-01', '2024-03-15');
→'-0000-02-14T00H00M00.000s'
Age of an order: 1 month, 8 days, 14 hours, 22 minutes
Parse specific components from the duration string using substr
sql
-- Extract just the days component from a timediff resultSELECTCAST(substr(timediff('now', '2024-01-01'), 9, 2) ASINTEGER) ASday_component;
→(day component of the duration)
⚠Anti-Pattern— Parsing timediff() Output with Fixed Substring Positions
The ISO 8601 duration format produced by timediff() uses a fixed-width format for most components, but treating specific character positions as reliable offsets for all components is fragile and error-prone when the sign character changes or when values overflow a single digit in a way you do not anticipate.
✓ Instead: For simple elapsed-time calculations in seconds, use unixepoch(datetime1) - unixepoch(datetime2) instead — the integer difference is far easier to work with programmatically.
The string produced by timediff() can be passed directly as a modifier to datetime(), date(), or similar functions to add the duration to another datetime. This makes it possible to do relative date arithmetic using computed intervals.
SQLite-specific function added in 3.43.0. Returns ISO 8601 duration format. For elapsed seconds, unixepoch() subtraction is simpler. Not available in PostgreSQL; use AGE() or timestamp subtraction instead.
Returns the number of bytes (octets) in the string or BLOB value x. For TEXT values, this is the byte length of the UTF-8 encoding, which may be larger than the character count for strings containing multi-byte Unicode characters. For BLOB values, returns the blob size in bytes. Returns NULL for NULL input. Added in SQLite 3.43.0. Equivalent to length() for BLOB inputs, but differs from length() for multi-byte text.
Signature
octet_length(x) → INTEGER
Parameters
Parameter
Type
Description
x
TEXT | BLOB
The string or BLOB value to measure in bytes.
Examples
ASCII text: character count equals byte count
sql
SELECToctet_length('hello');
→5
UTF-8: 'é' encodes as 2 bytes, so 5 bytes for 4 characters
sql
SELECToctet_length('café');
→5
Each Japanese character is 3 bytes in UTF-8; length() returns character count
sql
SELECToctet_length('日本語'), length('日本語');
→9 | 3
BLOB value: 4 bytes
sql
SELECToctet_length(x'DEADBEEF');
→4
Enforce byte-level constraints on text columns
sql
-- Find rows where UTF-8 byte length exceeds a limit (e.g., for HTTP header constraints)SELECTid, name, octet_length(name) ASbyte_lenFROMusersWHEREoctet_length(name) >64;
→(users whose name exceeds 64 bytes in UTF-8)
⚠Anti-Pattern— Using length() to Check Byte Limits on Non-ASCII Text
length() returns the number of Unicode characters, not bytes. A string like '你好' has length() = 2 but octet_length() = 6. Using length() to enforce a byte budget for fields that accept non-ASCII input will allow strings that exceed the byte limit.
✓ Instead: Use octet_length() whenever you need to enforce byte-level storage constraints, such as fitting within a VARCHAR(n) byte limit or preparing data for a protocol with byte-length headers.
For data that will be stored in external systems with byte-level limits (HTTP headers, file names, database fields with byte-based size limits), octet_length() gives you the true storage cost in bytes. For pure character counting (e.g., tweet length), use length() instead.
→(products whose description exceeds 255 bytes when UTF-8 encoded)
◆SQLite Note
Added in SQLite 3.43.0. For BLOB, octet_length() and length() return the same value. For TEXT, octet_length() measures UTF-8 bytes while length() measures Unicode code points.
Concatenates two or more strings and returns the result as a single TEXT value. Unlike the || operator, concat() propagates NULL: if any argument is NULL, the return value is NULL. Accepts any number of arguments. Non-TEXT arguments are cast to TEXT before concatenation. Added in SQLite 3.44.0.
Signature
concat(string1, string2, ...) → TEXT
Parameters
Parameter
Type
Description
string1, string2, ...
TEXT
Two or more string values to concatenate. NULL input causes NULL output.
-- NULL propagation: concat returns NULL if any arg is NULLSELECTconcat('Hello', NULL, 'World');
→NULL
Both || and concat() propagate NULL; use concat_ws() to skip NULLs
sql
-- Contrast with || operator which also propagates NULLSELECT'Hello'||NULL||'World', -- NULLconcat('Hello', NULL, 'World'); -- NULL
→NULL | NULL
⚠Anti-Pattern— Expecting concat() to Skip NULLs Like COALESCE
Developers familiar with MySQL's CONCAT() function may expect SQLite's concat() to skip NULL arguments and concatenate only the non-NULL values. In SQLite, concat() propagates NULL: a single NULL argument makes the entire result NULL.
✓ Instead: Use concat_ws(separator, ...) to skip NULL arguments, or wrap nullable columns in COALESCE(col, '') to substitute empty strings before passing them to concat().
concat() propagates NULL like the || operator. When you are building strings from nullable columns (like first_name, middle_name, last_name where middle_name may be NULL), use concat_ws() instead — it skips NULL arguments and only includes non-NULL values separated by the delimiter.
Added in SQLite 3.44.0. Propagates NULL (unlike MySQL's CONCAT which skips NULLs). The || operator also propagates NULL. Use concat_ws() for NULL-skipping concatenation.
Concatenates two or more strings with a separator between each non-NULL value, skipping NULL arguments. The first argument is the separator. Unlike concat(), NULL values among the string arguments are silently skipped rather than causing the entire result to be NULL. Returns NULL only if the separator itself is NULL. Added in SQLite 3.44.0.
Signature
concat_ws(separator, string1, string2, ...) → TEXT
Parameters
Parameter
Type
Description
separator
TEXT
String placed between each non-NULL value. If NULL, the entire result is NULL.
string1, string2, ...
TEXT
Two or more values to concatenate. NULL values are skipped.
Examples
Comma-separated list from three values
sql
SELECTconcat_ws(', ', 'Alice', 'Bob', 'Carol');
→'Alice, Bob, Carol'
NULL middle_name is silently skipped; no double spaces
⚠Anti-Pattern— Using concat_ws() With a NULL Separator
Unlike the string arguments, a NULL separator is NOT skipped — it causes the entire result to be NULL. This is a common surprise when the separator itself comes from a column or variable that might be NULL.
✓ Instead: Ensure the separator is a non-NULL literal or wrap it in COALESCE(separator_col, '') before passing it to concat_ws().
concat_ws() is the cleanest way to build delimited strings from a mix of nullable columns. It avoids the boilerplate of wrapping every column in COALESCE() and produces clean output with no leading, trailing, or doubled separators when values are NULL.
→'Dr. Alice Smith' -- works even if title is NULL for some rows
◆SQLite Note
Added in SQLite 3.44.0. The separator (first argument) must not be NULL — a NULL separator returns NULL. String arguments that are NULL are skipped. Similar to MySQL/MariaDB concat_ws().