36. PostgreSQL: SQL functions
This chapter describes the operators and functions that a MIRAJ instance in PostgreSQL language makes available to queries: strings, pattern matching, numbers, dates and intervals, formatting (to_char, to_date, to_number), conditional expressions, aggregates (including statistical and ordered-set aggregates), window functions, JSON and JSONB (including SQL/JSON paths), conversions, bytes and hashing, system functions, arrays and set-returning functions. Every example was run on a real instance; the results shown are the ones it returned.
The chapter describes the PostgreSQL language as MIRAJ speaks it. Not all PostgreSQL functions are present and a few behave differently: section 36.16 lists them, with the SQLSTATE and the message received.
Table of contents#
- 36.0 General
- 36.1 Operators
- 36.2 String functions
- 36.3 Pattern matching
- 36.4 Numeric functions
- 36.5 Dates, times and intervals
- 36.6 Formatting:
to_char,to_date,to_timestamp - 36.7 Conditional expressions
- 36.8 Aggregate functions
- 36.9 Window functions
- 36.10 JSON and JSONB
- 36.11 Conversions
- 36.12 Bytes, hashing and encoding
- 36.13 System, session, sequences and series
- 36.14 Arrays and set-returning functions
- 36.15 MIRAJ language functions
- 36.16 Unsupported features and differences
36.0 General#
- Case-insensitive names.
upper,UPPERandUpperdesignate the same function. This chapter writes them in lowercase, like the PostgreSQL documentation. Thepg_catalog.prefix is accepted and ignored (pg_catalog.version()). NULLpropagation. Unless stated otherwise, a function that receives aNULLargument returnsNULL. The exceptions (coalesce,concat,concat_ws,format,quote_nullable,array_to_string...) are flagged in their description; aggregates ignoreNULLvalues (exceptarray_agg,json_aggandjsonb_agg).- Positions and lengths are counted in characters, starting at 1.
octet_lengthandbit_lengthcount bytes and bits of the UTF-8 encoding. - Byte-by-byte text comparisons. Like PostgreSQL under the "C" collation, texts are compared and sorted byte by byte:
'abc' = 'ABC'and'é' = 'e'are false,ORDER BYplaces uppercase letters before lowercase ones and accented letters afterz,'a'and'A'are two distinct values forDISTINCT,GROUP BYor aUNIQUEkey. To compare regardless of case and accents:ILIKE,~*,lower(x) = lower(y),unaccentor theCOLLATE utf8mb4_general_cicollation (see 36.3). - Unknown function: error
42883(FUNCTION … does not exist); wrong number of arguments:42883as well, with the PostgreSQL message (function btrim(unknown, unknown, unknown) does not exist). - Type of a result:
pg_typeof(expr)returns it. The tables in this chapter give the type announced to the client. - A computed column without an alias carries the text as written (
SELECT upper(nom) FROM clientnames the columnupper(nom), where PostgreSQL writesupper): give an alias (AS …) to columns that are read by name.
Example database#
The examples use the ventes database of the manual. The lines below give the results displayed in this chapter.
CREATE SCHEMA ventes;
SET search_path TO ventes;
CREATE TABLE client (
id serial PRIMARY KEY, nom varchar(80) NOT NULL, ville varchar(60),
email varchar(120), cree_le timestamp NOT NULL);
CREATE TABLE article (
id serial PRIMARY KEY, reference varchar(20) NOT NULL UNIQUE, designation varchar(120) NOT NULL,
prix numeric(10,2) NOT NULL, stock integer NOT NULL DEFAULT 0, attributs jsonb);
CREATE TABLE commande (
id serial PRIMARY KEY, client_id integer NOT NULL REFERENCES client (id),
date_commande date NOT NULL, statut varchar(20) NOT NULL DEFAULT 'en cours', livree_le timestamp);
CREATE TABLE ligne_commande (
commande_id integer NOT NULL REFERENCES commande (id), article_id integer NOT NULL REFERENCES article (id),
quantite integer NOT NULL, prix_unitaire numeric(10,2) NOT NULL, PRIMARY KEY (commande_id, article_id));
INSERT INTO client (nom, ville, email, cree_le) VALUES
('Dupont', 'Lyon', '[email protected]', '2026-01-15 09:30:00'),
('Martin', 'Paris', '[email protected]', '2026-02-03 14:05:00'),
('Bernard', 'Lyon', NULL, '2026-03-21 08:00:00'),
('Élodie Petit', NULL, '[email protected]', '2026-04-02 17:45:30');
INSERT INTO article (reference, designation, prix, stock, attributs) VALUES
('STY-001', 'Stylo bille bleu', 1.20, 500, '{"couleur": "bleu", "tags": ["bureau", "ecriture"], "dim": {"l": 14, "d": 0.8}}'),
('CAH-096', 'Cahier 96 pages', 2.50, 120, '{"couleur": "rouge", "tags": ["papier"], "pages": 96}'),
('CLA-A4', 'Classeur A4', 4.90, 35, '{"couleur": "noir", "tags": [], "recycle": true}'),
('AGR-024', 'Agrafeuse', 12.00, 0, NULL);
INSERT INTO commande (client_id, date_commande, statut, livree_le) VALUES
(1, '2026-09-28', 'livrée', '2026-10-01 10:15:00'), (1, '2026-10-05', 'en cours', NULL),
(2, '2026-10-06', 'livrée', '2026-10-08 16:40:00'), (3, '2026-10-09', 'annulée', NULL);
INSERT INTO ligne_commande VALUES
(1, 1, 100, 1.20), (1, 2, 10, 2.50), (2, 3, 4, 4.90), (3, 1, 50, 1.10), (3, 4, 2, 12.00), (4, 2, 5, 2.50);The column types are described in chapter 32. Data types, queries in chapter 35. SELECT queries.
36.1 Operators#
Arithmetic#
| Operator | Role | Example | Result |
|---|---|---|---|
+, -, * | addition, subtraction, multiplication | 7 * 2 | 14 |
/ | division; integer division (truncated toward zero) between two integers | 7 / 2, -7 / 2, 7 / 2.0 | 3, -3, 3.5000000000000000 |
DIV, div(a, b) | integer division (truncated toward zero) | 7 DIV 2, div(-17, 5) | 3, -3 |
%, mod(a, b) | remainder, with the sign of the dividend | 17 % 5, mod(-17, 5) | 2, -2 |
^, power(a, b), pow(a, b) | power | 2 ^ 10 | 1024 |
-x | negation | -(3 + 2) | -5 |
SELECT 7 / 2, -7 / 2, 7 / 2.0, 1.0 / 3, 7::float8 / 2, 7 % 2, 2 ^ 3;3 | -3 | 3.5000000000000000 | 0.33333333333333333333 | 3.5 | 1 | 8As in PostgreSQL, the division of two integers is an integer division: 7 / 2 is 3, of the operands' type. For a decimal quotient, make one operand decimal or floating-point: 7 / 2.0, 7::numeric / 2, 7::float8 / 2.
Result types:
| Operation | Type |
|---|---|
integer + - * / % integer | integer; smallint between two smallint, bigint as soon as one operand is a bigint |
with a numeric | numeric: scale of the operands for + - * (2.50 * 3 is 7.50, 1.10 * 1.1 is 1.210); for /, at least sixteen significant digits, never fewer decimals than an operand (7 / 2.0 is 3.5000000000000000, 1.0 / 3 is 0.33333333333333333333, 2.50 / 2 is 1.25000000000000000000) |
with a double precision or a real | double precision |
integer ^ integer, power(2, 10) | double precision; exact numeric to 16 digits as soon as one operand is a decimal (2 ^ 0.5 is 1.4142135623730950) |
A written integer that fits in 32 bits is an integer, a larger integer a bigint, a number with a decimal point or an exponent (1.5, 1e3) a numeric.
Calculation errors:
| Statement | Response |
|---|---|
SELECT 1 / 0, 5 % 0, 1.0 / 0.0, div(1, 0) | 22012: division by zero |
SELECT 2147483647 + 1 | 22003: integer out of range (convert one operand: 2147483647::bigint + 1 is 2147483648) |
SELECT 9223372036854775807 + 1 | 22003: BIGINT value is out of range in '9223372036854775807 + 1' |
SELECT power(0, -1) | 2201F: zero raised to a negative power is undefined |
SELECT power(-8, 0.5) | 2201F: a negative number raised to a non-integer power yields a complex result |
mod(a, 0) returns the same 22012 error as a % 0.
Comparison#
| Operator | Role |
|---|---|
=, <>, !=, <, <=, >, >= | comparison; NULL if an operand is NULL |
a BETWEEN x AND y, a NOT BETWEEN x AND y | closed range |
a IN (v1, v2, …), a NOT IN (…), a IN (SELECT …) | membership in a list or a subquery |
a IS NULL, a IS NOT NULL, a ISNULL, a NOTNULL | NULL test |
a IS DISTINCT FROM b, a IS NOT DISTINCT FROM b | comparison that treats NULL as a value |
b IS TRUE, IS NOT TRUE, IS FALSE, IS NOT FALSE, IS UNKNOWN | boolean test |
a = ANY (array), a = SOME (array), a <> ALL (array), a > ANY (array) | comparison with each element of an array (see 36.14) |
a = ANY (SELECT …), a < ALL (SELECT …), EXISTS (SELECT …) | comparison with a subquery |
(a, b) = (c, d), (a, b) < (c, d), (a, b) IN ((1, 'x'), (2, 'y')), ROW(a, b) = ROW(c, d) | row comparison |
SELECT 5 BETWEEN 1 AND 10, 5 IN (1, 5), 1 IS DISTINCT FROM NULL, NULL IS NOT DISTINCT FROM NULL,
1 = ANY (ARRAY[1, 2]), 3 <> ALL (ARRAY[1, 2]), (1, 2) < (1, 3);t | t | t | t | t | t | tSELECT x ISNULL, x NOTNULL, 'abc' ~~ 'a%', 'abc' ~~* 'A%', 'abc' !~~ 'b%' FROM (SELECT NULL::int AS x) t;t | f | t | t | t~~, ~~*, !~~ and !~~* are the operators of LIKE, ILIKE, NOT LIKE and NOT ILIKE (see 36.3).
A condition placed in the list of a SELECT is a boolean, returned as t or f. Placed in a calculation or passed to a function, it is the integer 1 or 0: (1 = 1) + 1 is 2. pg_typeof(1 = 1) returns boolean, and the condition converted to text is written true or false.
Logic#
AND, OR, NOT, with the standard three-valued logic:
SELECT true AND NULL, false AND NULL, true OR NULL, NOT true;NULL | f | t | f&&, || (as OR) and ! are not logical operators in PostgreSQL language: they are rejected (42601) or have another meaning (|| concatenates).
Concatenation#
a || b concatenates two values, converted to text if needed. The result is NULL if either one is NULL.
SELECT 'Stylo' || ' ' || 'bille', 'Total : ' || 12.5 || ' €', 'n=' || NULL, coalesce('n=' || NULL, 'sans');Stylo bille | Total : 12.5 € | NULL | sansA concatenated boolean is written true or false ('abc' || true is abctrue); the concat function writes it as t or f. Between two jsonb documents, || merges the documents (see 36.10); between two json values, it is rejected (42883).
Bitwise#
| Operator | Role | Example | Result |
|---|---|---|---|
a & b | AND | 5 & 3 | 1 |
a | b | OR | 5 | 3 | 7 |
a # b | exclusive OR | 5 # 3 | 6 |
~a | complement | ~5 | -6 |
a << n, a >> n | shifts | 1 << 4, -8 >> 1 | 16, -4 |
Integers are signed. bit_count(n) counts the bits set to 1 (bit_count(7) is 3).
Bit strings#
On bit strings (bit(n), bit varying(n), literals B'101' and X'1F', see 32. Data types), the same operators work position by position and return a bit string:
| Operator or function | Role | Example | Result |
|---|---|---|---|
a & b, a | b, a # b | AND, OR, exclusive OR of two strings of the same length | B'101' & B'110' | 100 (bit) |
~a | complement, over the positions of the string | ~B'101' | 010 |
a << n, a >> n | shifts, length kept, incoming zeros | B'10001' >> 2 | 00100 |
a || b | concatenation | B'101' || B'11' | 10111 (bit varying) |
length(a), bit_length(a) | number of positions | length(B'10111') | 5 |
octet_length(a) | number of bytes | octet_length(B'101100111') | 2 |
bit_count(a) | number of bits set to 1 | bit_count(B'1101') | 3 |
get_bit(a, n) | position n, counted from 0 from the left | get_bit(B'101', 1) | 0 |
set_bit(a, n, v) | string whose position n is v (0 or 1) | set_bit(B'101', 1, 1) | 111 |
substring(a from d for n) | extracted positions | substring(B'110010111111' from 3 for 2) | 00 |
position(s in a) | rank of the substring | position(B'010' in B'000001101011') | 8 |
overlay(a placing s from d for n) | replaced positions | overlay(B'0101' placing B'11' from 2 for 2) | 0111 |
&, | and # require two strings of the same length (22026: cannot AND bit strings of different sizes). Next to a bit string, a written text is read as a bit string (b & '110'); a number is rejected (42883: operator does not exist: bit & integer), as is an arithmetic operator (b + 1). get_bit outside the string: 2202E (bit index 3 out of valid range (0..2)). get_bit and set_bit exist only for a bit string.
Precedence#
From highest to lowest: ::; unary -; ^; *, /, %; +, -; ||; comparisons; NOT; AND; OR. ^ is left-associative (2 ^ 3 ^ 2 is 64). Since + comes before ||, 1 + 2 || 'a' is 3a and 'a' || 1 + 2 is a3.
Since :: comes before the unary minus, -3.7::int reads as -(3.7::int) and is -4, like (-3.7)::int.
Two intervals in a row (d + INTERVAL '1 month' - INTERVAL '1 day') read without parentheses; INTERVAL '1 hour' * 2 multiplies the interval.
36.2 String functions#
Summary:
| Function | Role | Type |
|---|---|---|
a || b, concat, concat_ws | concatenation | text |
upper, lower, initcap | case | text |
length, char_length, character_length | length in characters | integer |
octet_length, bit_length | length in bytes, in bits | integer |
trim, btrim, ltrim, rtrim | removal of characters (or bytes) at the ends | text, bytea |
substring, substr | substring | text |
left, right | start, end | text |
position, strpos | position of a substring | integer |
starts_with | prefix | boolean |
replace, translate, overlay | replacement | text |
lpad, rpad, repeat, reverse | padding, repetition, reversal | text |
split_part | nth field of a split string | text |
ascii, chr | code of a character, character of a code | integer, text |
format | template with %s, %I, %L | text |
quote_ident, quote_literal, quote_nullable | quoting for built SQL | text |
normalize, unistr | Unicode normal form, Unicode escapes | text |
parse_ident | splitting of a qualified name | text[] |
levenshtein, soundex, difference, similarity, unaccent | distance, phonetic code, similarity, accent removal | integer, text, real |
md5, sha256, to_hex, encode, decode, convert_to, convert_from | hashing, encoding | see 36.12 |
regexp_replace, regexp_match and regular expressions | patterns | see 36.3 |
to_char, to_number | formatting and reading of a number or a date | see 36.6 |
string_agg | concatenation of a group | see 36.8 |
lower(1) and upper(1.5) are rejected (42883: function lower(integer) does not exist): these two functions exist only for text. The others convert their argument to text (length(12) is 2).
concat and concat_ws#
concat(value1, value2, ...)
concat_ws(separator, value1, value2, ...)concat puts its arguments end to end, converted to text; NULL arguments are ignored (concat('a', NULL, 'b') is ab, concat(NULL) an empty string), whereas 'a' || NULL is NULL.
concat_ws inserts separator between the non-NULL arguments; NULL values are ignored. A NULL separator returns NULL.
A boolean is written there as t or f, as in PostgreSQL: concat('a', true) is at, concat_ws(',', true, 1 = 2) is t,f (whereas 'a' || true is atrue). The distinction is kept in the definition of a view, a generated column or a default value.
SELECT concat(nom, ' (', ville, ')') AS a,
nom || ' (' || coalesce(ville, '?') || ')' AS b,
concat_ws(' - ', nom, ville, email) AS c
FROM client ORDER BY id; a | b | c
-----------------+------------------+------------------------------------
Dupont (Lyon) | Dupont (Lyon) | Dupont - Lyon - [email protected]
Martin (Paris) | Martin (Paris) | Martin - Paris - [email protected]
Bernard (Lyon) | Bernard (Lyon) | Bernard - Lyon
Élodie Petit () | Élodie Petit (?) | Élodie Petit - [email protected]upper, lower, initcap#
upper(string) lower(string) initcap(string)Uppercase, lowercase (Unicode rules), and first letter of each word in uppercase, the others in lowercase.
SELECT upper('café'), lower('CAFÉ'), initcap('jean-pierre de la fontaine');CAFÉ | café | Jean-Pierre De La FontaineLengths#
length(string) char_length(string) character_length(string)
octet_length(string) bit_length(string)length, char_length and character_length count characters; octet_length counts the bytes of the UTF-8 encoding; bit_length eight times more. On a bytea, length and octet_length count bytes.
SELECT length('café'), char_length('café'), octet_length('café'), bit_length('café'), length('abc'::bytea);4 | 4 | 5 | 40 | 3trim, btrim, ltrim, rtrim#
trim([BOTH | LEADING | TRAILING] [characters] FROM string)
trim(string)
btrim(string [, characters]) ltrim(string [, characters]) rtrim(string [, characters])Remove characters at the start (LEADING, ltrim), at the end (TRAILING, rtrim) or on both sides (BOTH, btrim, the default of trim). Without characters, spaces are removed.
SELECT '[' || trim(' x ') || ']', '[' || ltrim(' x ') || ']', '[' || rtrim(' x ') || ']';
SELECT trim(BOTH 'x' FROM 'xxaxx'), trim(LEADING 'x' FROM 'xxaxx'), trim(TRAILING 'x' FROM 'xxaxx');
SELECT btrim('yxaxyx', 'xy'), ltrim('yxaxx', 'xy'), rtrim('axyyx', 'xy');[x] | [x ] | [ x]
a | axx | xxa
a | axx | acharacters is read as a set of characters, as in PostgreSQL, in all forms: trim(BOTH 'ab' FROM 'baxab') and btrim('baxab', 'ab') are x. trim is also written with a list (trim(BOTH FROM string, characters), trim(LEADING string, characters)) and is kept as btrim / ltrim / rtrim in the definition of a view or a generated column.
These functions exist only for text and for bytea:
- an argument of another type (number, boolean, date, time, interval,
uuid,json,jsonb) is rejected:trim(42)returns42883(function btrim(integer) does not exist); convert it (trim(n::text)); - on a
bytea, it is bytes that are removed and the result is abytea:btrim(bytes, bytes),ltrim,rtrim,trim([BOTH | LEADING | TRAILING] bytes FROM bytes). An untyped literal is read asbyteathere ('\x00'). The bytes to remove are mandatory (btrim(bytes):42883), and a text does not mix with bytes (btrim(bytes, text_column):42883); - more than two arguments:
42883(function btrim(unknown, unknown, unknown) does not exist).
SELECT btrim('\x0000ff1000'::bytea, '\x00'), trim(LEADING '\x00' FROM '\x0000ff1000'::bytea), rtrim('\x0000ff1000'::bytea, '\x00');\xff10 | \xff1000 | \x0000ff10substring and substr#
substring(string FROM start [FOR length])
substring(string FOR length)
substring(string, start [, length])
substr(string, start [, length])
substring(string FROM pattern)Substring from character start (1 for the first), over length characters or to the end. With a text instead of start, substring(string FROM pattern) reads pattern as a regular expression (see 36.3) and returns the first match, or the text of the first captured group if the pattern has one; NULL if there is no match.
SELECT substring('abcdef' FROM 2 FOR 3), substring('abcdef' FROM 3), substring('abcdef' FOR 2), substr('abcdef', 4);
SELECT substring(email FROM position('@' IN email) + 1) FROM client WHERE id = 1;bcd | cdef | ab | def
exemple.frSELECT substring('abc123def' FROM '[0-9]+'), substring('abc123def' FROM 'c(\d)'), substring('abc' FROM 'z');123 | 1 | NULLA negative start counts from the end (substring('abcdef', -2) is ef), and a zero start returns an empty string: PostgreSQL counts these positions outside the string differently.
left and right#
left(string, n) right(string, n)The first or last n characters; with a negative n, everything except the last |n| (left) or first |n| (right) characters.
SELECT left('abcdef', 2), right('abcdef', 2), left('abcdef', -2), right('abcdef', -2);ab | ef | abcd | cdefposition and strpos#
position(substring IN string) strpos(string, substring) starts_with(string, prefix)Position of the first occurrence, 0 if it is absent. starts_with returns a boolean.
SELECT position('cd' IN 'abcdef'), strpos('abcdef', 'cd'), position('z' IN 'abc'), starts_with('abc', 'ab');3 | 3 | 0 | treplace, translate, overlay#
replace(string, search, replacement)
translate(string, from, to)
overlay(string PLACING new FROM start [FOR length])replacereplaces all occurrences ofsearch.translatereplaces each character offromwith the character of the same rank into; a character offromwith no counterpart is removed.overlayreplaceslengthcharacters (the length ofnewby default) starting atstart.
SELECT replace('aXbXc', 'X', '-'), translate('12345', '143', 'ax'),
overlay('abcdef' PLACING 'ZZ' FROM 2 FOR 3), overlay('abcdef' PLACING 'ZZ' FROM 2);a-b-c | a2x5 | aZZef | aZZdeflpad, rpad, repeat, reverse#
lpad(string, length [, fill]) rpad(string, length [, fill])
repeat(string, n) reverse(string)lpad and rpad pad on the left or on the right up to length characters (with spaces by default) and truncate a longer string.
SELECT lpad('7', 3, '0'), rpad('7', 3, '*'), lpad('abcdef', 3), '[' || lpad('ab', 5) || ']', repeat('ab', 3), reverse('abc');
SELECT reference, lpad(id::text, 6, '0') AS numero, rpad(designation, 20, '.') || to_char(prix, '990.00') AS ligne
FROM article ORDER BY id;007 | 7** | abc | [ ab] | ababab | cba
reference | numero | ligne
-----------+--------+-----------------------------
STY-001 | 000001 | Stylo bille bleu.... 1.20
CAH-096 | 000002 | Cahier 96 pages..... 2.50
CLA-A4 | 000003 | Classeur A4......... 4.90
AGR-024 | 000004 | Agrafeuse........... 12.00split_part#
split_part(string, separator, n)The nth field of string split by separator; a negative n counts from the end; empty string if the field does not exist. Zero n: 22023 (field position must not be zero).
SELECT split_part('a,b,c', ',', 2), split_part('a,b,c', ',', 5), split_part('a,b,c', ',', -1);
SELECT reference, split_part(reference, '-', 1) AS famille FROM article WHERE id = 1;
SELECT split_part(email, '@', 2) AS domaine FROM client WHERE id = 1;b | | c
STY-001 | STY
exemple.frascii and chr#
ascii(string) chr(code)chr returns the character of Unicode code point code (chr(0): 54000, null character not permitted). ascii returns the Unicode code point of the first character (ascii('é') is 233), 0 for an empty string.
SELECT ascii('A'), ascii('é'), ascii('€uro'), chr(66), chr(233), chr(8364);65 | 233 | 8364 | B | é | €format#
format(template, value1, value2, ...)Replaces each marker in template with the next argument:
| Marker | Writes |
|---|---|
%s | the value as text; nothing for NULL |
%I | the value as an SQL identifier, in double quotes if needed; NULL: 22004 |
%L | the value as an SQL literal in single quotes; NULL is written NULL |
%% | the % character |
%2$s | the argument of the given rank |
%10s, %-10s, %*s | minimum width, right-aligned, left-aligned, or read from the previous argument |
SELECT format('%s : %s €', designation, prix) FROM article WHERE id = 1;
SELECT format('%I = %L', 'nom', nom), format('%2$s %1$s', 'a', 'b'), format('[%5s|%-5s]', id, id) FROM client WHERE id = 1;Stylo bille bleu : 1.20 €
nom = 'Dupont' | b a | [ 1|1 ]Too few arguments: 22023 (too few arguments for format()). Another marker (%d): 22023 (unrecognized format() type specifier).
quote_ident, quote_literal, quote_nullable#
quote_ident(text) quote_literal(value) quote_nullable(value)Quote a value to insert it into a built SQL statement: identifier in double quotes if needed, literal in single quotes (single quotes doubled). quote_literal(NULL) returns NULL, quote_nullable(NULL) returns the text NULL.
SELECT quote_ident('Mon Id'), quote_ident('abc'), quote_literal('it''s'), quote_literal(42), quote_nullable(NULL);"Mon Id" | abc | 'it''s' | '42' | NULLnormalize and unistr#
normalize(string [, NFC | NFD | NFKC | NFKD])
unistr(string)normalize returns the requested Unicode normal form (NFC by default): NFD separates letters from their accents, NFKC and NFKD also replace compatibility characters (ligatures...). The form is written as a keyword, without quotes. unistr replaces the Unicode escapes written in the text: \XXXX and \uXXXX (4 hexadecimal digits), \+XXXXXX and \UXXXXXXXX (6 and 8 digits); an invalid escape is rejected (22025: invalid Unicode escape).
SELECT length('é'), length(normalize('é', NFD)), normalize('fi', NFKC), unistr('caf\00e9 \+01F600');1 | 2 | fi | café 😀parse_ident#
parse_ident(qualified_name [, strict])Splits a qualified name into its elements, returned as an array of texts (see 36.14). An unquoted element is lowercased, a quoted element is kept as is. With strict set to false, whatever follows the name is ignored; otherwise, a malformed name is rejected (22023: string is not a valid identifier: "a..b").
SELECT parse_ident('ventes."Mon Client"'), parse_ident('Ventes.Client'), parse_ident('ventes.client()', false);{ventes,"Mon Client"} | {ventes,client} | {ventes,client}Approximate comparison: levenshtein, soundex, similarity, unaccent#
Functions of common PostgreSQL extensions (fuzzystrmatch, pg_trgm, unaccent), present without installation (CREATE EXTENSION is accepted and has no effect):
| Function | Returns | Type |
|---|---|---|
levenshtein(a, b [, insertion, deletion, substitution]) | minimum number of edits to turn a into b, with the given costs (1 by default) | integer |
levenshtein_less_equal(a, b [, costs…], max) | the same distance if it does not exceed max; otherwise a number greater than max | integer |
soundex(text) | phonetic code (a letter and three digits) | text |
difference(a, b) | number of characters shared by the two soundex codes, from 0 to 4 | integer |
similarity(a, b) | trigram similarity, from 0 to 1 | real |
unaccent([dictionary,] text) | the text without its accents or ligatures | text |
SELECT levenshtein('kitten', 'sitting'), levenshtein('kitten', 'sitting', 1, 1, 2), levenshtein_less_equal('kitten', 'sitting', 2),
soundex('Robert'), soundex('Rupert'), difference('Robert', 'Rupert'), similarity('word', 'words'), unaccent('Élodie à Noël');
SELECT nom FROM client WHERE levenshtein(lower(nom), 'dupond') <= 1;
SELECT nom FROM client WHERE unaccent(lower(nom)) LIKE 'elo%';3 | 5 | 3 | R163 | R163 | 4 | 0.5714286 | Elodie a Noel
Dupont
Élodie Petit36.3 Pattern matching#
LIKE and ILIKE#
string [NOT] LIKE pattern [ESCAPE character]
string [NOT] ILIKE pattern
string ~~ pattern string ~~* pattern string !~~ pattern string !~~* pattern% stands for any sequence of characters, _ for one character. The backslash \ (or the character given by ESCAPE) removes the special meaning of % and _.
SELECT 'abc' LIKE 'a_c', 'a%b' LIKE 'a\%b', 'a%b' LIKE 'a#%b' ESCAPE '#', 'axb' LIKE 'a\%b';
SELECT nom FROM client WHERE nom ILIKE 'd%';t | t | t | f
DupontLIKE is case- and accent-sensitive, like =: 'ABC' LIKE 'abc' and 'é' LIKE 'e' are false; ILIKE ignores both. The names of the catalog tables (pg_class.relname, information_schema.tables.table_name...) are an exception: like any MIRAJ name, they are compared regardless of case (relname ~ '^(CLIENT)$' finds client). To compare regardless of case and accents, apply the utf8mb4_general_ci collation to the string; COLLATE "C", "POSIX" and ucs_basic compare byte by byte, like the default collation:
SELECT 'abc' LIKE 'A%', 'abc' ILIKE 'A%', 'abc' = 'ABC', 'abc' COLLATE utf8mb4_general_ci = 'ABC', 'é' COLLATE utf8mb4_general_ci = 'e', 'abc' COLLATE "C" = 'ABC';
SELECT nom FROM client WHERE nom COLLATE utf8mb4_general_ci = 'dupont';f | t | f | t | t | f
DupontRegular expressions: ~, ~*, !~, !~*#
| Operator | Meaning |
|---|---|
string ~ pattern | the string contains a match of the pattern |
string ~* pattern | same, ignoring case and accents |
string !~ pattern, string !~* pattern | no match |
SELECT 'a1' ~ '^[a-z]\d$', 'a b' ~ '\s', 'abc' ~ 'a|z', 'aaa' ~ '^a{3}$', 'abc' ~* 'B', 'abc' !~ 'z';
SELECT nom FROM client WHERE nom ~ '^[A-D]' ORDER BY nom;t | t | t | t | t | t
Bernard
DupontThe pattern is written in a standard string, where \ is an ordinary character: '\d' is enough. ~ is case-sensitive ('abc' ~ 'B' is false), ~* ignores case. An invalid pattern ('abc' ~ '(') is a 2201B error (invalid regular expression: error: unclosed group), as for the regexp_… functions.
regexp_replace#
regexp_replace(string, pattern, replacement [, start [, n]] [, flags])Replaces the first match of the pattern; all of them with the g flag; the nth with n (n = 0: all), searching from character start. In replacement, \1 to \9 reuse the captured groups and \& the whole match. Like ~, regexp_replace is case-sensitive, except with the i flag.
SELECT regexp_replace('abc123', '[0-9]+', '#'), regexp_replace('aaa', 'a', 'b', 'g'),
regexp_replace('Hello', 'L', 'x'), regexp_replace('Hello', 'L', 'x', 'i'),
regexp_replace('2026-10-11', '(\d+)-(\d+)-(\d+)', '\3/\2/\1'),
regexp_replace('a1b2c3', '[0-9]', '#', 1, 2),
regexp_replace(' a b ', '\s+', ' ', 'g');abc# | bbb | Hello | Hexlo | 11/10/2026 | a1b#c3 | a b Invalid pattern: 2201B (invalid regular expression: error: unclosed group).
SELECT reference, regexp_replace(reference, '^([A-Z]+)-0*', '\1:') AS court FROM article ORDER BY id; reference | court
-----------+--------
STY-001 | STY:1
CAH-096 | CAH:96
CLA-A4 | CLA:A4
AGR-024 | AGR:24Flags#
The regexp_… functions accept a string of flags:
| Flag | Effect |
|---|---|
i | case ignored |
c | case respected (default) |
g | all matches: regexp_replace, regexp_matches only (regexp_match rejects it: 22023, regexp_match() does not support the "global" option) |
n | . does not match the newline, ^ and $ match the starts and ends of lines |
x | extended syntax: whitespace in the pattern is ignored |
Another flag is rejected (22023: invalid regular expression option: "q"), as is a start lower than 1 (22023: invalid value for parameter "start": 0).
regexp_match and regexp_matches#
regexp_match(string, pattern [, flags])
regexp_matches(string, pattern [, flags])regexp_match returns the first match as an array of texts (see 36.14): one element per captured group, or the whole match if the pattern has no group; NULL if there is none. An element is read by its rank: (regexp_match(…))[2].
regexp_matches is a set-returning function: it returns one row per match (all of them with the g flag, the first one otherwise), no row if there is none. It is used in a FROM or in the SELECT list.
SELECT regexp_match('Commande 2026-10-11', '(\d+)-(\d+)-(\d+)'), regexp_match('abc', 'z'), (regexp_match('ref STY-001', '([A-Z]+)-(\d+)'))[2];
SELECT regexp_matches('ab12cd34', '([a-z]+)(\d+)', 'g');{2026,10,11} | NULL | 001
{ab,12}
{cd,34}regexp_split_to_array, regexp_split_to_table, regexp_count#
regexp_split_to_array(string, pattern [, flags])
regexp_split_to_table(string, pattern [, flags])
regexp_count(string, pattern [, start [, flags]])regexp_split_to_array splits the string at each match of the pattern and returns the array of pieces; regexp_split_to_table returns the same pieces as rows (set-returning function, WITH ORDINALITY numbers the rows). regexp_count counts the matches, starting from character start.
SELECT regexp_split_to_array('a, b,c', ',\s*'), regexp_count('a1b22c333', '\d+'), regexp_count('AbAb', 'a', 1, 'i');
SELECT * FROM regexp_split_to_table('Stylo bille bleu', '\s+') WITH ORDINALITY AS t(mot, rang);{a,b,c} | 3 | 2
Stylo | 1
bille | 2
bleu | 3regexp_substr, regexp_instr, regexp_like#
regexp_substr(string, pattern [, start [, n [, flags [, group]]]])
regexp_instr(string, pattern [, start [, n [, end [, flags [, group]]]]])
regexp_like(string, pattern [, flags])regexp_substrreturns thenth match (the first by default) found from characterstart, or the text of the captured group of rankgroup;NULLif there is none;regexp_instrreturns the position of this match (withendset to 1, the position that follows it), 0 if there is none;regexp_likereturns a boolean; like~, it is case-sensitive, except with theiflag.
SELECT regexp_substr('a1b22c333', '\d+', 1, 3), regexp_substr('STY-001', '([A-Z]+)-(\d+)', 1, 1, '', 2),
regexp_instr('a1b22c333', '\d+', 1, 2), regexp_instr('a1b22c333', '\d+', 1, 2, 1),
regexp_like('abc', 'B'), regexp_like('abc', 'B', 'i');333 | 001 | 4 | 6 | f | t36.4 Numeric functions#
Summary:
| Function | Role | Result type |
|---|---|---|
abs(x) | absolute value | that of x |
sign(x) | -1, 0 or 1 | integer |
round(x), round(x, n) | rounding to the integer, to n decimals | numeric (double precision for a float) |
trunc(x), trunc(x, n) | truncation toward zero | numeric |
ceil(x), ceiling(x), floor(x) | next integer up, down | integer for a decimal (double precision for a float) |
mod(a, b), a % b | remainder of the division | integer or numeric |
div(a, b), a DIV b | integer quotient | numeric; integer for DIV |
power(a, b), pow(a, b), a ^ b | power | double precision or numeric (see 36.1) |
sqrt(x), cbrt(x), exp(x), ln(x), log(x), log10(x), log2(x), log(b, x) | square and cube roots, exponential, logarithms | double precision |
pi(), degrees(x), radians(x) | π, angle conversions | double precision |
sin, cos, tan, cot, asin, acos, atan, atan2(y, x) | trigonometry, in radians | double precision |
gcd(a, b), lcm(a, b) | GCD, LCM | integer |
factorial(n) | factorial | numeric |
width_bucket(x, low, high, n) | rank of the bucket that contains x | integer |
scale(x), min_scale(x), trim_scale(x) | scale of a numeric, scale without trailing zeros, value without trailing zeros | integer, integer, numeric |
greatest(a, b, …), least(a, b, …) | largest, smallest value | common type |
random(), random_normal([mean [, std_dev]]) | random number in [0, 1), following a normal distribution | double precision |
setseed(x) | seeds the session's random generator (x from -1 to 1) |
abs, sign#
SELECT abs(-12.50), abs(-3), sign(-12.5), sign(0), sign(7);12.50 | 3 | -1 | 0 | 1round, trunc, ceil, floor#
round(x [, n]) trunc(x [, n]) ceil(x) ceiling(x) floor(x)round rounds a numeric to the nearest value, halves moving away from zero; a negative n rounds to the left of the decimal point. A double precision without n is rounded to the nearest even integer, as in PostgreSQL.
SELECT round(2.5), round(-2.5), round(2.567, 2), round(1250, -2), round(2.5::float8), round(3.5::float8);
SELECT trunc(2.567, 2), trunc(-2.567), trunc(1299, -2), ceil(2.1), ceil(-2.1), floor(2.9), floor(-2.1);3 | -3 | 2.57 | 1300 | 2 | 4
2.56 | -2 | 1200 | 3 | -2 | 2 | -3Example on the database: total amount, total increased by 20% and average price of the order lines.
SELECT sum(quantite * prix_unitaire) AS total,
round(sum(quantite * prix_unitaire) * 1.2, 2) AS ttc,
round(avg(prix_unitaire), 2) AS prix_moyen
FROM ligne_commande; total | ttc | prix_moyen
--------+--------+------------
256.10 | 307.32 | 4.03mod, div#
SELECT mod(17, 5), mod(-17, 5), mod(17.5, 5), 17 % 5, div(17, 5), div(-17, 5), 17 DIV 5;2 | -2 | 2.5 | 2 | 3 | -3 | 3power, sqrt, exp, logarithms#
SELECT power(2, 10), power(2.5, 2), 10 ^ -2, sqrt(2), exp(1), ln(exp(1)), log(1000), log(2, 1024), log10(1000), log2(1024);1024 | 6.2500000000000000 | 0.01 | 1.4142135623730951 | 2.718281828459045 | 1 | 3 | 10 | 3 | 10SELECT cbrt(27), cbrt(-8), scale(1.230), min_scale(1.230), trim_scale(1.230), pow(2, 3);3 | -2 | 3 | 2 | 1.23 | 8log(x) is the decimal logarithm, ln(x) the natural logarithm, log(b, x) the logarithm to base b. Outside their domain, sqrt(-1), ln(0), log(-1), asin(2) and acos(2) return NULL (PostgreSQL raises an error).
Trigonometry#
Angles in radians.
SELECT pi(), degrees(pi()), radians(180), sin(pi() / 2), cos(0), atan2(1, 1);3.141592653589793 | 180 | 3.141592653589793 | 1 | 1 | 0.7853981633974483gcd, lcm, factorial, width_bucket#
SELECT gcd(12, 18), lcm(4, 6), factorial(5), factorial(20);
SELECT width_bucket(5.5, 0, 10, 5), width_bucket(-1, 0, 10, 5), width_bucket(10, 0, 10, 5);6 | 12 | 120 | 2432902008176640000
3 | 0 | 6width_bucket(x, low, high, n) splits [low, high) into n equal buckets and returns the rank of the one that contains x: 0 below low, n + 1 from high on. factorial(-1): 2201E (factorial of a negative number is undefined).
greatest, least#
greatest(value1, value2, ...) least(value1, value2, ...)Largest and smallest of the values (numbers, texts, dates). A NULL argument returns NULL (PostgreSQL ignores NULL values).
SELECT greatest(3, 7, 5), least(3, 7, 5), greatest('abc', 'abd'), least(date '2026-01-01', date '2025-01-01'), greatest(1, NULL);7 | 3 | abd | 2025-01-01 | NULLrandom#
random() returns a random double precision, greater than or equal to 0 and less than 1. For an integer from 0 to 9: floor(random() * 10)::int. random_normal(mean, std_dev) draws a number from a normal distribution (0 and 1 by default). setseed(x) reseeds the session's generator: the draws that follow are then reproducible. gen_random_uuid() is described in 36.13.
SELECT round(random_normal(100, 0)), random_normal() IS NOT NULL;100 | tOutside [-1, 1], setseed is rejected (22023: setseed parameter 2 is out of allowed range [-1,1]).
36.5 Dates, times and intervals#
The date, time, timestamp (without time zone) and timestamptz (an instant, displayed in the session's time zone) types are described in chapter 32. Data types; SET TIME ZONE in chapter 37. Transactions and session. The examples in this section that display a timestamptz were run after:
SET TIME ZONE 'Europe/Paris';Current date and time#
| Expression | Returns | Type |
|---|---|---|
now(), current_timestamp, transaction_timestamp() | date and time of the start of the transaction (of the statement, outside a block) | timestamptz |
statement_timestamp(), clock_timestamp() | date and time of the start of the statement; of the clock at each call | timestamptz |
current_timestamp(n) | same, with n decimals of a second | timestamptz |
localtimestamp, localtimestamp(n) | current date and time of the session, without time zone | timestamp |
current_date | today's date | date |
current_time, current_time(n) | current time, without offset | time |
localtime, localtime(n) | current time of the session | time |
timeofday() | date and time of the clock, as text | text |
SELECT now(), localtimestamp, current_date, current_time;2026-10-11 21:36:38+02 | 2026-10-11 21:36:38 | 2026-10-11 | 21:36:38Literals#
SELECT date '2026-10-11', time '14:30:00', timestamp '2026-10-11 14:30:00',
timestamptz '2026-10-11 14:30:00+02', '2026-10-11'::date, '2026-10-11T12:30:00Z'::timestamptz;2026-10-11 | 14:30:00 | 2026-10-11 14:30:00 | 2026-10-11 14:30:00+02 | 2026-10-11 | 2026-10-11 14:30:00+02Dates are written in ISO format (YYYY-MM-DD). The words 'today', 'tomorrow', 'yesterday', 'now' and 'epoch' are read as in PostgreSQL. A text that is not a valid date is rejected ('2026-02-30'::date, '11/10/2026'::date: 22008, date/time field value out of range: "2026-02-30"): to read another format, use to_date (36.6).
SELECT 'today'::date = current_date, 'tomorrow'::date - 'yesterday'::date, 'epoch'::timestamp;t | 2 | 1970-01-01 00:00:00Arithmetic#
| Operation | Result | Example | Value |
|---|---|---|---|
date + integer, date - integer | date | date '2026-10-11' + 7 | 2026-10-18 |
date - date | number of days (integer) | date '2026-12-25' - date '2026-10-11' | 75 |
date + time | timestamp | date '2026-10-11' + time '14:30' | 2026-10-11 14:30:00 |
date ± interval | timestamp | date '2026-01-31' + INTERVAL '1 month' | 2026-02-28 00:00:00 |
timestamp ± interval | timestamp | timestamp '2026-10-11 14:30:00' - INTERVAL '1 week' | 2026-10-04 14:30:00 |
timestamptz ± interval | timestamptz | now() + INTERVAL '2 hours' | |
time ± interval | time, wrapped around 24 hours | time '23:30' + INTERVAL '2 hours' | 01:30:00 |
time - time | interval | time '16:00' - time '14:30' | 01:30:00 |
timestamp - timestamp | interval | timestamp '2026-10-13 16:30:00' - timestamp '2026-10-11 14:30:00' | 2 days 02:00:00 |
A month added to an end of month gives the end of the next month (2026-01-31 + 1 month = 2026-02-28). A number added to a timestamp, or a decimal number added to a date, is rejected (42883: operator does not exist: date + numeric): add an interval.
SELECT id, date_commande, livree_le,
livree_le - date_commande AS delai,
livree_le::date - date_commande AS jours
FROM commande WHERE livree_le IS NOT NULL ORDER BY id; id | date_commande | livree_le | delai | jours
----+---------------+---------------------+-----------------+-------
1 | 2026-09-28 | 2026-10-01 10:15:00 | 3 days 10:15:00 | 3
3 | 2026-10-06 | 2026-10-08 16:40:00 | 2 days 16:40:00 | 2timestamptz and daylight saving changes. An interval in days, weeks, months or years keeps the session's civil time; an interval in hours, minutes or seconds counts elapsed time. The difference of two timestamptz values is the real duration between the two instants. Around the switch to winter time on October 25, 2026:
SELECT timestamptz '2026-10-25 00:30:00+02' + INTERVAL '1 day' AS un_jour,
timestamptz '2026-10-25 00:30:00+02' + INTERVAL '24 hours' AS vingt_quatre_heures,
timestamptz '2026-10-26 00:30:00+01' - timestamptz '2026-10-25 00:30:00+02' AS ecart; un_jour | vingt_quatre_heures | ecart
------------------------+------------------------+----------------
2026-10-26 00:30:00+01 | 2026-10-25 23:30:00+01 | 1 day 01:00:00Intervals#
An interval is written INTERVAL 'quantity unit …' or '…'::interval (also col::interval, $1::interval). As in PostgreSQL, it keeps its months, its days and its duration apart, is written 1 mon 2 days 03:00:00 and is announced as interval (OID 1186) to the client; a column can be of type interval (the written text is checked there, 22007, and rewritten: '25 hours' becomes 25:00:00).
| Notation | Accepted |
|---|---|
| units | microsecond(s), millisecond(s), second(s) / sec, minute(s) / min, hour(s) / h, day(s) / d, week(s), month(s) / mon, year(s) |
| several units | '2 hours 30 minutes', '1 day 02:00:00', '1 year 2 months', '2 weeks 3 days' |
| time | '01:30', '1:30:15.5' |
| decimal or negative quantity | '1.5 hours', '-1 day', '1 day ago' |
| standard form | INTERVAL '3' DAY (after a date: d + INTERVAL '3' DAY) |
| ISO 8601 | 'P1D', 'P1Y2M3DT4H5M6S' (M: months before the T, minutes after), 'PT1.5S', 'P2W', 'P0001-02-03T04:05:06' |
SELECT timestamp '2026-10-11 14:30:00' + INTERVAL '90 minutes',
timestamp '2026-10-11 14:30:00' + INTERVAL '1 day 2 hours',
timestamp '2026-10-11 14:30:00' + INTERVAL '1 year 2 months',
timestamp '2026-10-11 14:30:00' + INTERVAL '500 milliseconds',
timestamp '2026-10-11 14:30:00' + '2 hours 30 minutes'::interval;2026-10-11 16:00:00 | 2026-10-12 16:30:00 | 2027-12-11 14:30:00 | 2026-10-11 14:30:00.5 | 2026-10-11 17:00:00Written alone in a SELECT list, an interval returns its text, PostgreSQL style:
SELECT INTERVAL '45 days', INTERVAL '2 hours 30 minutes', INTERVAL '1 year 2 months', INTERVAL '1 week',
INTERVAL '1 day 12 hours', INTERVAL '1 month 1 day', INTERVAL '1.5 days';45 days | 02:30:00 | 1 year 2 mons | 7 days | 1 day 12:00:00 | 1 mon 1 day | 1 day 12:00:00| Operation | Result | Example | Value |
|---|---|---|---|
interval ± interval | field by field | INTERVAL '1 mon' - INTERVAL '1 day' | 1 mon -1 days |
interval * n, n * interval, interval / n, -interval | each field, the fraction of a month moving to 30-day days, that of a day to hours | INTERVAL '1 mon' * 1.5 | 1 mon 15 days |
=, <>, <, >… | by duration (30-day month, 24-hour day) | INTERVAL '1 day' = INTERVAL '24 hours' | t |
ORDER BY, BETWEEN, IN, greatest, least | by duration (greatest and least ignore NULL values) | INTERVAL '1 day' IN ('24:00:00') | t |
DISTINCT, GROUP BY (including ROLLUP, CUBE, GROUPING SETS), count(DISTINCT …), join | by duration: '1 day' and '24:00:00' are the same value, the value returned is that of one of the rows | ||
UNION, INTERSECT, EXCEPT (including WITH RECURSIVE … UNION) | by duration; UNION keeps the first notation encountered | SELECT INTERVAL '1 day' UNION SELECT INTERVAL '24:00:00' | 1 day |
primary key, UNIQUE, unique index | by duration: '24:00:00' is rejected (23505) if '1 day' exists; a foreign key to the column follows the same equality | ||
min, max | by duration | max(duree) | 1 mon |
sum, avg | field by field; avg divides like interval / n (sum(DISTINCT …), avg(DISTINCT …): 0A000) | avg(duree) | 10 days 17:00:00 |
A text next to an interval is read as an interval (WHERE duree > '1 day'); interval / 0: 22012; interval + 1: 42883. justify_*, age and timestamp - timestamp return an interval.
In a UNION, INTERSECT or EXCEPT, in the recursive term of a WITH RECURSIVE as in a VALUES list, a text written without a type next to an interval is also read as an interval (SELECT INTERVAL '1 day' UNION SELECT '24:00:00': one row 1 day, of type interval); if unreadable, it is rejected (22007). See 5.8.
extract and date_part#
extract(field FROM source)
date_part('field', source)Return a field of a date, a timestamp, a timestamptz or a time. extract returns a numeric, date_part a double precision. The field can be written in quotes in extract (extract('month' FROM d)).
| Field | Value | For 2026-10-11 14:30:45.5 (a Sunday) |
|---|---|---|
year, month, day | year, month, day | 2026, 10, 11 |
hour, minute | hour, minute | 14, 30 |
second | seconds with their fraction | 45.500000 |
milliseconds, microseconds | seconds in milliseconds, in microseconds | 45500.000, 45500000 |
dow | day of the week, 0 = Sunday | 0 |
isodow | ISO day of the week, 1 = Monday, 7 = Sunday | 7 |
doy | day of the year | 284 |
week | ISO week | 41 |
isoyear | year of the ISO week | 2026 |
quarter | quarter | 4 |
decade, century, millennium | decade, century, millennium | 202, 21, 3 |
julian | Julian day | 2461325 |
epoch | seconds since January 1, 1970 at 00:00 UTC | see below |
SELECT extract(year FROM date '2026-10-11'), extract(second FROM timestamp '2026-10-11 14:30:45.5'),
extract(dow FROM date '2026-10-11'), extract(week FROM date '2027-01-01'), extract(isoyear FROM date '2027-01-01'),
date_part('hour', timestamp '2026-10-11 14:30:45.5'), extract(hour FROM time '14:30:45');2026 | 45.500000 | 0 | 53 | 2026 | 14 | 14epoch counts the exact instant of a timestamptz; a date or a timestamp without time zone is read as UTC, as in PostgreSQL. The result is an integer for a date, with six decimals otherwise:
SELECT extract(epoch FROM timestamptz '2026-10-11 14:30:00+02') AS instant,
extract(epoch FROM timestamp '2026-10-11 14:30:00') AS sans_fuseau,
extract(epoch FROM date '2026-10-11') AS jour; instant | sans_fuseau | jour
-------------------+-------------------+------------
1791721800.000000 | 1791729000.000000 | 1791676800From an interval, extract reads its fields like PostgreSQL: hour is not wrapped to the day (extract(hour FROM INTERVAL '26 hours') is 26), epoch counts the month as 30 days and the year as 365.25 (extract(epoch FROM a - b): the duration in seconds). For a duration between two instants, the two epoch values can also be subtracted:
SELECT id, (extract(epoch FROM livree_le) - extract(epoch FROM date_commande)) / 3600 AS heures
FROM commande WHERE livree_le IS NOT NULL ORDER BY id; id | heures
----+---------------------
1 | 82.2500000000000000
3 | 64.6666666666666667extract(timezone FROM …): 0A000 (EXTRACT(timezone FROM …) is not supported yet in the PostgreSQL language).
date_trunc#
date_trunc('field', source)Brings source back to the start of the unit: microseconds, milliseconds, second, minute, hour, day, week (Monday), month, quarter, year, decade, century, millennium. The result is a timestamp for a date or a timestamp, a timestamptz for a timestamptz (truncated in the session's time zone). Unknown unit: 22023 (unit "fortnight" not recognized for type timestamp without time zone).
SELECT date_trunc('month', timestamp '2026-10-11 14:30:45'), date_trunc('week', timestamp '2026-10-11 14:30:45'),
date_trunc('hour', timestamp '2026-10-11 14:30:45'), date_trunc('quarter', date '2026-08-15')::date;2026-10-01 00:00:00 | 2026-10-05 00:00:00 | 2026-10-11 14:00:00 | 2026-07-01Number of orders per month:
SELECT date_trunc('month', date_commande)::date AS mois, count(*) AS commandes
FROM commande GROUP BY 1 ORDER BY 1; mois | commandes
------------+-----------
2026-09-01 | 1
2026-10-01 | 3Last day of the month of a date:
SELECT ((date_trunc('month', date '2026-02-10') + INTERVAL '1 month') - INTERVAL '1 day')::date; -- 2026-02-28make_date, make_time, make_timestamp, make_timestamptz, make_interval#
make_date(year, month, day)
make_time(hour, minute, second)
make_timestamp(year, month, day, hour, minute, second)
make_timestamptz(year, month, day, hour, minute, second [, zone])
make_interval([years [, months [, weeks [, days [, hours [, mins [, secs]]]]]]])Build a date, a time, a timestamp, a timestamptz (time read in zone, the session's by default) or an interval; the second can have a fraction. The arguments can also be written in named notation (make_date(year => 2026, month => 3, day => 1), make_interval(days => 3, hours => 2)); omitted make_interval arguments are 0. A value outside the calendar is a 22008 error (date field value out of range: 2026-02-30, time field value out of range: 25:00:00).
SELECT make_date(2026, 2, 28), make_time(10, 30, 15.5), make_timestamp(2026, 2, 28, 10, 30, 0);
SELECT make_timestamptz(2026, 10, 11, 14, 30, 0), make_timestamptz(2026, 10, 11, 14, 30, 0, 'UTC');
SELECT make_interval(days => 3, hours => 2), make_interval(1, 2), make_interval(weeks => 2, secs => 1.5);2026-02-28 | 10:30:15.5 | 2026-02-28 10:30:00
2026-10-11 14:30:00+02 | 2026-10-11 16:30:00+02
3 days 02:00:00 | 1 year 2 mons | 14 days 00:00:01.5date_bin#
date_bin(stride, source, origin)Brings source (a timestamp or a timestamptz) back to the start of the slice of length stride that contains it, the slices being counted from origin. The stride contains neither months nor years (22023: timestamps cannot be binned into intervals containing months or years) and it is positive (22023: stride must be greater than zero).
SELECT date_bin('15 minutes', timestamp '2026-10-11 14:38:20', timestamp '2026-01-01'),
date_bin('1 hour', timestamp '2026-10-11 14:38:20', timestamp '2026-01-01 00:30');2026-10-11 14:30:00 | 2026-10-11 14:30:00date_add, date_subtract, timezone#
date_add(instant, interval [, zone])
date_subtract(instant, interval [, zone])
timezone(zone, value)date_add and date_subtract add or remove an interval to or from a timestamptz; days and months are counted in civil time of zone (the session's by default), hours in elapsed time. timezone(zone, value) is the function form of value AT TIME ZONE zone (see below).
SELECT date_add(timestamptz '2026-10-24 12:00+02', interval '1 day'), date_add(timestamptz '2026-10-24 12:00+02', interval '24 hours'),
date_subtract(timestamptz '2026-10-25 12:00+01', interval '1 day', 'UTC');
SELECT timezone('America/New_York', timestamptz '2026-10-11 14:30:00+02'), timezone('UTC', timestamp '2026-10-11 14:30:00');2026-10-25 12:00:00+01 | 2026-10-25 11:00:00+01 | 2026-10-24 13:00:00+02
2026-10-11 08:30:00 | 2026-10-11 16:30:00+02isfinite(date | timestamp | interval) returns t: a date, a time or an interval is always finite ('infinity'::timestamp is rejected, 22007).
age, justify_days, justify_hours, justify_interval#
age(end, start) age(date_or_timestamp)
justify_days(interval) justify_hours(interval) justify_interval(interval)age(end, start) returns the gap in years, months and days, as an interval; age(t) counts from today's date. justify_days groups days into 30-day months, justify_hours hours into 24-hour days, justify_interval does both.
SELECT age(date '2026-10-11', date '1990-05-20'), age(timestamp '2026-10-11 10:00', timestamp '2026-10-11 08:30'),
age(timestamp '2024-02-29', timestamp '2026-10-11');
SELECT justify_days(INTERVAL '45 days'), justify_hours(INTERVAL '50 hours'), justify_interval(INTERVAL '400 days');36 years 4 mons 22 days | 01:30:00 | -2 years -7 mons -11 days
1 mon 15 days | 2 days 02:00:00 | 1 year 1 mon 10 daysThe number of years is read with extract:
SELECT extract(year FROM age(date '2026-10-11', date '1990-05-20')), pg_typeof(age(date '2026-10-11', date '1990-05-20'));36 | intervalAT TIME ZONE#
value AT TIME ZONE 'zone'- a
timestamptzbecomes thetimestampof the civil time inzone; - a
timestamp(without time zone) is read as a time inzoneand becomes atimestamptz.
zone is a time zone name ('Europe/Paris', 'UTC', 'America/New_York') or an offset; a written offset ('+02') is read west of Greenwich (POSIX notation), as in PostgreSQL. Unknown time zone: 22023 (time zone "Mars/Olympus" not recognized).
SELECT timestamptz '2026-10-11 14:30:00+02' AT TIME ZONE 'America/New_York' AS a_new_york,
timestamptz '2026-07-14 12:00:00+02' AT TIME ZONE 'UTC' AS en_utc,
timestamp '2026-10-11 14:30:00' AT TIME ZONE 'UTC' AS instant; a_new_york | en_utc | instant
---------------------+---------------------+------------------------
2026-10-11 08:30:00 | 2026-07-14 10:00:00 | 2026-10-11 16:30:00+02to_timestamp (seconds)#
to_timestamp(seconds) returns the timestamptz located that many seconds after January 1, 1970 at 00:00 UTC; it is the inverse of extract(epoch FROM …).
SELECT to_timestamp(1760000000), to_timestamp(0) AT TIME ZONE 'UTC';2025-10-09 10:53:20+02 | 1970-01-01 00:00:0036.6 Formatting: to_char, to_date, to_timestamp#
to_char of a date or time#
to_char(date_or_time, template)Writes a date, a timestamp, a timestamptz (in the session time zone) or a time according to template.
| Pattern | Writes | For 2026-10-11 14:05:09.123456 |
|---|---|---|
YYYY, YYY, YY, Y | year, its last 3, 2 or 1 digits | 2026, 026, 26, 6 |
IYYY | ISO week year | 2026 |
MM | month, 01 to 12 | 10 |
Month, MONTH, month | English month name, padded to 9 characters | October |
Mon, MON, mon | abbreviated name | Oct |
DD | day of the month, 01 to 31 | 11 |
DDD | day of the year | 284 |
Day, DAY, day | English day name, padded to 9 characters | Sunday |
Dy, DY, dy | abbreviated name | Sun |
D | day of the week, 1 = Sunday | 1 |
ID | ISO day of the week, 1 = Monday | 7 |
IW, WW | ISO week, week of the year | 41, 41 |
Q | quarter | 4 |
CC | century | 21 |
J | Julian day | 2461325 |
HH24 | hour, 00 to 23 | 14 |
HH12, HH | hour, 01 to 12 | 02 |
MI, SS | minutes, seconds | 05, 09 |
SSSS | seconds since midnight | 50709 |
MS, US | milliseconds, microseconds | 123, 123456 |
AM, PM | half-day indicator | PM |
TH, th (after a number) | English ordinal suffix | 11TH, 11th |
FM (before a pattern) | no leading zeros or padding spaces | FMDD: 11, FMMonth: October |
"text" | the text as is |
Any other character is copied.
SELECT to_char(timestamp '2026-10-11 14:05:09', 'YYYY-MM-DD HH24:MI:SS'),
to_char(timestamp '2026-10-11 14:05:09', 'DD/MM/YYYY HH12:MI AM'),
to_char(timestamp '2026-10-11 14:05:09', 'FMDay, FMDD FMMonth YYYY'),
to_char(date '2026-03-01', 'FMDD/FMMM/YYYY'),
to_char(time '14:05:09', 'HH24hMI');2026-10-11 14:05:09 | 11/10/2026 02:05 PM | Sunday, 11 October 2026 | 1/3/2026 | 14h05SELECT nom, to_char(cree_le, 'DD/MM/YYYY à HH24"h"MI') AS cree, to_char(cree_le, 'YYYY"-T"Q') AS trimestre
FROM client ORDER BY id; nom | cree | trimestre
--------------+--------------------+-----------
Dupont | 15/01/2026 à 09h30 | 2026-T1
Martin | 03/02/2026 à 14h05 | 2026-T1
Bernard | 21/03/2026 à 08h00 | 2026-T1
Élodie Petit | 02/04/2026 à 17h45 | 2026-T2Day and month names are in English. More patterns:
| Pattern | Meaning |
|---|---|
FF1 to FF6 | fraction of a second, 1 to 6 digits (FF3: milliseconds) |
RM, rm | month in Roman numerals (XI), padded to 4 characters without FM |
TZ, tz | abbreviation of the session time zone for a timestamptz (CEST, UTC; the offset, +05:30, for a zone without a name or the machine's zone); empty for a timestamp |
OF | time zone offset from UTC (+02, +05:30); +00 for a timestamp |
TZH, TZM | hours and minutes of the offset (+02, 00) |
W | week of the month, from 1 to 5 (the first starts on the 1st) |
TM (before Day, Month...) | name translated into the language of lc_time; it is C: English names |
SET TIME ZONE 'Europe/Paris';
SELECT to_char(timestamptz '2026-07-04 05:06:07+00', 'HH24:MI TZ OF'),
to_char(timestamp '2026-03-04 05:06:07.123456', 'SS.FF3'), to_char(date '2026-11-04', 'FMRM');
SELECT to_char(timestamptz '2026-07-04 05:06:07+00', 'HH24:MI TZH:TZM'), to_char(date '2026-10-11', 'W'), to_char(date '2026-10-11', 'TMDay TMMonth');07:06 CEST +02 | 07.123 | XI
07:06 +02:00 | 2 | Sunday Octoberto_date and to_timestamp do not read FF1...FF6, RM, TZ or OF.
to_char of an interval#
to_char(interval, template)Same patterns as for a date: YYYY, MM (years and months taken from the months), DD (days), DDD (days, months counted as 30), HH24 (hours not reduced to the day: 50 hours gives 50), HH12 / HH, MI, SS, MS, US, SSSS (seconds of the duration), CC, Q, WW, with FM, TH and text between quotes. A negative field keeps its sign (-05). Month and day names, AM / PM, D, ID, IW, IYYY and J have no meaning for an interval: 22007 (invalid format specification for an interval value).
SELECT to_char(interval '15h 2m 12s', 'HH24:MI:SS'), to_char(interval '50 hours 5.25 seconds', 'HH24 HH12 MI SS MS'),
to_char(interval '1 year 2 months 3 days', 'YYYY-MM-DD'), to_char(interval 'P2DT3H', 'FMDD "jours" FMHH24 "h"');15:02:12 | 50 02 00 05 250 | 0001-02-03 | 2 jours 3 hto_char of a number#
to_char(number, template)| Pattern | Meaning |
|---|---|
9 | a digit; a non-significant zero is replaced by a space |
0 | a digit; leading zeros are written |
., D | decimal separator (a point) |
,, G | thousands separator (a comma) |
S | sign + or -, written in place |
MI | trailing - sign for a negative number |
L | currency symbol from lc_monetary; it is C: a space |
PR | negative number between angle brackets (<485>), positive between two spaces |
RN, rn | Roman numerals (uppercase, lowercase) of the rounded integer, from 1 to 3999, on 15 right-aligned positions; outside these bounds: 15 # |
EEEE | scientific notation, with as many decimals as the template (9.99EEEE) |
V | number multiplied by 10 to the power of the number of digits that follow V (99V9) |
FM | no leading spaces or trailing zeros |
Without FM, the result starts with the place of the sign: a space for a positive number. A zero number is written 0 in a template without decimals (to_char(0, '99'): ' 0'); before decimals, the zero of the integer part is not written (to_char(0.5, '99.99'): ' .50'), like PostgreSQL.
SELECT '[' || to_char(1234.5, '9999.99') || ']', '[' || to_char(1234.5, 'FM9999.00') || ']',
'[' || to_char(12, '9999') || ']', '[' || to_char(12, 'FM9999') || ']',
to_char(7, 'FM000'), to_char(-7, '000'), to_char(1234567.891, 'FM9,999,999.99'),
to_char(12.5, 'FMS999.9'), to_char(0.126, 'FM0.00');[ 1234.50] | [1234.50] | [ 12] | [12] | 007 | -007 | 1,234,567.89 | +12.5 | 0.13SELECT reference, to_char(prix, 'FM9990.00') AS prix, to_char(prix * stock, 'FM999G990D00') AS valeur_stock,
to_char(stock, 'FM000000') AS stock
FROM article ORDER BY id; reference | prix | valeur_stock | stock
-----------+-------+--------------+--------
STY-001 | 1.20 | 600.00 | 000500
CAH-096 | 2.50 | 300.00 | 000120
CLA-A4 | 4.90 | 171.50 | 000035
AGR-024 | 12.00 | 0.00 | 000000The number is rounded to the number of decimals of the template. A number too large for the template is written as # (to_char(12345, '999') returns ###).
SELECT '[' || to_char(-485, '999PR') || ']', to_char(485, 'FMRN'), to_char(1994, 'FMrn'),
'[' || to_char(0.0004859, '9.99EEEE') || ']', '[' || to_char(-1234.5, '9.99EEEE') || ']',
'[' || to_char(12.45, '99V9') || ']';[<485>] | CDLXXXV | mcmxciv | [ 4.86e-04] | [-1.23e+03] | [ 125]The SG pattern is not recognized, and neither is to_char with a single argument.
to_number#
to_number(text, template)Reads a number written according to a template made of to_char patterns (9, 0, . / D, , / G, S, MI, L); thousands separators and the currency symbol are skipped. The result is a numeric.
SELECT to_number('12,454.8-', '99G999D9S'), to_number('$1,234.50', 'L9,999.99'), to_number('-42', '999'), pg_typeof(to_number('1', '9'));-12454.8 | 1234.50 | -42 | numericA text without a readable digit is rejected (22P02); the EEEE pattern is not allowed for input (0A000: "EEEE" not supported for input, like PostgreSQL). For a text in ordinary decimal format, the ::numeric conversion is enough (see 36.11).
to_date and to_timestamp#
to_date(text, template)
to_timestamp(text, template)Read a text according to a template written with the to_char patterns. to_date returns a date, to_timestamp a timestamptz (the time read is that of the session time zone).
SELECT to_date('11/10/2026', 'DD/MM/YYYY'), to_date('20261011', 'YYYYMMDD'), to_date('11 oct 2026', 'DD Mon YYYY'),
to_date('11 October 2026', 'DD Month YYYY'), to_date('11.10.26', 'DD.MM.YY'), to_date('2026-284', 'YYYY-DDD');
SELECT to_timestamp('2026-10-11 14:30:45', 'YYYY-MM-DD HH24:MI:SS'),
to_timestamp('11/10/2026 02:30 PM', 'DD/MM/YYYY HH12:MI AM');2026-10-11 | 2026-10-11 | 2026-10-11 | 2026-10-11 | 2026-10-11 | 2026-10-11
2026-10-11 14:30:45+02 | 2026-10-11 14:30:00+02A missing field takes its starting value (to_date('10/2026', 'MM/YYYY') returns 2026-10-01). Errors:
| Statement | Response |
|---|---|
SELECT to_date('31/02/2026', 'DD/MM/YYYY') | 22008: date/time field value out of range: "31/02/2026" |
SELECT to_date('abc', 'DD/MM/YYYY') | 22007: invalid value "ab" for "DD" |
SELECT to_timestamp('2026-10-11 25:00', 'YYYY-MM-DD HH24:MI') | 22008: date/time field value out of range: "2026-10-11 25:00" |
36.7 Conditional expressions#
CASE#
CASE WHEN condition THEN result [WHEN ...] [ELSE result] END
CASE expression WHEN value THEN result [WHEN ...] [ELSE result] ENDReturns the result of the first true branch, that of ELSE otherwise, NULL without ELSE.
SELECT reference, stock,
CASE WHEN stock = 0 THEN 'rupture' WHEN stock < 50 THEN 'faible' ELSE 'ok' END AS etat
FROM article ORDER BY id; reference | stock | etat
-----------+-------+---------
STY-001 | 500 | ok
CAH-096 | 120 | ok
CLA-A4 | 35 | faible
AGR-024 | 0 | ruptureThe CASE expression WHEN value form compares with =, so byte by byte for a text: CASE ville WHEN 'lyon' THEN 1 ELSE 0 END is 0 for Lyon.
The result type is the common type of the branches: integer and numeric give a numeric. Branches of different families (CASE WHEN ... THEN 1 ELSE 'a' END) are returned as text, where PostgreSQL rejects the query.
coalesce#
coalesce(value1, value2, ...)The first value that is not NULL; NULL if all of them are.
SELECT nom, coalesce(ville, 'inconnue') AS ville FROM client ORDER BY id; nom | ville
--------------+----------
Dupont | Lyon
Martin | Paris
Bernard | Lyon
Élodie Petit | inconnuenullif#
nullif(a, b)NULL if a = b, a otherwise. Used to avoid a division by zero:
SELECT reference, prix / nullif(stock, 0) AS prix_par_unite_en_stock FROM article ORDER BY id; reference | prix_par_unite_en_stock
-----------+-------------------------
STY-001 | 0.0024000000000000
CAH-096 | 0.0208333333333333
CLA-A4 | 0.1400000000000000
AGR-024 | NULLSince the comparison is case-sensitive, nullif('a', 'A') returns a.
greatest and least#
See 36.4. A NULL argument returns NULL.
Booleans#
TRUE and FALSE are returned as t and f. A boolean column and the ::boolean conversion of a text read t, true, yes, on, 1, their opposites and their prefixes; any other text is rejected (22P02: invalid input syntax for type boolean: "maybe"). An integer also converts (0: false); a numeric, a float, a date or a time do not convert (2.5::boolean: 42846, cannot cast type numeric to boolean), as in PostgreSQL.
SELECT 1::boolean, 0::boolean, 'yes'::boolean, 'off'::boolean, true::int, false::int;t | f | t | f | 1 | 0A boolean converted to text is written true or false: true::text, CAST(actif AS varchar), 'x' || true (xtrue). The ::boolean conversion of a computed text (column, value extracted from a document) returns a boolean, and likewise rejects a text that is not one (22P02).
SELECT true::text, false::varchar, 'actif : ' || true, ('{"ok": "yes"}'::jsonb ->> 'ok')::boolean;true | false | actif : true | t36.8 Aggregate functions#
An aggregate function computes a value for a group of rows (GROUP BY, see 35. SELECT queries) or for all rows. NULL values are ignored (except by array_agg, json_agg and jsonb_agg); on no rows, count and regr_count return 0 and the other aggregates NULL.
| Function | Purpose | Result type |
|---|---|---|
count(*), count(expr), count(DISTINCT expr) | number of rows, of non-NULL values, of distinct values | bigint |
sum(expr) | sum | bigint for integers, numeric for numeric, double precision for floats |
avg(expr) | average | numeric with at least 16 decimals; double precision for floats |
min(expr), max(expr) | smallest, largest value | that of expr |
any_value(expr) | one of the non-NULL values of the group | that of expr |
string_agg(expr, separator) | concatenation | text |
array_agg(expr) | array of the values | array (integer[], text[]...) |
json_agg, jsonb_agg, json_object_agg, jsonb_object_agg | JSON array or object | json or jsonb |
bool_and(cond), bool_or(cond), every(cond) | all true, at least one true | boolean |
stddev, stddev_samp, stddev_pop, variance, var_samp, var_pop | standard deviation, variance | numeric or double precision |
corr, covar_pop, covar_samp, regr_* | correlation, covariance, linear regression | double precision (regr_count: bigint) |
percentile_cont, percentile_disc, mode | quantiles, most frequent value (WITHIN GROUP) | double precision, that of the value |
rank, dense_rank, percent_rank, cume_dist with WITHIN GROUP | rank a value would have in the group | bigint, double precision |
bit_and(expr), bit_or(expr), bit_xor(expr) | bitwise AND, OR, XOR (of a bit(n) column: the number, not the bit string) | bigint |
count, sum, avg, min, max#
SELECT count(*), count(ville), count(DISTINCT ville) FROM client;
SELECT sum(stock), avg(stock), min(prix), max(prix), sum(prix), avg(prix) FROM article;4 | 3 | 2
655 | 163.7500000000000000 | 1.20 | 12.00 | 20.60 | 5.1500000000000000avg of integers or decimals is an exact numeric with 16 decimals (or at the scale of the argument if it is larger); round for display: round(avg(prix), 2). sum of a bigint column is a numeric. sum(DISTINCT expr) and avg(DISTINCT expr) are accepted.
SELECT article_id, sum(quantite) AS quantite, round(avg(prix_unitaire), 2) AS prix_moyen, count(*) AS lignes
FROM ligne_commande GROUP BY article_id ORDER BY article_id; article_id | quantite | prix_moyen | lignes
------------+----------+------------+--------
1 | 150 | 1.15 | 2
2 | 15 | 2.50 | 2
3 | 4 | 4.90 | 1
4 | 2 | 12.00 | 1min and max also apply to texts, dates and booleans.
FILTER#
aggregate(...) FILTER (WHERE condition)Passes to the aggregate only the rows that satisfy the condition.
SELECT count(*) FILTER (WHERE statut = 'livrée') AS livrees,
count(*) FILTER (WHERE statut <> 'livrée') AS autres
FROM commande;
SELECT count(*) FILTER (WHERE stock > 0), sum(stock) FILTER (WHERE prix < 3), avg(prix) FILTER (WHERE stock > 0) FROM article;2 | 2
3 | 620 | 2.8666666666666667A discarded row is not seen by the aggregate: it does not appear in json_agg (no null), and its NULL key does not make json_object_agg fail. A retained row keeps its value, NULL included. On a group with no retained row, count returns 0 and the other aggregates NULL. The result type is that of the aggregate without FILTER (sum(bigint): numeric).
SELECT json_agg(nom) FILTER (WHERE ville = 'Lyon') FROM client;
SELECT json_object_agg(reference, stock) FILTER (WHERE stock > 100) FROM article;["Dupont", "Bernard"]
{"STY-001": 500, "CAH-096": 120}FILTER only goes with an aggregate: after another function, 42809 (FILTER specified, but upper is not an aggregate function); after a window function that is not an aggregate (row_number, lag...), 0A000. The condition contains no aggregate (42803). The argument of the aggregate is computed for all rows, discarded ones included: a computation error on a discarded row (sum(10 / n) FILTER (WHERE n <> 0): 22012) is raised, where PostgreSQL does not compute the argument.
In a view or a routine body, the filtered aggregate is kept in the form aggregate(PG_AGG_FILTER(condition, argument)), which both languages read back (count(*): count(CASE WHEN condition THEN 1 END)).
string_agg#
string_agg(expr, separator [ORDER BY ...])
string_agg(DISTINCT expr, separator [ORDER BY ...])Concatenates the non-NULL values of the group, separated by separator, in the requested order. Both arguments are required; expr is converted to text; separator must be a constant.
SELECT string_agg(nom, ', ' ORDER BY nom) FROM client;
SELECT string_agg(DISTINCT ville, '/') FROM client;
SELECT ville, string_agg(nom, ' + ' ORDER BY nom) AS clients FROM client GROUP BY ville ORDER BY ville;Bernard, Dupont, Martin, Élodie Petit
Lyon/Paris
ville | clients
-------+------------------
Lyon | Bernard + Dupont
Paris | Martin
NULL | Élodie PetitSELECT string_agg(reference || '=' || stock, '; ' ORDER BY stock DESC) FROM article;STY-001=500; CAH-096=120; CLA-A4=35; AGR-024=0On no rows, string_agg returns NULL. The sort follows byte order: Élodie comes after Martin.
array_agg#
array_agg(expr [ORDER BY ...])
array_agg(DISTINCT expr [ORDER BY ...])Returns the array of the values of the group (see 36.14), NULL included, in the requested order; with DISTINCT, each value once, sorted (NULL last). On no rows, NULL. The result type is the array of the type of expr (integer[], text[]...).
SELECT array_agg(nom ORDER BY nom) AS noms, array_agg(DISTINCT ville) AS villes, array_agg(id) FILTER (WHERE ville = 'Lyon') AS lyonnais FROM client;
SELECT c.nom, array_agg(o.id ORDER BY o.id) AS commandes FROM client c JOIN commande o ON o.client_id = c.id GROUP BY c.nom ORDER BY c.nom;{Bernard,Dupont,Martin,"Élodie Petit"} | {Lyon,Paris,NULL} | {1,3}
nom | commandes
---------+-----------
Bernard | {4}
Dupont | {1,2}
Martin | {3}JSON aggregates#
json_agg(expr [ORDER BY ...]) jsonb_agg(expr [ORDER BY ...])
json_object_agg(key, value) jsonb_object_agg(key, value)json_agg and jsonb_agg return the JSON array of the values of the group (NULL included, written null); json_object_agg and jsonb_object_agg an object. A NULL key is rejected (22032: JSON documents may not contain NULL member names.).
SELECT json_agg(nom ORDER BY nom) FROM client;
SELECT json_object_agg(reference, stock) FROM article;
SELECT c.nom, json_agg(jsonb_build_object('commande', o.id, 'date', o.date_commande) ORDER BY o.id) AS commandes
FROM client c JOIN commande o ON o.client_id = c.id GROUP BY c.nom ORDER BY c.nom;["Bernard", "Dupont", "Martin", "Élodie Petit"]
{"STY-001": 500, "CAH-096": 120, "CLA-A4": 35, "AGR-024": 0}
nom | commandes
---------+--------------------------------------------------------------------------------
Bernard | [{"date": "2026-10-09", "commande": 4}]
Dupont | [{"date": "2026-09-28", "commande": 1}, {"date": "2026-10-05", "commande": 2}]
Martin | [{"date": "2026-10-06", "commande": 3}]bool_and, bool_or, every#
bool_and(condition) (or every) is true if the condition is true for all rows, bool_or(condition) if it is true for at least one. The result is a boolean (t / f), whether the argument is a boolean column or a condition; it is NULL with no rows. In a condition (HAVING bool_or(...)), it is used as is.
SELECT bool_and(stock > 0), bool_or(stock > 0), every(prix > 1) FROM article;f | t | tStandard deviation and variance#
| Function | Computes |
|---|---|
stddev(expr), stddev_samp(expr) | corrected (sample) standard deviation |
stddev_pop(expr) | population standard deviation |
variance(expr), var_samp(expr) | corrected variance |
var_pop(expr) | population variance |
On integers or decimals, the result is an exact numeric with at least 16 decimals; on floats, a double precision.
SELECT stddev(quantite), variance(quantite), stddev_pop(quantite), var_pop(quantite), stddev(quantite::float8)
FROM ligne_commande;39.4246116023988244 | 1554.3000000000000000 | 35.9895818258562154 | 1295.2500000000000000 | 39.42461160239883Correlation and regression#
Two-argument aggregates, y (dependent variable) then x, computed on the rows where both are not NULL. The result is a double precision, except regr_count (bigint).
| Function | Computes |
|---|---|
corr(y, x) | correlation coefficient |
covar_pop(y, x), covar_samp(y, x) | population covariance, sample covariance |
regr_slope(y, x), regr_intercept(y, x) | slope and intercept of the least-squares line |
regr_r2(y, x) | square of the correlation coefficient |
regr_count(y, x) | number of retained rows |
regr_avgx(y, x), regr_avgy(y, x) | averages of x and y |
regr_sxx(y, x), regr_syy(y, x), regr_sxy(y, x) | sums of squares and of products of deviations |
SELECT corr(y, x), covar_pop(y, x), covar_samp(y, x), regr_slope(y, x), regr_intercept(y, x), regr_r2(y, x), regr_count(y, x)
FROM (VALUES (1, 3), (2, 5), (3, 7), (4, NULL)) v(x, y);
SELECT regr_avgx(y, x), regr_avgy(y, x), regr_sxx(y, x), regr_syy(y, x), regr_sxy(y, x)
FROM (VALUES (1, 3), (2, 5), (3, 7), (4, NULL)) v(x, y);1 | 1.3333333333333333 | 2 | 2 | 1 | 1 | 3
2 | 5 | 2 | 8 | 4Ordered-set aggregates: WITHIN GROUP#
percentile_cont(fraction) WITHIN GROUP (ORDER BY expr)
percentile_disc(fraction) WITHIN GROUP (ORDER BY expr)
mode() WITHIN GROUP (ORDER BY expr)
rank(value) WITHIN GROUP (ORDER BY expr) -- likewise dense_rank, percent_rank, cume_distpercentile_contreturns the quantilefraction(from 0 to 1) of the sorted values, interpolated between the two neighboring values (double precision);percentile_cont(0.5)is the median;percentile_discreturns the first value of the group whose rank reaches the fraction, without interpolation (type ofexpr);mode()returns the most frequent value;rank,dense_rank,percent_rankandcume_distwith an argument return the rankvaluewould have if it were added to the group (NULLS FIRST/NULLS LASTaccepted in theORDER BY).
A fraction outside [0, 1] is rejected (22023: percentile value 1.5 is not between 0 and 1). FILTER can be added as with the other aggregates.
Fractions can be given as an array: percentile_cont and percentile_disc then return an array of as many quantiles (double precision[] for percentile_cont, array of the value type for percentile_disc); a NULL fraction gives a NULL element, a NULL array returns NULL.
SELECT percentile_cont(ARRAY[0.25, 0.5]) WITHIN GROUP (ORDER BY v),
percentile_disc(ARRAY[0.5, 1]) WITHIN GROUP (ORDER BY v)
FROM (VALUES (10), (20), (30), (40)) AS w(v);
-- {17.5,25} | {20,40}SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY quantite) AS mediane, percentile_disc(0.5) WITHIN GROUP (ORDER BY quantite) AS mediane_disc,
percentile_cont(0.9) WITHIN GROUP (ORDER BY quantite) AS p90, mode() WITHIN GROUP (ORDER BY article_id) AS article_frequent
FROM ligne_commande;
SELECT rank(10) WITHIN GROUP (ORDER BY quantite), dense_rank(10) WITHIN GROUP (ORDER BY quantite),
percent_rank(10) WITHIN GROUP (ORDER BY quantite), cume_dist(10) WITHIN GROUP (ORDER BY quantite)
FROM ligne_commande; mediane | mediane_disc | p90 | article_frequent
---------+--------------+-----+------------------
7.5 | 5 | 75 | 1
4 | 4 | 0.5 | 0.7142857142857143Multiple groupings#
GROUP BY ROLLUP (...), GROUP BY CUBE (...), GROUP BY GROUPING SETS (...) and the grouping(column) function (1 on a total row) are described in chapter 35. SELECT queries.
SELECT grouping(ville) AS total, ville, count(*) FROM client GROUP BY ROLLUP (ville); total | ville | count(*)
-------+-------+----------
0 | NULL | 1
0 | Lyon | 2
0 | Paris | 1
1 | NULL | 436.9 Window functions#
A window function computes, for each row, a value over a set of neighboring rows without grouping them. The OVER clause, frames and the WINDOW clause are described in chapter 35. SELECT queries.
function(...) OVER ([PARTITION BY ...] [ORDER BY ...] [frame])
function(...) OVER window_name| Function | Purpose | Type |
|---|---|---|
row_number() | number of the row in its partition | bigint |
rank() | rank, with gaps after ties | bigint |
dense_rank() | rank without gaps | bigint |
percent_rank() | relative rank, from 0 to 1 | double precision |
cume_dist() | share of rows with a lower or equal rank | double precision |
ntile(n) | group number, the partition being split into n groups | integer |
lag(expr [, offset [, default]]) | value of a preceding row | that of expr |
lead(expr [, offset [, default]]) | value of a following row | that of expr |
first_value(expr), last_value(expr) | first, last value of the frame | that of expr |
nth_value(expr, n) | nth value of the frame | that of expr |
count, sum, avg, min, max, stddev, variance..., bool_and, bool_or, every | aggregate over the frame | that of the aggregate |
Ranks#
SELECT id, designation, prix,
row_number() OVER (ORDER BY prix DESC) AS n,
rank() OVER (ORDER BY prix DESC) AS rang,
ntile(2) OVER (ORDER BY prix DESC) AS moitie
FROM article ORDER BY id; id | designation | prix | n | rang | moitie
----+------------------+-------+---+------+--------
1 | Stylo bille bleu | 1.20 | 4 | 4 | 2
2 | Cahier 96 pages | 2.50 | 3 | 3 | 2
3 | Classeur A4 | 4.90 | 2 | 2 | 1
4 | Agrafeuse | 12.00 | 1 | 1 | 1SELECT id, percent_rank() OVER w, cume_dist() OVER w, dense_rank() OVER w
FROM article WINDOW w AS (ORDER BY prix) ORDER BY id;1 | 0 | 0.25 | 1
2 | 0.3333333333333333 | 0.5 | 2
3 | 0.6666666666666666 | 0.75 | 3
4 | 1 | 1 | 4A window function cannot be cited in WHERE: to keep the two most expensive items, use a derived table, or the QUALIFY clause (a MIRAJ extension):
SELECT * FROM (SELECT id, prix, rank() OVER (ORDER BY prix DESC) AS r FROM article) x WHERE r <= 2 ORDER BY r;
SELECT id, prix FROM article QUALIFY row_number() OVER (ORDER BY prix DESC) <= 2 ORDER BY id;4 | 12.00 | 1
3 | 4.90 | 2
3 | 4.90
4 | 12.00lag and lead#
SELECT id, prix,
lag(prix) OVER (ORDER BY id) AS precedent,
lead(prix) OVER (ORDER BY id) AS suivant,
lag(prix, 2, 0) OVER (ORDER BY id) AS avant_dernier
FROM article ORDER BY id; id | prix | precedent | suivant | avant_dernier
----+-------+-----------+---------+---------------
1 | 1.20 | NULL | 2.50 | 0.00
2 | 2.50 | 1.20 | 4.90 | 0.00
3 | 4.90 | 2.50 | 12.00 | 1.20
4 | 12.00 | 4.90 | NULL | 2.50Number of days between an order and the previous one:
SELECT id, date_commande, date_commande - lag(date_commande) OVER (ORDER BY date_commande) AS jours
FROM commande ORDER BY id; id | date_commande | jours
----+---------------+-------
1 | 2026-09-28 | NULL
2 | 2026-10-05 | 7
3 | 2026-10-06 | 1
4 | 2026-10-09 | 3first_value, last_value, nth_value#
These functions read the row's frame. With ORDER BY and no explicit frame, the frame stops at the current row: last_value then returns the value of the row itself. For the last value of the partition, write the whole frame.
SELECT id, prix,
first_value(prix) OVER (ORDER BY id) AS premier,
last_value(prix) OVER (ORDER BY id) AS dernier_du_cadre,
last_value(prix) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS dernier,
nth_value(prix, 2) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS deuxieme
FROM article ORDER BY id; id | prix | premier | dernier_du_cadre | dernier | deuxieme
----+-------+---------+------------------+---------+----------
1 | 1.20 | 1.20 | 1.20 | 12.00 | 2.50
2 | 2.50 | 1.20 | 2.50 | 12.00 | 2.50
3 | 4.90 | 1.20 | 4.90 | 12.00 | 2.50
4 | 12.00 | 1.20 | 12.00 | 12.00 | 2.50Window aggregates#
SELECT commande_id, article_id, quantite,
sum(quantite) OVER (PARTITION BY commande_id) AS total_commande,
count(*) OVER (PARTITION BY commande_id) AS lignes
FROM ligne_commande ORDER BY 1, 2; commande_id | article_id | quantite | total_commande | lignes
-------------+------------+----------+----------------+--------
1 | 1 | 100 | 110 | 2
1 | 2 | 10 | 110 | 2
2 | 3 | 4 | 4 | 1
3 | 1 | 50 | 52 | 2
3 | 4 | 2 | 52 | 2
4 | 2 | 5 | 5 | 1Running total, sliding average over two rows and share of the total:
SELECT id, prix,
sum(prix) OVER (ORDER BY id) AS cumul,
avg(prix) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS moyenne_glissante,
round(100 * prix / sum(prix) OVER (), 1) AS part
FROM article ORDER BY id; id | prix | cumul | moyenne_glissante | part
----+-------+-------+--------------------+------
1 | 1.20 | 1.20 | 1.2000000000000000 | 5.8
2 | 2.50 | 3.70 | 1.8500000000000000 | 12.1
3 | 4.90 | 8.60 | 3.7000000000000000 | 23.8
4 | 12.00 | 20.60 | 8.4500000000000000 | 58.3FILTER is also used with OVER (count(*) FILTER (WHERE stock > 0) OVER (ORDER BY id)), as are ordered-set aggregates:
SELECT id, prix, percentile_cont(0.5) WITHIN GROUP (ORDER BY prix) OVER () AS mediane FROM article ORDER BY id; id | prix | mediane
----+-------+---------
1 | 1.20 | 3.7
2 | 2.50 | 3.7
3 | 4.90 | 3.7
4 | 12.00 | 3.7Supported frames: ROWS with UNBOUNDED PRECEDING, n PRECEDING, CURRENT ROW, n FOLLOWING, UNBOUNDED FOLLOWING; RANGE without offset (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW). RANGE with an offset, GROUPS, EXCLUDE, as well as string_agg and json_agg with OVER, are not supported (see 36.16).
SELECT DISTINCT ON (keys) ... keeps the first row of each group of keys in the ORDER BY order (see 35. SELECT queries): the last order of each customer.
SELECT DISTINCT ON (client_id) client_id, id, date_commande FROM commande ORDER BY client_id, date_commande DESC; client_id | id | date_commande
-----------+----+---------------
1 | 2 | 2026-10-05
2 | 3 | 2026-10-06
3 | 4 | 2026-10-0936.10 JSON and JSONB#
The json and jsonb types are described in chapter 32. Data types. A jsonb column has the storage and functions of a json column, keeps its declared type and stores its documents normalized (keys sorted by length then in binary order, duplicates removed). The examples use the column article.attributs (jsonb).
Operators#
| Operator | Purpose | Example | Result |
|---|---|---|---|
doc -> 'key' | member of an object, as JSON | '{"a": 1}'::jsonb -> 'a' | 1 |
doc -> n | element of an array (0 for the first, negative from the end), as JSON | '[1, 2, 3]'::jsonb -> -1 | 3 |
doc ->> 'key', doc ->> n | likewise, as text | '{"s": "txt"}'::jsonb ->> 's' | txt |
doc #> '{a,0}' | element at the path written as an array, as JSON | '{"b": {"c": [10, 20, 30]}}'::jsonb #> '{b,c,2}' | 30 |
doc #>> '{a,0}' | likewise, as text | '{"b": {"c": [10, 20, 30]}}'::jsonb #>> '{b,c,0}' | 10 |
a @> b | a contains b | '{"a": 1, "b": 2}'::jsonb @> '{"a": 1}' | t |
a <@ b | a is contained in b | '[1, 2]'::jsonb <@ '[1, 2, 3]' | t |
doc ? 'key' | the object has this member, or the array contains this string | '{"a": 1}'::jsonb ? 'a' | t |
doc ?| ARRAY[...] | at least one of the keys exists | '{"a": 1}'::jsonb ?| ARRAY['x', 'a'] | t |
doc ?& ARRAY[...] | all the keys exist | '{"a": 1}'::jsonb ?& ARRAY['a', 'b'] | f |
a || b | merge of two objects, concatenation of two arrays | '{"a": 1}'::jsonb || '{"b": 2}'::jsonb | {"a": 1, "b": 2} |
doc - 'key' | object without this member | '{"a": 1, "b": 2}'::jsonb - 'a' | {"b": 2} |
doc - n | array without this element | '[1, 2, 3]'::jsonb - 0 | [2, 3] |
doc #- '{a,b}' | document without the element at the path | '{"a": {"b": 1, "c": 2}}'::jsonb #- '{a,b}' | {"a": {"c": 2}} |
Operators can be chained: doc -> 'b' -> 'c' -> 1, doc -> 'a' ->> 'b'.
SELECT reference,
attributs -> 'couleur' AS json,
attributs ->> 'couleur' AS texte,
attributs -> 'tags' ->> 0 AS premier_tag,
attributs #>> '{dim,l}' AS longueur
FROM article ORDER BY id; reference | json | texte | premier_tag | longueur
-----------+---------+-------+-------------+----------
STY-001 | "bleu" | bleu | bureau | 14
CAH-096 | "rouge" | rouge | papier | NULL
CLA-A4 | "noir" | noir | NULL | NULL
AGR-024 | NULL | NULL | NULL | NULL- A missing member or element returns
NULL.->on anullmember returns the JSONnull;->>returnsNULL. ->and#>returnjsonbon ajsonbcolumn or value,jsononjson;->>and#>>returntext.->>always returns a text: convert to compare numbers,(attributs ->> 'pages')::int > 50.
Searching in a JSON column:
SELECT reference FROM article WHERE attributs ->> 'couleur' = 'bleu'; -- STY-001
SELECT reference FROM article WHERE attributs @> '{"couleur": "noir"}'; -- CLA-A4
SELECT reference FROM article WHERE attributs -> 'tags' @> '["papier"]'; -- CAH-096
SELECT reference FROM article WHERE (attributs ->> 'pages')::int > 50; -- CAH-096
SELECT reference FROM article WHERE attributs -> 'pages' IS NOT NULL; -- CAH-096
SELECT attributs ->> 'couleur' AS couleur, count(*) FROM article GROUP BY attributs ->> 'couleur' ORDER BY 1; couleur | count(*)
---------+----------
bleu | 1
noir | 1
rouge | 1
NULL | 1Key existence. ?, ?| and ?& test the first-level keys of an object, or the strings of an array; a NULL document returns NULL.
SELECT reference, attributs ? 'pages' AS a_pages, attributs ?| ARRAY['pages', 'recycle'] AS l_un,
attributs ?& ARRAY['couleur', 'tags'] AS les_deux, attributs -> 'tags' ? 'papier' AS tag_papier
FROM article ORDER BY id; reference | a_pages | l_un | les_deux | tag_papier
-----------+---------+------+----------+------------
STY-001 | f | f | t | f
CAH-096 | t | t | t | t
CLA-A4 | f | t | t | f
AGR-024 | NULL | NULL | NULL | NULLThe same tests are written as functions: jsonb_exists(doc, key), jsonb_exists_any(doc, array), jsonb_exists_all(doc, array). The other operators also have a function form: jsonb_concat(a, b) (||), jsonb_delete(doc, key) (-), jsonb_delete_path(doc, path) (#-), jsonb_contains(a, b) (@>), jsonb_contained(a, b) (<@).
|| merges two documents. Next to a jsonb, an object or array written as an untyped string is read as jsonb (attributs || '{"promo": true}'); between two texts, or between a json and an untyped string, || remains text concatenation.
UPDATE article SET attributs = attributs || '{"promo": true}'::jsonb WHERE id = 3 RETURNING attributs;
UPDATE article SET attributs = attributs - 'promo' WHERE id = 3 RETURNING attributs;{"tags": [], "promo": true, "couleur": "noir", "recycle": true}
{"tags": [], "couleur": "noir", "recycle": true}Removing an index from an object ('{"a": 1}'::jsonb - 0): 22023 (cannot delete from object using integer index).
Construction#
| Function | Returns |
|---|---|
jsonb_build_object(key1, value1, ...), json_build_object(...) | an object; odd number of arguments: 42883 |
jsonb_build_array(value1, ...), json_build_array(...) | an array |
to_jsonb(value), to_json(value) | the value as JSON: number, quoted string, true / false, null |
json_object(text_array), json_object(keys, values) | an object with text values, from {key1, value1, ...} or two arrays; odd number of elements: 22023 (array must have even number of elements) |
SELECT jsonb_build_object('a', 1, 'b', 'x', 'c', true, 'd', NULL, 'e', 1.5), jsonb_build_array(1, 'x', true, NULL),
jsonb_build_object('n', jsonb_build_object('x', 1), 't', jsonb_build_array(1, 2));
SELECT to_jsonb('txt'::text), to_json(42), to_jsonb(true), to_jsonb(date '2026-10-11'), to_jsonb(NULL::text);
SELECT jsonb_build_object('id', id, 'nom', nom, 'ville', ville) FROM client WHERE id IN (1, 4) ORDER BY id;
SELECT json_object('{a, 1, b, 2}'), json_object('{a, b}', '{x, y}');{"a": 1, "b": "x", "c": true, "d": null, "e": 1.5} | [1, "x", true, null] | {"n": {"x": 1}, "t": [1, 2]}
"txt" | 42 | true | "2026-10-11" | null
{"id": 1, "nom": "Dupont", "ville": "Lyon"}
{"id": 4, "nom": "Élodie Petit", "ville": null}
{"a": "1", "b": "2"} | {"a": "x", "b": "y"}to_jsonb does not apply to an entire row (to_jsonb(c): 42703): build the object with jsonb_build_object.
Modification#
jsonb_set(document, path, value [, create])Replaces the element at path (written as an array, '{a,0}') with value, a JSON document. If the last element of the path does not exist, it is added, unless create is false; a path with a missing intermediate element leaves the document unchanged.
SELECT jsonb_set('{"a": 1}'::jsonb, '{a}', '5'), jsonb_set('{"a": {"b": 1}}'::jsonb, '{a,b}', '"x"'),
jsonb_set('{"a": 1}'::jsonb, '{b}', '2'), jsonb_set('{"a": 1}'::jsonb, '{b}', '2', false),
jsonb_set('{"a": [1, 2]}'::jsonb, '{a,0}', '9');
SELECT jsonb_set(attributs, '{stock}', to_jsonb(stock)) FROM article WHERE id = 1;{"a": 5} | {"a": {"b": "x"}} | {"a": 1, "b": 2} | {"a": 1} | {"a": [9, 2]}
{"dim": {"d": 0.8, "l": 14}, "tags": ["bureau", "ecriture"], "stock": 500, "couleur": "bleu"}In an UPDATE: UPDATE article SET attributs = jsonb_set(attributs, '{couleur}', '"vert"') WHERE id = 3.
jsonb_insert(document, path, value [, after])
jsonb_strip_nulls(document) json_strip_nulls(document)jsonb_insert inserts value at path: in an array, before the element of that index (after it with after set to true); in an object, as a new member; an existing member is rejected (22023: cannot replace existing key). jsonb_strip_nulls and json_strip_nulls remove, at all levels, object members whose value is null (the nulls of an array stay).
SELECT jsonb_insert('{"a": [1, 2]}', '{a, 1}', '9'), jsonb_insert('{"a": [1, 2]}', '{a, 1}', '9', true), jsonb_insert('{"a": 1}', '{b}', '2');
SELECT jsonb_strip_nulls('{"a": 1, "b": null, "c": {"d": null}}'), json_strip_nulls('{"a": null, "b": 2}');{"a": [1, 9, 2]} | {"a": [1, 2, 9]} | {"a": 1, "b": 2}
{"a": 1, "c": {}} | {"b":2}Inspection#
| Function | Returns |
|---|---|
jsonb_typeof(doc), json_typeof(doc) | object, array, string, number, boolean, null |
jsonb_array_length(doc), json_array_length(doc) | number of elements of an array; any other document: 22023 (cannot get array length of a non-array) |
jsonb_extract_path(doc, key1, key2, ...), jsonb_extract_path_text(...) | element at the path given key by key (an index for an array), as JSON or text: like #> and #>> |
jsonb_pretty(doc) | the document laid out on several lines, indented by four spaces |
SELECT jsonb_typeof('{"a": 1}'::jsonb), jsonb_typeof('[1]'::jsonb), jsonb_typeof('1'::jsonb),
jsonb_typeof('"a"'::jsonb), jsonb_typeof('true'::jsonb), jsonb_typeof('null'::jsonb);
SELECT reference, jsonb_array_length(attributs -> 'tags') AS tags, jsonb_typeof(attributs -> 'dim') AS dim
FROM article ORDER BY id;object | array | number | string | boolean | null
reference | tags | dim
-----------+------+--------
STY-001 | 2 | object
CAH-096 | 1 | NULL
CLA-A4 | 0 | NULL
AGR-024 | NULL | NULLSELECT jsonb_extract_path(attributs, 'dim', 'l'), jsonb_extract_path_text(attributs, 'tags', '1') FROM article WHERE id = 1;
SELECT jsonb_pretty(attributs) FROM article WHERE id = 2;14 | ecriture
{
"tags": [
"papier"
],
"pages": 96,
"couleur": "rouge"
}The aggregates json_agg, jsonb_agg, json_object_agg and jsonb_object_agg are described in 36.8.
SQL/JSON paths#
jsonb_path_exists(doc, path [, variables [, silent]])
jsonb_path_match(doc, path [, variables [, silent]])
jsonb_path_query_array(doc, path [, variables [, silent]])
jsonb_path_query_first(doc, path [, variables [, silent]])Evaluate an SQL/JSON path ($.a.b, $.tags[*], $.a[last], $.dim.*, filters ? (@ > 2) with ==, !=, <, >, &&, ||, !), in lax mode (default: missing member ignored) or strict (written at the start of the path: missing member is an error 2203A, JSON object does not contain key "b"). variables is a JSON object whose members are cited as $name in the path; silent set to true turns path errors into NULL.
jsonb_path_exists: does the path find at least one element?jsonb_path_match: result of a path that is a predicate ($.pages > 50); a path that does not return a single boolean is rejected (22038:single boolean result is expected);jsonb_path_query_array: all the elements found, as a JSON array;jsonb_path_query_first: the first element found,NULLif there is none.
SELECT reference, jsonb_path_exists(attributs, '$.tags[*] ? (@ == "papier")') AS papier, jsonb_path_match(attributs, '$.pages > 50') AS gros,
jsonb_path_query_array(attributs, '$.dim.*') AS dims, jsonb_path_query_first(attributs, '$.tags[last]') AS dernier_tag
FROM article ORDER BY id;
SELECT jsonb_path_query_array('{"a": [1, 2, 3, 4]}', '$.a[*] ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
SELECT jsonb_path_exists('{"a": 1}', 'strict $.b', '{}', true), jsonb_path_exists('{"a": 1}', 'lax $.b'); reference | papier | gros | dims | dernier_tag
-----------+--------+------+-----------+-------------
STY-001 | f | f | [0.8, 14] | "ecriture"
CAH-096 | t | t | [] | "papier"
CLA-A4 | f | f | [] | NULL
AGR-024 | NULL | NULL | NULL | NULL
[2, 3]
NULL | fJSON set-returning functions#
| Function | Returns one row per | Columns |
|---|---|---|
jsonb_each(doc), json_each(doc) | object member | key (text), value (JSON) |
jsonb_each_text(doc), json_each_text(doc) | object member | key, value (text) |
jsonb_array_elements(doc), json_array_elements(doc) | array element | value (JSON) |
jsonb_array_elements_text(doc), json_array_elements_text(doc) | array element | value (text) |
jsonb_object_keys(doc), json_object_keys(doc) | object key | one text column |
They are used in a FROM, lateral to the tables that precede them (FROM article a, jsonb_array_elements_text(a.attributs -> 'tags') AS t(tag)), with WITH ORDINALITY (one more column of numbers) and column aliases AS e(cle, valeur). Written in the SELECT list with a constant document, they return one row per value (see 36.14); with an argument that cites a column, place them in the FROM (otherwise 0A000).
SELECT * FROM jsonb_each_text('{"a": 1, "b": "x", "c": null}');
SELECT * FROM jsonb_array_elements('[1, "x", {"k": true}]') WITH ORDINALITY AS e(valeur, rang);
SELECT a.reference, t.tag FROM article a, jsonb_array_elements_text(a.attributs -> 'tags') AS t(tag) ORDER BY a.id, t.tag;
SELECT a.reference, k.cle FROM article a, jsonb_object_keys(a.attributs) AS k(cle) WHERE a.id = 2 ORDER BY 2;a | 1
b | x
c | NULL
1 | 1
"x" | 2
{"k": true} | 3
reference | tag
-----------+----------
STY-001 | bureau
STY-001 | ecriture
CAH-096 | papier
reference | cle
-----------+---------
CAH-096 | couleur
CAH-096 | pages
CAH-096 | tagsMIRAJ language JSON functions#
The MIRAJ JSON functions remain available, with their paths written '$.a.b[0]':
| Function | Use |
|---|---|
json_keys(doc) | keys of an object, as a JSON array |
json_remove(doc, '$.a.b') | document without the element at the path |
json_pretty(doc) | layout |
json_table(doc, path COLUMNS (...)) | typed rows and columns taken from a document, in the FROM |
json_extract(doc, '$.a[0]'), json_value(doc, '$.a'), json_contains, json_search | reading and searching by path |
json_valid(text), json_depth(doc), json_length(doc) | validity, depth, size |
SELECT json_keys(attributs), json_length(attributs), json_remove(attributs, '$.dim'), json_value(attributs, '$.couleur')
FROM article WHERE id = 1;
SELECT a.reference, t.tag
FROM article a, json_table(a.attributs, '$.tags[*]' COLUMNS (tag text PATH '$')) AS t ORDER BY a.id, t.tag;["dim", "tags", "couleur"] | 3 | {"tags": ["bureau", "ecriture"], "couleur": "bleu"} | bleu
reference | tag
-----------+----------
STY-001 | bureau
STY-001 | ecriture
CAH-096 | papierTheir full description is in chapter 9. SQL functions (calls are written with strings between apostrophes).
Differences from PostgreSQL#
- A
jsonbdocument is normalized, as in PostgreSQL: in each object, a repeated key keeps only its last value and keys are sorted by length then in binary order; spacing is canonical and a number written with an exponent is expanded.'{"b": 1, "a": 2, "a": 3}'::jsonbis displayed as{"a": 3, "b": 1}, and'{"a": 1, "b": 2}'::jsonb = '{"b": 2, "a": 1}'::jsonbis true. Ajsonkeeps the text as written. Twojsonbvalues are compared by value, in the order specific to the type ('1.0'::jsonb = '1'::jsonbis true, so is'2'::jsonb < '10'::jsonb; see 32.8), including forORDER BY,DISTINCT,GROUP BY,UNIONand a join key. Leftovers: a document stored in ajsonbcolumn by an earlier version, or written in the MIRAJ language, keeps its text until its next write in the PostgreSQL language (UPDATE t SET doc = doc::jsonbnormalizes it); a primary key, aUNIQUEconstraint and a search through an index on ajsonboperate on the text of the document. - An unreadable document is rejected (
22P02), in ajsonorjsonbcolumn as in the conversion of a text:'{"a": 1'::jsonb. - The results of
||,-,#-,jsonb_set,jsonb_build_object,jsonb_build_array,to_jsonband of thejsonb_agg/jsonb_object_aggaggregates arejsonb(type announced to the client andpg_typeof), normalized; those ofjson_build_object,json_build_array,to_json,json_aggandjson_object_aggarejson, in the written order.||,-,#-andjsonb_setexist only for ajsonb, as in PostgreSQL: on ajson(column,::json, result of ajson_*function), they are rejected (42883:operator does not exist: json || json); convert with::jsonb.CASE ... THEN doc ENDandcoalescekeep the type of their values (jsonb,json,uuid, interval, enumeration);max(doc)andmin(doc)of ajsonb, absent from PostgreSQL, follow the type's order and return ajsonb.
36.11 Conversions#
The types and their literals are described in chapter 32. Data types. This section summarizes the conversions useful in an expression.
expression::type
CAST(expression AS type)
type 'text'SELECT '42'::int + 1, CAST('42' AS integer), 42::text || '!', '3.14'::numeric(5,1), '3.14'::float8,
int4 '5', boolean 't', ' 42 '::int;43 | 42 | 42! | 3.1 | 3.14 | 5 | t | 42| Conversion | Rule | Example | Result |
|---|---|---|---|
| decimal number → integer | rounded to the nearest | 3.7::int, 2.5::int, (-3.7)::int | 4, 3, -4 |
→ numeric(p,s) | rounded to s decimals | 123.456::numeric(5,2) | 123.46 |
→ numeric without precision | value kept, without trailing zeros | '1.50'::numeric | 1.5 |
→ text | written form of the number, of the date | 1.50::text, date '2026-10-11'::text | 1.50, 2026-10-11 |
→ varchar(n) | truncated to n characters | 42::varchar(1) | 4 |
timestamp → date, time | date part, time part | '2026-10-11 14:30:45'::timestamp::date | 2026-10-11 |
date → timestamp | at midnight | date '2026-10-11'::timestamp | 2026-10-11 00:00:00 |
→ timestamp(n) | n decimals of second | '2026-10-11 14:30:45.123456'::timestamp(0) | 2026-10-11 14:30:45 |
text → timestamptz | offset (+02, Z) or time zone name read, instant displayed in the session time zone | '2026-10-11T12:30:00Z'::timestamptz | 2026-10-11 14:30:00+02 |
text → bytea | bytes of the text, or hexadecimal \x... | '\x414243'::bytea | \x414243 |
bytea → text | UTF-8 text of the bytes | '\x414243'::bytea::text | ABC |
| hexadecimal, octal, binary text → integer | prefixes 0x, 0o, 0b, separator _ | '0x1F'::int, '0b101'::int, '1_000'::int | 31, 5, 1000 |
An unreadable or out-of-range value is an error, not a zero, a numeric prefix or a NULL:
| Statement | Response |
|---|---|
SELECT 'abc'::int | 22P02: invalid input syntax for type integer: "abc" |
SELECT '1.5'::int, SELECT ''::int | 22P02 |
SELECT 'x'::numeric | 22P02: invalid input syntax for type numeric: "x" |
SELECT 99999::smallint | 22003: smallint out of range |
SELECT 3000000000::int | 22003: integer out of range |
SELECT 'maybe'::boolean | 22P02: invalid input syntax for type boolean: "maybe" |
SELECT 2.5::boolean | 42846: cannot cast type numeric to boolean |
SELECT '{"a": 1}'::json || '{"b": 2}'::json | 42883: operator does not exist: json || json |
SELECT 'abc'::interval | 22P02: invalid input syntax for type interval: "abc" |
SELECT 12345.678::numeric(5,2) | 22003: numeric field overflow: A field with precision 5, scale 2 must round to an absolute value less than 10^3. |
SELECT '2026-02-30'::date | 22008: date/time field value out of range: "2026-02-30" |
SELECT 'pas-un-uuid'::uuid | 22P02: invalid input syntax for type uuid: "pas-un-uuid" |
SELECT '{"a": 1'::jsonb | 22P02: invalid input syntax for type jsonb: "{"a": 1" |
pg_input_is_valid(text, type) tells whether a text is readable in a type without raising the error (see 36.13).
pg_typeof(expression) returns the type name of an expression:
SELECT pg_typeof(1), pg_typeof(3000000000), pg_typeof(1.5), pg_typeof(1.5::float8), pg_typeof('a'::text),
pg_typeof(true), pg_typeof(now()), pg_typeof(current_date);integer | bigint | numeric | double precision | text | boolean | timestamp with time zone | dateConversions to regclass, regtype... and format_type are used to read the catalog: see 40. Catalog and tools.
36.12 Bytes, hashing and encoding#
| Function | Purpose | Type |
|---|---|---|
md5(text_or_bytes) | MD5 digest, 32 hexadecimal digits | text |
sha224(bytes), sha256(bytes), sha384(bytes), sha512(bytes) | SHA-2 digests | bytea |
digest(data, algorithm) | md5, sha1, sha224, sha256, sha384 or sha512 digest | bytea |
to_hex(integer) | hexadecimal form | text |
encode(bytes, format) | bytes → text; formats hex, base64, escape | text |
decode(text, format) | text → bytes | bytea |
convert_to(text, encoding) | text → bytes in the given encoding | bytea |
convert_from(bytes, encoding) | bytes of an encoding → text | text |
get_byte(bytes, n), set_byte(bytes, n, value) | byte at index n (from 0), bytes with that byte replaced | integer, bytea |
length(bytes), octet_length(bytes) | number of bytes | integer |
crypt(password, salt), gen_salt(type) | password hash, fresh salt | text |
gen_random_bytes(n) | n random bytes (1 to 1024) | bytea |
SELECT md5('abc'), to_hex(255), to_hex(-1);
SELECT encode('abc'::bytea, 'hex'), encode('abc'::bytea, 'base64'), decode('616263', 'hex'), decode('YWJj', 'base64'),
length(decode('ff00', 'hex'));
SELECT encode(nom::bytea, 'base64'), decode('w4lsb2RpZSBQZXRpdA==', 'base64')::text FROM client WHERE id = 4;900150983cd24fb0d6963f7d28e17f72 | ff | ffffffff
616263 | YWJj | \x616263 | \x616263 | 2
w4lsb2RpZSBQZXRpdA== | Élodie PetitA bytea is returned to the client in hexadecimal (\x...). The escape format writes non-printable bytes as \ooo (octal) and reads them the same way (decode('a\000b', 'escape') is \x610062). Unknown format: 22023 (unrecognized encoding: "bin"); invalid hexadecimal text: 22023 (invalid hexadecimal digit).
Digests#
sha224 to sha512 return the digest as bytes: encode(..., 'hex') writes it in hexadecimal. digest (from the pgcrypto extension) accepts a text or bytes; an unknown algorithm is rejected (22023: Cannot use "sha3": No such hash algorithm). The MIRAJ functions sha1(text) and sha2(text, bits) directly return the hexadecimal.
SELECT sha256('abc'), encode(sha224('abc'), 'hex'), length(sha384('abc')), length(sha512('abc'));
SELECT encode(digest('abc', 'sha256'), 'hex'), digest('abc', 'md5') = decode(md5('abc'), 'hex'), sha2('a', 256);\xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad | 23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7 | 48 | 64
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad | t | ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bbEncodings and bytes#
convert_to and convert_from know the encodings UTF8, LATIN1, WIN1252 and SQL_ASCII (any other name: 22023, invalid source encoding name "XYZ"). A character with no equivalent in the target encoding is rejected (22P05: character with byte sequence 0xe2 0x82 0xac in encoding "UTF8" has no equivalent in encoding "LATIN1"), and so is an invalid byte sequence (22021: invalid byte sequence for encoding "UTF8": 0xff). get_byte and set_byte outside the bytes: 2202E (index 5 out of valid range, 0..0).
SELECT convert_to('café', 'LATIN1'), convert_to('café', 'UTF8'), convert_from('\x636166e9'::bytea, 'LATIN1'), convert_from('\x80'::bytea, 'WIN1252');
SELECT get_byte('\x1234ff'::bytea, 2), set_byte('\x1234ff'::bytea, 0, 0);\x636166e9 | \x636166c3a9 | café | €
255 | \x0034ffPasswords and random bytes#
crypt(password, salt) hashes a password according to the salt: a fresh salt from gen_salt('md5') ($1$...) or gen_salt('des') (2 characters), or an existing hash, which makes it possible to verify a password: crypt(entered, hash) = hash. gen_salt('bf') and gen_salt('xdes') are rejected (0A000), and so is any other type (22023: gen_salt: Unknown salt algorithm). gen_random_bytes(n) outside 1 to 1024: 22023 (Length not in range).
SELECT crypt('secret', '$1$abcdefgh'), crypt('secret', '$1$abcdefgh$cHJi5PXp/ki/ktXzqlk6I1') = '$1$abcdefgh$cHJi5PXp/ki/ktXzqlk6I1',
length(gen_salt('des')), gen_salt('md5') LIKE '$1$%', length(gen_random_bytes(16));$1$abcdefgh$cHJi5PXp/ki/ktXzqlk6I1 | t | 2 | t | 1636.13 System, session, sequences and series#
Session and server#
| Function | Returns |
|---|---|
version() | PostgreSQL 16.4 (MIRAJ 1.0.0) on aarch64-macos, 64-bit (the server platform) |
current_database(), current_catalog | miraj: the only PostgreSQL database of the instance |
current_schema, current_schema() | the current MIRAJ database (ventes) |
current_schemas(true) | {pg_catalog,ventes} |
current_user, session_user, user, current_role | the connected account, without its host (root) |
pg_backend_pid() | connection number |
pg_cancel_backend(pid), pg_terminate_backend(pid [, timeout]) | interrupts the running query of session pid, closes that session: t; f with a warning if pid is unknown; 42501 without the right on it (see 37.10) |
txid_current(), pg_current_xact_id() | transaction number: positive, the same until it ends (specific to MIRAJ) |
txid_current_if_assigned(), pg_current_xact_id_if_assigned() | the same number inside a BEGIN ... COMMIT block, NULL outside a block |
txid_current_snapshot(), pg_current_snapshot() | snapshot xmin:xmax: (both bounds equal the transaction number, the list of running transactions is empty) |
pg_is_in_recovery() | f |
pg_postmaster_start_time(), pg_conf_load_time() | server start time (timestamptz) |
inet_client_addr(), inet_server_addr() | client address; server address for a loopback connection, NULL otherwise |
pg_my_temp_schema() | 0 |
current_setting(name [, missing_ok]) | value of a parameter; unknown parameter: 42704, or NULL with missing_ok set to true |
set_config(name, value, local) | sets the parameter and returns value: like SET if local is false, like SET LOCAL (until the end of the transaction) if true; applied at the end of the statement (see 37.7) |
pg_reload_conf() | t: parameters apply as soon as they are written, there is nothing to reload; 42501 without the SUPER privilege |
pg_notify(channel, payload) | same effect as NOTIFY channel, 'payload'; returns an empty value |
pg_typeof(expr) | type name of the expression |
pg_column_size(value) | bytes occupied by the stored value (length header included for a text) |
pg_input_is_valid(text, type) | is the text a valid value of the type, without raising the error? Integer types, numeric, floats, boolean, uuid, json, jsonb, bytea, interval, texts; unknown type: 42704; date, time and others: 0A000 |
pg_sleep(seconds) | waits; returns 0; if canceled, fails with 57014 |
SELECT current_database(), current_schema, current_user, current_setting('server_version'), current_setting('TimeZone'),
current_setting('inconnu.param', true);miraj | ventes | root | 16.4 | Europe/Paris | NULLSELECT pg_input_is_valid('42', 'integer'), pg_input_is_valid('4x', 'integer'), pg_input_is_valid('{"a":', 'jsonb'),
pg_input_is_valid('1 day', 'interval'), pg_input_is_valid('maybe', 'boolean');
SELECT pg_column_size('abc'::text), pg_column_size(true), inet_client_addr(), pg_postmaster_start_time() <= now();t | f | f | t | f
4 | 1 | 127.0.0.1 | tThe functions that describe objects (pg_get_indexdef, pg_get_constraintdef, obj_description, has_table_privilege, pg_table_is_visible, to_regclass...) and sizes (pg_relation_size, pg_total_relation_size, pg_database_size, pg_size_pretty) are described in chapter 40. Catalog and tools.
Identifiers#
gen_random_uuid() (or uuid_generate_v4(), from the uuid-ossp extension) returns a random UUID (version 4), of type uuid, written in 36 characters:
SELECT gen_random_uuid(); -- fe6bc144-804b-49de-9ad2-555c3f027f68Sequences#
| Function | Purpose |
|---|---|
nextval('sequence') | advances the sequence and returns the new value (bigint) |
currval('sequence') | last value returned by nextval in the session; 55000 if the session has not yet called it on this sequence |
setval('sequence', n [, is_called]) | sets the value: the next nextval returns the following value, or n itself if is_called is false |
Creating a sequence is described in chapter 33. DDL language.
CREATE SEQUENCE seq_facture START 100 INCREMENT 10;
SELECT nextval('seq_facture'), nextval('seq_facture'); -- 100 | 110
SELECT currval('seq_facture'); -- 110
SELECT setval('seq_facture', 500), nextval('seq_facture'); -- 500 | 510
SELECT setval('seq_facture', 1000, false), nextval('seq_facture'); -- 1000 | 1000
DROP SEQUENCE seq_facture;Unknown sequence: 42P01. A serial column has no named sequence (pg_get_serial_sequence('article', 'id') returns NULL, and currval('article_id_seq') fails): the assigned value is read with INSERT ... RETURNING id (see 34. DML language). lastval() returns the last value served to the session, all sequences combined, by nextval or by a serial / IDENTITY column; as long as the session has received none, it returns 55000 (lastval is not yet defined in this session).
generate_series#
generate_series(start, stop [, step])Set-returning function: returns the values from start to stop, in steps of step (1 by default, negative for a countdown), one per row; no rows if stop is already passed. The bounds are integers, numeric or dates and instants (date, timestamp, timestamptz), the step then being an interval (for a timestamptz, a step in days keeps the session's civil time). It is used in a FROM (with WITH ORDINALITY and a column alias AS s(n)) or in the SELECT list (see 36.14).
SELECT n, n * n AS carre FROM generate_series(1, 3) AS s(n);
SELECT * FROM generate_series(10, 1, -3) WITH ORDINALITY AS s(n, rang);
SELECT * FROM generate_series(0.5, 1.5, 0.5);
SELECT * FROM generate_series(timestamp '2026-10-11 08:00', timestamp '2026-10-11 10:00', interval '1 hour');
SELECT generate_series(1, 3) AS n, 'x' AS lettre;1 | 1
2 | 4
3 | 9
10 | 1
7 | 2
4 | 3
1 | 4
0.5
1.0
1.5
2026-10-11 08:00:00
2026-10-11 09:00:00
2026-10-11 10:00:00
1 | x
2 | x
3 | xA series of days serves as a calendar: number of orders per day, days without orders included.
SELECT j.jour::date AS jour, count(o.id) AS commandes
FROM generate_series(date '2026-10-04', date '2026-10-07', interval '1 day') AS j(jour)
LEFT JOIN commande o ON o.date_commande = j.jour
GROUP BY j.jour ORDER BY 1; jour | commandes
------------+-----------
2026-10-04 | 0
2026-10-05 | 1
2026-10-06 | 1
2026-10-07 | 0A zero step: 22023 (step size cannot equal zero).
Advisory locks#
| Function | Purpose |
|---|---|
pg_advisory_lock(key) | takes the lock, waiting if it is held by another session |
pg_try_advisory_lock(key) | takes the lock without waiting; t if taken, f otherwise |
pg_advisory_unlock(key) | releases the lock; f, with a warning, if the session did not hold it |
pg_advisory_xact_lock(key) | takes the lock until the end of the transaction, waiting if it is held |
pg_try_advisory_xact_lock(key) | likewise without waiting; t if taken, f otherwise |
The key is a bigint or two integer values (pg_try_advisory_lock(1, 2)). Session locks are cumulative (taken twice, they are released twice) and all released at the end of the session; transaction locks are released at COMMIT or ROLLBACK, without pg_advisory_unlock.
SELECT pg_try_advisory_lock(42), pg_try_advisory_lock(42), pg_advisory_unlock(42), pg_advisory_unlock(42), pg_advisory_unlock(42);
BEGIN;
SELECT pg_try_advisory_xact_lock(8);
COMMIT;t | t | t | t | f
t36.14 Arrays and set-returning functions#
Arrays#
An array is a list of values of the same type, written ARRAY[v1, v2, ...] or as text '{v1,v2,...}'::type[]. It is returned to the client in PostgreSQL notation ({1,2,3}; empty elements, or containing a comma, a brace, a quote or a blank, between quotes; NULL written as is) and announced under the array type (integer[], text[]...). The element type is the common type of the written values (ARRAY[1, 2.5] is a numeric[]); incompatible values are rejected (ARRAY[1, 'a']: 22P02). Arrays are expression values: a table column cannot be of an array type (0A000).
SELECT ARRAY[1, 2, 3], ARRAY['a', 'b c', NULL], ARRAY[1, 2.5], '{1,2,3}'::int[], ARRAY[]::text[], pg_typeof(ARRAY[1, 2]);
SELECT ARRAY[true, false], ARRAY['', 'a,b', 'q"x'], ARRAY[date '2026-10-11'];{1,2,3} | {a,"b c",NULL} | {1,2.5} | {1,2,3} | {} | integer[]
{t,f} | {"","a,b","q\"x"} | {2026-10-11}| Expression | Returns | Example | Result |
|---|---|---|---|
array[n] | element at index n (from 1); NULL outside the array | (ARRAY[10, 20, 30])[2] | 20 |
array_length(array, 1), cardinality(array) | number of elements (array_length: NULL for another dimension or an empty array) | cardinality(ARRAY[1, 2]) | 2 |
array_lower(array, 1), array_upper(array, 1) | first and last index | array_upper(ARRAY[1, 2, 3], 1) | 3 |
array_position(array, value) | index of the first occurrence; NULL if absent | array_position(ARRAY['a', 'b'], 'b') | 2 |
array_to_string(array, separator [, null_text]) | elements joined by separator; NULLs are skipped, or replaced by null_text | array_to_string(ARRAY[1, NULL, 3], ',', '*') | 1,*,3 |
=, <>, <, >... | element-by-element comparison | ARRAY[1, 2] < ARRAY[1, 3] | t |
value = ANY (array), value > ALL (array) | comparison with each element | 2 = ANY (ARRAY[1, 2]) | t |
SELECT (ARRAY[10, 20, 30])[2], ('{a,b,c}'::text[])[3], (ARRAY[1])[5], (current_schemas(true))[1];
SELECT array_length(ARRAY[1, 2, 3], 1), array_length(ARRAY[1, 2], 2), cardinality(ARRAY[1, 2]), array_upper(ARRAY[1, 2, 3], 1),
array_position(ARRAY['a', 'b'], 'b'), array_position(ARRAY['a'], 'z');
SELECT array_to_string(ARRAY[1, NULL, 3], ','), array_to_string(ARRAY[1, NULL, 3], ',', '*'), ARRAY[1, 2] = '{1,2}'::int[];
SELECT nom FROM client WHERE ville = ANY (ARRAY['Paris', 'Marseille']);20 | c | NULL | pg_catalog
3 | NULL | 2 | 3 | 2 | NULL
1,3 | 1,*,3 | t
MartinFunctions that return an array: array_agg, regexp_match, regexp_split_to_array, parse_ident, current_schemas. The array columns of the catalog (pg_proc.proargnames, pg_constraint.conkey...) are read with the same functions.
unnest#
unnest(array)Set-returning function: one row per element of the array, in order. In a FROM, WITH ORDINALITY adds the index; the array is written out (ARRAY[...], '{...}'::type[]) or passed as a parameter (unnest($1::int[])).
SELECT * FROM unnest(ARRAY['rouge', 'vert']) WITH ORDINALITY AS c(couleur, rang);
SELECT max(x), count(*) FROM unnest(ARRAY[3, 1, 2]) AS x;rouge | 1
vert | 2
3 | 3Set-returning functions in the SELECT list#
generate_series, unnest, regexp_matches, regexp_split_to_table, json[b]_array_elements[_text] and json[b]_object_keys can also be written in the SELECT list: the query then returns one row per value, each row of the source being repeated. Several functions in the same list advance together: the longest sets the number of rows, the others are padded with NULL.
SELECT unnest(ARRAY[1, 2]) AS n, unnest(ARRAY['a', 'b', 'c']) AS l;
SELECT unnest(ARRAY[id, id * 10]) AS v FROM client WHERE id <= 2;1 | a
2 | b
NULL | c
1
10
2
20For the JSON and pattern functions, an argument that cites a column requires the FROM form (0A000 in the SELECT list: jsonb_array_elements_text citing a column in the select list is not supported yet in the PostgreSQL language).
36.15 MIRAJ language functions#
An instance in the PostgreSQL language keeps the MIRAJ language functions, described in chapter 9. SQL functions. They are called with PostgreSQL lexical conventions (strings between apostrophes, identifiers between double quotes). When a PostgreSQL function has the same name, it is the one that is called: length counts characters, format reads a %s template, log(x) is decimal, trunc, to_char, version()... follow this chapter.
A few are useful where a PostgreSQL function is missing:
| MIRAJ function | Use | Example | Result |
|---|---|---|---|
last_day(date) | last day of the month | last_day(date '2026-02-10') | 2026-02-28 |
datediff(a, b), timestampdiff(unit, a, b) | difference in days, in a unit | timestampdiff(day, date '2026-10-01', date '2026-10-11') | 10 |
dayname(d), monthname(d) | name of the day, of the month | dayname(date '2026-10-11') | Sunday |
sha1(t), sha2(t, bits), crc32(t) | digests | sha1('a') | 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 |
substring_index(t, sep, n) | start of a text up to the nth separator | substring_index('a.b.c', '.', 2) | a.b |
find_in_set(t, list) | index in a comma-separated list | find_in_set('b', 'a,b') | 2 |
group_concat(expr ORDER BY ... SEPARATOR '...') | concatenation of a group | see string_agg | |
json_keys, json_remove, json_pretty, json_table... | JSON | see 36.10 | |
inet_aton(t), inet_ntoa(n) | IPv4 addresses | inet_aton('10.0.0.1') | 167772161 |
The vector functions (vec_distance_euclidean, l2_distance, cosine_distance), with the pgvector operators <-> (Euclidean distance), <=> (cosine distance), <#> (negative inner product) and <+> (Manhattan distance), the spatial functions (st_distance, st_astext...) and full-text search (MATCH (...) AGAINST (...)) are also available: see chapters 9, 10 and 29.
SELECT '[1,2]'::vector(2) <-> '[1,3]'::vector(2), '[1,2]'::vector(2) <=> '[1,3]'::vector(2), '[1,2]'::vector(2) <#> '[1,3]'::vector(2),
'[1,2]'::vector(2) <+> '[1,3]'::vector(2), l2_distance('[1,2]'::vector(2), '[1,3]'::vector(2)), cosine_distance('[1,2]'::vector(2), '[1,3]'::vector(2));1 | 0.010050506338833531 | -7 | 1 | 1 | 0.010050506338833531To compare while treating NULL as a value, write a IS NOT DISTINCT FROM b.
A view, a generated column or a constraint that cites a PostgreSQL-specific function keeps the language of its creation (see 29.10).
36.16 Unsupported features and differences#
Missing functions and operators#
Unless stated otherwise, the response is 42883 (FUNCTION ... does not exist).
| Family | Missing | Instead |
|---|---|---|
| strings | string_to_array, string_to_table, to_ascii, pg_client_encoding | regexp_split_to_array, regexp_split_to_table, unaccent |
| strings | string SIMILAR TO pattern, substring(string SIMILAR ...): 42601 | ~, LIKE |
| strings | string ^@ prefix: 42601 | starts_with(string, prefix) |
| strings | Unicode literal U&'\0041': 42703 | unistr('\0041'), chr(65) |
| numbers | sinh, cosh, tanh, asinh, acosh, atanh, sind, cosd, tand..., erf, random(a, b), num_nulls, num_nonnulls | floor(random() * n) |
| numbers | operators @ (absolute value), |/, ||/, !: 42601 | abs, sqrt, cbrt |
| comparison | BETWEEN SYMMETRIC: 42601; x LIKE ANY (ARRAY[...]) | BETWEEN least(a, b) AND greatest(a, b); OR |
| dates | (a, b) OVERLAPS (c, d): 42601 | a < d AND c < b |
| dates | date_trunc(field, t, zone) | date_trunc(field, t AT TIME ZONE zone) |
| dates | extract(timezone FROM ...): 0A000 | |
| aggregates | xmlagg, range_agg | string_agg, json_agg |
| aggregates | count(DISTINCT a, b): 42601 | count(DISTINCT a || '/' || b) |
| windows | RANGE with an offset: 0A000 (This version of Miraj doesn't yet support 'RANGE with an offset in a window frame'); GROUPS, EXCLUDE: 42601 | ROWS |
| windows | string_agg(...) OVER, json_agg(...) OVER: 0A000 | |
| JSON | @?, @@: 42601 | jsonb_path_exists, jsonb_path_match |
| JSON | jsonb_path_query, jsonb_set_lax, jsonb_object, jsonb_to_record, jsonb_populate_record, json_extract_path, row_to_json, array_to_json | jsonb_path_query_array, jsonb_extract_path, json_object, json_table, jsonb_build_object |
| arrays | column of an array type (int[]): 0A000; ARRAY(SELECT ...), || between arrays: 0A000 | array_agg |
| arrays | array_append, array_cat, array_remove, array_replace, array_prepend, array_dims, array_ndims, array_fill, trim_array, generate_subscripts | |
| system | pg_get_serial_sequence returns NULL | INSERT ... RETURNING, lastval() |
| system | pg_advisory_lock_shared, pg_advisory_unlock_all, inet_server_port, inet_client_port, pg_get_keywords | pg_advisory_lock |
Behavior differences#
These forms are accepted but do not return the PostgreSQL result.
| Form | MIRAJ | PostgreSQL |
|---|---|---|
'a' = 'a ' | true (trailing spaces ignored) | false |
greatest(1, NULL), least(...) | NULL | the NULL is ignored |
sqrt(-1), ln(0), asin(2) | NULL | error |
ceil(1.2), floor(prix) of a decimal | integer | numeric |
1 + true | 2 | error |
2.5::float8::int | 3 | 2 (rounded to even) |
brut || '{"k": 1}' (json column, untyped string) | text concatenation | error (merge for a jsonb, here too) |
| name of a computed column | the written text (upper(nom)) | the function name (upper) or ?column? |
The condensed list of what the grammar accepts or rejects is in chapter 29.