🔢

PostgreSQL Math & Numeric Functions

Complete reference for PostgreSQL math and numeric functions covering arithmetic, rounding, trigonometry, logarithms, and number theory. Every function includes syntax, parameters, real-world SQL examples, and pro tips. Updated for PostgreSQL 16.

34 functions

What are PostgreSQL Math & Numeric Functions?

PostgreSQL math functions are built-in functions that perform numerical calculations on integer, float, and numeric types. They cover rounding (round, ceil, floor), trigonometry (sin, cos, tan), logarithms, power operations, and random number generation. Common PostgreSQL numeric functions include round(), trunc(), abs(), sqrt(), log(), mod(), and power().

abs

PG 8.0+same as input

Returns the absolute (non-negative) value of a number.

DeveloperData Eng

Signature

abs ( numeric_type ) → numeric_type

Parameters

ParameterTypeDescription
xnumericAny numeric value

Examples

sql
SELECT abs(-17.4);
17.4
sql
SELECT abs(17.4);
17.4
sql
SELECT abs(-2147483648::bigint);
2147483648
sql
SELECT id, amount FROM adjustments WHERE abs(amount) > 10000 ORDER BY abs(amount) DESC;
Rows with large positive or negative adjustments
sql
SELECT avg(abs(actual - forecast)) AS mae FROM predictions;
Mean absolute error of forecasts
Anti-PatternUsing abs() in a WHERE clause on an indexed column

Writing `WHERE abs(delta) < 5` prevents PostgreSQL from using an index on `delta`. The planner cannot invert a function wrapper to perform an index seek.

✓ Instead: Rewrite as `WHERE delta BETWEEN -5 AND 5` to allow a btree index scan on `delta`.

Use `abs(a - b)` to compute unsigned distance between two values. This is common in tolerance checks: `WHERE abs(measured - expected) < 0.001` is cleaner than writing both `> -0.001 AND < 0.001`.

example
SELECT id FROM readings WHERE abs(value - target) > tolerance;
Rows where deviation exceeds tolerance

cbrt

PG 8.0+double precision

Returns the cube root of a number.

DeveloperData Eng

Signature

cbrt ( double precision ) → double precision

Parameters

ParameterTypeDescription
xdouble precisionValue to find the cube root of

Examples

sql
SELECT cbrt(27.0);
3
sql
SELECT cbrt(125.0);
5
sql
SELECT cbrt(-8.0);
-2
sql
SELECT product_id, cbrt(volume_ml) AS equiv_side_mm FROM products WHERE shape = 'cube';
Side length of equivalent cube for each product
Anti-PatternUsing power(x, 1.0/3.0) instead of cbrt

`power(x, 1.0/3.0)` computes 1.0/3.0 as a floating-point approximation (~0.3333333333333333), which can introduce rounding error. `cbrt` uses a dedicated algorithm that is more accurate for this specific case.

✓ Instead: Use `cbrt(x)` for cube roots rather than `power(x, 1.0/3.0)`.

When scaling a 3D volume proportionally, use `cbrt(volume)` to find the side length of an equivalent cube. More accurate than `power(x, 1.0/3.0)` which can accumulate floating-point error.

example
SELECT cbrt(volume_cm3) AS side_length FROM objects;
Side length in cm
See also:sqrtpowerexp

ceil

PG 8.0+same as input

Returns the smallest integer greater than or equal to the argument (rounds up).

DeveloperData EngDBA

Signatures

ceil ( numeric ) → numeric
ceil ( double precision ) → double precision

Parameters

ParameterTypeDescription
xnumeric or double precisionValue to round up

Examples

sql
SELECT ceil(42.2);
43

Ceiling of a negative rounds toward zero

sql
SELECT ceil(-42.8);
-42
sql
SELECT ceil(5.0);
5
sql
SELECT ceil(bytes_used::numeric / (1024 * 1024)) AS mb_required FROM file_uploads;
Storage in whole MB, always rounded up
Anti-PatternUsing ceil/floor for integer division when counting buckets

Developers sometimes use `ceil(n / k)` expecting pages or chunk counts, but forget that integer division of two integers in SQL truncates before `ceil` ever sees it. `ceil(7 / 2)` = `ceil(3)` = 3, not 4.

✓ Instead: Cast at least one operand to numeric first: `ceil(n::numeric / k)`.

Use `ceil(total_rows::numeric / page_size)` to compute total pages. This avoids the classic integer division truncation bug and handles cases where total_rows is not a multiple of page_size.

example
SELECT ceil(count(*)::numeric / 25) AS total_pages FROM orders;
Correct page count for 25-item pages

ceiling

PG 8.0+same as input

Alias for ceil — returns the smallest integer greater than or equal to the argument.

DeveloperData EngDBA

Signatures

ceiling ( numeric ) → numeric
ceiling ( double precision ) → double precision

Parameters

ParameterTypeDescription
xnumeric or double precisionValue to round up

Examples

sql
SELECT ceiling(42.2);
43
sql
SELECT ceiling(-42.8);
-42

Any positive fraction rounds up to 1

sql
SELECT ceiling(0.001);
1
sql
SELECT order_id, ceiling(weight_kg) AS billable_kg FROM shipments;
Freight billing always rounds up to next whole kg
Anti-PatternMixing ceil and ceiling in the same codebase

Using both `ceil` and `ceiling` interchangeably across a codebase creates inconsistency that confuses code reviewers and makes grep-based audits harder.

✓ Instead: Pick one form — preferably `ceil` for brevity — and apply it consistently via a linting rule or style guide.

Both `ceil` and `ceiling` are SQL-standard. Prefer `ceil` for brevity in application code, `ceiling` when writing SQL that must be readable by non-developers.

example
SELECT ceiling(price / 100.0) * 100 AS rounded_up_price FROM products;
Price rounded up to nearest 100

degrees

PG 8.0+double precision

Converts radians to degrees.

DeveloperData Eng

Signature

degrees ( double precision ) → double precision

Parameters

ParameterTypeDescription
radiansdouble precisionAngle in radians

Examples

sql
SELECT degrees(pi());
180
sql
SELECT degrees(pi() / 2);
90
sql
SELECT degrees(0.523598775598);
30.0000000000001
sql
SELECT sensor_id, degrees(atan2(acc_y, acc_x)) AS tilt_deg FROM imu_readings;
Tilt angle in human-readable degrees from accelerometer data
Anti-PatternStoring angles in radians and converting on every read

Storing raw radian values from a library and calling `degrees()` in every SELECT adds per-row overhead and makes ad-hoc queries confusing.

✓ Instead: Store angles in degrees in the database (the human-friendly unit) and convert to radians only when calling trig functions with `radians()`. Alternatively, use the degree-native trig variants `sind`, `cosd`, `tand`.

Many geometry libraries return angles in radians. Wrap results in `degrees()` before displaying to users, and use `radians()` when accepting degree input for trig functions.

example
SELECT degrees(atan2(dy, dx)) AS bearing_deg FROM vectors;
Bearing in human-readable degrees

div

PG 8.0+numeric

Returns the integer quotient of y divided by x, truncating toward zero.

DeveloperData Eng

Signature

div ( y numeric, x numeric ) → numeric

Parameters

ParameterTypeDescription
ynumericDividend
xnumericDivisor

Examples

sql
SELECT div(9, 4);
2

Truncates toward zero, not floor

sql
SELECT div(-9, 4);
-2

div + mod = original

sql
SELECT div(9, 4), mod(9, 4);
2 | 1
sql
SELECT user_id, div(points_earned, 500) AS reward_vouchers, mod(points_earned, 500) AS leftover_points FROM loyalty;
Vouchers issued and residual points per user
Anti-PatternUsing div() on non-integer numeric types and expecting exact integer semantics

`div(9.9, 3.3)` may not return 3 due to floating-point representation of the inputs — numeric arithmetic can leave a tiny fractional residue that causes div to return 2.

✓ Instead: Cast inputs to integer or bigint before using `div` when operating on whole-number values stored as numeric: `div(y::bigint, x::bigint)`.

Use `div(n, k)` and `mod(n, k)` together to split `n` items into `k`-sized groups: `div` gives full groups, `mod` tells you how many items are left over.

example
SELECT div(items, 12) AS full_dozens, mod(items, 12) AS remainder FROM inventory;
Full dozens and leftover count
See also:modtruncfloor

exp

PG 8.0+same as input

Returns e raised to the power of the argument (the exponential function).

DeveloperData Eng

Signatures

exp ( numeric ) → numeric
exp ( double precision ) → double precision

Parameters

ParameterTypeDescription
xnumeric or double precisionExponent value

Examples

sql
SELECT exp(1.0);
2.71828182845904523536
sql
SELECT exp(0);
1
sql
SELECT exp(ln(10));
10.00000000000000000000
sql
SELECT cohort, round(initial_users * exp(-churn_rate * months_elapsed), 0) AS predicted_active FROM cohort_analysis;
Predicted retained users under exponential decay model
Anti-PatternUsing exp() with very large arguments without overflow checks

`exp(710)` overflows double precision and returns an error or infinity. ETL pipelines ingesting raw model outputs can crash when an unbounded coefficient exceeds ~709.

✓ Instead: Clamp the input before passing it to exp: `exp(LEAST(coef, 700))`, and document why the clamp is safe in your domain.

Use `exp()` and `ln()` for compound growth calculations. For example, `initial * exp(rate * periods)` gives continuous compounding. This is more numerically stable than `power(1 + rate, periods)` for small rates.

example
SELECT round(1000 * exp(0.05 * years), 2) AS value FROM growth_periods;
Continuously compounded value
See also:lnlogpower

factorial

PG 14+numeric

Returns the factorial of a non-negative integer.

DeveloperData Eng

Signature

factorial ( bigint ) → numeric

Parameters

ParameterTypeDescription
nbigintNon-negative integer; must be >= 0

Examples

sql
SELECT factorial(5);
120
sql
SELECT factorial(0);
1
sql
SELECT factorial(10);
3628800
sql
SELECT n, k, factorial(n) / (factorial(k) * factorial(n - k)) AS combinations FROM combo_params;
C(n,k) — combinations without repetition
Anti-PatternComputing factorials for large n directly

`factorial(25)` returns a 26-digit numeric that then gets divided by another large factorial — intermediate overflow is likely when both n and k are large, and the computation is much slower than using logarithms.

✓ Instead: For large combinatoric computations, use the log-gamma trick: `round(exp(lgamma((n+1)::float) - lgamma((k+1)::float) - lgamma((n-k+1)::float)))::bigint`.

Factorials grow extremely fast: factorial(20) already exceeds 2^63. For combinations/permutations, prefer `exp(lgamma(n+1) - lgamma(k+1) - lgamma(n-k+1))` for large n to avoid overflow.

example
SELECT factorial(n) / (factorial(k) * factorial(n - k)) AS combinations FROM params;
C(n, k) combinations
See also:gcdlcm

floor

PG 8.0+same as input

Returns the largest integer less than or equal to the argument (rounds down).

DeveloperData EngDBA

Signatures

floor ( numeric ) → numeric
floor ( double precision ) → double precision

Parameters

ParameterTypeDescription
xnumeric or double precisionValue to round down

Examples

sql
SELECT floor(42.8);
42

Floor of negative rounds away from zero

sql
SELECT floor(-42.2);
-43
sql
SELECT floor(5.0);
5
sql
SELECT floor(age_years / 10) * 10 AS age_decade, count(*) FROM users GROUP BY 1 ORDER BY 1;
User counts by decade (20s, 30s, 40s...)
Anti-PatternUsing floor for negative quotients when trunc is intended

`floor(-7.0 / 2)` = `floor(-3.5)` = -4, not -3. If you want integer division that truncates toward zero (the behavior most programmers expect), `floor` gives the wrong answer for negative dividends.

✓ Instead: Use `trunc(y / x)` or the `div(y, x)` function when you want truncation-toward-zero semantics for integer quotients.

For time bucketing without `date_trunc`, use `floor(extract(epoch from ts) / 3600) * 3600` to get the start of each hour as a Unix timestamp, then cast back. `date_trunc` is cleaner for timestamps, but `floor` is useful for numeric epoch bucketing.

example
SELECT floor(score / 10) * 10 AS score_band, count(*) FROM results GROUP BY 1;
Score bands: 0, 10, 20, 30...

gcd

PG 13+same as input

Returns the greatest common divisor of two integers; returns 0 if both are zero.

DeveloperData Eng

Signature

gcd ( numeric_type, numeric_type ) → numeric_type

Parameters

ParameterTypeDescription
ainteger/bigint/numericFirst value
binteger/bigint/numericSecond value

Examples

sql
SELECT gcd(1071, 462);
21
sql
SELECT gcd(0, 42);
42
sql
SELECT gcd(100, 75);
25
sql
SELECT num, den, num/gcd(num,den) AS reduced_num, den/gcd(num,den) AS reduced_den FROM ratio_table WHERE gcd(num,den) > 1;
Fractions with reducible ratios, shown in simplified form
Anti-PatternCalling gcd with floating-point arguments

`gcd` requires integer types (integer, bigint, or numeric with scale 0). Passing a non-integer numeric like `gcd(1.5, 2.5)` raises an error because GCD is not defined for reals.

✓ Instead: Cast to integer before calling: `gcd(val1::bigint, val2::bigint)`, and only when your domain guarantees integer values.

Use `gcd` to reduce fractions: `numerator / gcd(numerator, denominator)` and `denominator / gcd(numerator, denominator)` give the simplified form without any application logic.

example
SELECT n/gcd(n,d) AS num, d/gcd(n,d) AS den FROM fractions;
Simplified fraction components
See also:lcmmoddiv

lcm

PG 13+same as input

Returns the least common multiple of two integers; returns 0 if either is zero.

DeveloperData Eng

Signature

lcm ( numeric_type, numeric_type ) → numeric_type

Parameters

ParameterTypeDescription
ainteger/bigint/numericFirst value
binteger/bigint/numericSecond value

Examples

sql
SELECT lcm(1071, 462);
23562
sql
SELECT lcm(4, 6);
12
sql
SELECT lcm(0, 100);
0
sql
SELECT job_a_interval, job_b_interval, lcm(job_a_interval, job_b_interval) AS next_overlap_minutes FROM cron_pairs;
Minutes until two recurring jobs next run at the same time
Anti-PatternUsing lcm on large numbers without overflow awareness

`lcm(a, b)` can produce a result much larger than either input — up to `a * b`. For large bigint inputs (e.g., scheduling intervals in seconds over years), the result can overflow bigint.

✓ Instead: Check `a * b / gcd(a, b) <= 9223372036854775807` before calling, or use numeric type inputs when overflow is possible.

Use `lcm` to find when two recurring events next coincide. If event A repeats every 4 days and event B every 6 days, `lcm(4, 6) = 12` tells you they align every 12 days.

example
SELECT lcm(period_a, period_b) AS sync_interval FROM schedules;
Days until schedules align
See also:gcdmoddiv

ln

PG 8.0+same as input

Returns the natural logarithm (base e) of a positive number.

DeveloperData Eng

Signatures

ln ( numeric ) → numeric
ln ( double precision ) → double precision

Parameters

ParameterTypeDescription
xnumeric or double precisionPositive value

Examples

sql
SELECT ln(2.718281828);
0.9999999998311266
sql
SELECT ln(1);
0
sql
SELECT ln(exp(1));
1
sql
SELECT product_id, round(ln(view_count + 1)::numeric, 3) AS log_views FROM product_stats ORDER BY log_views DESC LIMIT 20;
Top 20 products by log-scaled view count, compressing viral outliers
Anti-PatternCalling ln() on a column that can contain zero or negative values

`ln(0)` and `ln(-1)` both throw errors. If your column contains zeroes or negatives (e.g., a balance that can go negative), the query will fail at runtime for those rows.

✓ Instead: Guard with `NULLIF` or a CASE: `ln(NULLIF(GREATEST(value, 0), 0))` to return NULL instead of an error, and document that negative values are excluded.

When plotting or comparing values that span many orders of magnitude (prices, counts), apply `ln(value + 1)` to compress the range. The `+ 1` prevents ln(0) errors for zero values.

example
SELECT ln(revenue + 1) AS log_revenue FROM sales ORDER BY log_revenue DESC;
Log-scaled revenue for comparison
See also:exploglog10

log

PG 8.0+same as input

Returns the base-10 logarithm of a number, or the base-b logarithm when two arguments are given.

DeveloperData Eng

Signatures

log ( numeric ) → numeric
log ( double precision ) → double precision
log ( b numeric, x numeric ) → numeric

Parameters

ParameterTypeDescription
bnumericLogarithm base (optional; defaults to 10)
xnumeric or double precisionPositive value

Examples

sql
SELECT log(100.0);
2
sql
SELECT log(1000.0);
3
sql
SELECT log(2, 64.0);
6.0000000000000000
sql
SELECT asset_id, round(log(file_size_bytes)::numeric, 2) AS log10_size FROM media_assets ORDER BY file_size_bytes DESC;
Log10-scaled file sizes for range-compressed display
Anti-PatternConfusing log() base between PostgreSQL and application code

In most programming languages `log(x)` means natural log (base e). In PostgreSQL `log(x)` means base-10 log. Porting formulas from Python or JavaScript without adjustment silently produces wrong results.

✓ Instead: Use `ln(x)` for natural log in PostgreSQL. Reserve `log(x)` only when you explicitly want base-10, and add a comment noting the distinction.

Use `floor(log(n)) + 1` to count decimal digits without string casting. Works for any positive integer and is faster than `length(n::text)` for large sets.

example
SELECT n, floor(log(n)) + 1 AS digit_count FROM numbers WHERE n > 0;
Number of decimal digits
See also:lnexplog10

min_scale

PG 13+integer

Returns the minimum scale (number of fractional decimal digits) needed to represent the value exactly.

DeveloperData EngDBA

Signature

min_scale ( numeric ) → integer

Parameters

ParameterTypeDescription
xnumericNumeric value to inspect

Examples

sql
SELECT min_scale(8.4100);
2
sql
SELECT min_scale(8.0);
0
sql
SELECT min_scale(1.23456789);
8
sql
SELECT column_name, max(min_scale(col_value)) AS max_precision_needed FROM data_audit GROUP BY column_name;
Minimum decimal precision needed per column — useful for schema sizing
Anti-PatternUsing min_scale on double precision columns

`min_scale` only accepts the `numeric` type. Passing a `float8` value causes a type error, which surprises developers who assume it works on all numeric types.

✓ Instead: Cast to numeric first: `min_scale(value::numeric)`. Be aware that float-to-numeric conversion may introduce representation artefacts.

Combine `min_scale` with `round` to strip trailing zeros: `round(x, min_scale(x))`. Useful for formatting prices or measurements that may have unnecessary decimal places.

example
SELECT round(value, min_scale(value)) AS trimmed FROM measurements;
Numeric values without trailing zeros

mod

PG 8.0+same as input

Returns the remainder of y divided by x; returns 0 if x divides y exactly.

DeveloperData Eng

Signature

mod ( y numeric_type, x numeric_type ) → numeric_type

Parameters

ParameterTypeDescription
ynumeric_typeDividend
xnumeric_typeDivisor

Examples

sql
SELECT mod(9, 4);
1

Sign matches the dividend

sql
SELECT mod(-9, 4);
-1
sql
SELECT mod(10, 5);
0
sql
SELECT id, created_at FROM events WHERE mod(extract(hour FROM created_at)::int, 6) = 0;
Events that occurred at 0, 6, 12, or 18 hours (every 6-hour boundary)
Anti-PatternExpecting positive results from mod with negative dividends

`mod(-3, 10)` returns -3 in PostgreSQL, not 7. Code that uses mod to compute a hash bucket index will map negative IDs to negative bucket numbers, causing array-out-of-bounds errors.

✓ Instead: Use `((y % x) + x) % x` or `mod(mod(y, x) + x, x)` to guarantee a non-negative result regardless of the dividend's sign.

Use `mod(id, 2) = 0` to select even-ID rows for sampling or batched processing. Also useful in sharding: `mod(user_id, num_shards)` determines which shard owns a row.

example
SELECT * FROM large_table WHERE mod(id, 10) = 0 LIMIT 1000;
10% sample of rows
See also:divtruncfloor

pi

PG 8.0+double precision

Returns the mathematical constant π (pi) as a double precision value.

DeveloperData Eng

Signature

pi ( ) → double precision

Examples

sql
SELECT pi();
3.141592653589793
sql
SELECT 2 * pi() * 5 AS circumference;
31.41592653589793
sql
SELECT pi() * 3^2 AS circle_area;
28.274333882308138
sql
SELECT zone_id, pi() * radius_m^2 AS coverage_area_m2 FROM cell_towers ORDER BY coverage_area_m2 DESC;
Coverage area in m² for each cell tower radius
Anti-PatternHard-coding 3.14159 instead of using pi()

Embedding a truncated constant like `3.14159` or `3.1415926` in queries introduces rounding error that compounds in formulas. It also makes the intent less clear.

✓ Instead: Always use `pi()` to get the full double-precision constant, or `radians(180)` as an equivalent expression.

When computing great-circle distance without PostGIS, the Haversine formula uses pi() for radian conversion. Store all angles in degrees in your DB and convert with `radians()` inside the formula.

example
SELECT 2 * 6371 * asin(sqrt(sin(radians((lat2-lat1)/2))^2 + cos(radians(lat1))*cos(radians(lat2))*sin(radians((lon2-lon1)/2))^2)) AS km FROM routes;
Haversine distance in kilometers

power

PG 8.0+same as input

Returns a raised to the power of b.

DeveloperData Eng

Signatures

power ( a double precision, b double precision ) → double precision
power ( a numeric, b numeric ) → numeric

Parameters

ParameterTypeDescription
anumeric or double precisionBase
bnumeric or double precisionExponent

Examples

sql
SELECT power(9, 3);
729
sql
SELECT power(2, 10);
1024
sql
SELECT power(100, 0.5);
10
sql
SELECT year, round(initial_value * power(1 + annual_rate, year - base_year), 2) AS projected FROM projections;
Compound-interest projection for each future year
Anti-PatternUsing power() for squaring inside an index-eligible WHERE clause

`WHERE power(x, 2) < 100` wraps the column in a function, preventing index use on `x`. For a simple square, the planner cannot invert `power(x, 2)` to a range scan.

✓ Instead: Rewrite as `WHERE x BETWEEN -10 AND 10` (i.e., manually invert the inequality) to allow an index scan, given that x is non-negative or you handle both signs.

PostgreSQL supports the `^` operator as an alias for `power`. Use `power()` in portable SQL and `^` for quick interactive queries.

example
SELECT 2^32 AS max_uint32;
4294967296
See also:sqrtcbrtexpln

radians

PG 8.0+double precision

Converts degrees to radians.

DeveloperData Eng

Signature

radians ( double precision ) → double precision

Parameters

ParameterTypeDescription
degreesdouble precisionAngle in degrees

Examples

sql
SELECT radians(180);
3.141592653589793
sql
SELECT radians(90);
1.5707963267948966
sql
SELECT sin(radians(30));
0.49999999999999994
sql
SELECT origin_id, dest_id, 2 * 6371 * asin(sqrt(power(sin(radians((dest_lat - orig_lat)/2)), 2) + cos(radians(orig_lat)) * cos(radians(dest_lat)) * power(sin(radians((dest_lon - orig_lon)/2)), 2))) AS distance_km FROM route_pairs;
Great-circle distance in km between origin and destination pairs
Anti-PatternForgetting to convert degrees when using trig functions

`sin(90)` does NOT return 1 in PostgreSQL — it interprets 90 as radians (~5156°), returning -0.894. This is one of the most common silent errors when porting formulas from spreadsheets.

✓ Instead: Write `sin(radians(90))` or use the degree-native `sind(90)` which returns exactly 1.

PostgreSQL's `sin`, `cos`, `tan` and friends take radians. Use `radians()` to convert user-facing degree input. Alternatively use the degree variants `sind`, `cosd`, `tand` that accept degrees natively.

example
SELECT cos(radians(angle_deg)) AS cos_value FROM observations;
Cosine from degree input

random

PG 8.0+double precision

Returns a random double precision value in the range 0.0 <= x < 1.0.

DeveloperData Eng

Signature

random ( ) → double precision

Examples

sql
SELECT random();
0.5972757217 (varies)
sql
SELECT floor(random() * 100 + 1)::int AS rand_1_to_100;
42 (varies)
sql
SELECT * FROM users ORDER BY random() LIMIT 5;
5 random rows
sql
INSERT INTO test_orders (user_id, amount, status) SELECT (random() * 9999 + 1)::int, round((random() * 500)::numeric, 2), CASE WHEN random() < 0.8 THEN 'completed' ELSE 'pending' END FROM generate_series(1, 10000);
10 000 synthetic orders with realistic status distribution
Anti-PatternUsing random() to generate security-sensitive tokens or passwords

`random()` uses a predictable PRNG seeded at session start. An attacker who can infer or observe the seed can predict all future values, making tokens generated this way cryptographically insecure.

✓ Instead: Use `encode(gen_random_bytes(16), 'hex')` from the `pgcrypto` extension, or PostgreSQL 17+ built-in `random_uuid()` / `gen_random_uuid()` for secure random identifiers.

`ORDER BY random()` forces a full table scan. For large tables, use `WHERE random() < 0.01` (keeps ~1%) for fast approximate sampling, or `TABLESAMPLE BERNOULLI(1)` for page-level sampling that skips most I/O.

example
SELECT * FROM events TABLESAMPLE BERNOULLI(1);
~1% of rows, very fast

random_normal

PG 16+double precision

Returns a random value drawn from a normal (Gaussian) distribution with the given mean and standard deviation (defaults: mean=0, stddev=1).

DeveloperData Eng

Signature

random_normal ( [ mean double precision [, stddev double precision ]] ) → double precision

Parameters

ParameterTypeDescription
meandouble precisionMean of the distribution (default 0)
stddevdouble precisionStandard deviation (default 1)

Examples

sql
SELECT random_normal();
-0.23 (varies)

IQ-like distribution

sql
SELECT random_normal(100, 15);
108.7 (varies)
sql
SELECT round(random_normal(0, 1)::numeric, 2) FROM generate_series(1,5);
Simulated normal variates
sql
INSERT INTO synthetic_sensors (device_id, temperature_c, ts) SELECT device_id, random_normal(22.0, 1.5), now() - (random() * interval '30 days') FROM devices CROSS JOIN generate_series(1, 100);
100 synthetic temperature readings per device, normally distributed around 22°C
Anti-PatternPassing a negative stddev to random_normal

`random_normal(0, -1)` raises an error because a negative standard deviation is not statistically valid. ETL code that computes stddev from data and passes it directly can fail when data is degenerate.

✓ Instead: Guard with `GREATEST(computed_stddev, 0.0001)` to ensure a small positive value before passing to `random_normal`.

Use `random_normal(mean, stddev)` to populate test tables with realistic distributions. For prices, try `abs(random_normal(50, 15))`. For response times, `abs(random_normal(200, 50))` gives millisecond-like values.

example
INSERT INTO test_metrics SELECT random_normal(150, 30) FROM generate_series(1, 10000);
10 000 rows with realistic latency distribution
See also:randomsetseed

round

PG 8.0+same as input

Rounds a number to the nearest integer, or to s decimal places when s is specified.

DeveloperData EngDBA

Signatures

round ( numeric ) → numeric
round ( double precision ) → double precision
round ( v numeric, s integer ) → numeric

Parameters

ParameterTypeDescription
xnumeric or double precisionValue to round
sintegerNumber of decimal places (optional; negative rounds left of decimal)

Examples

sql
SELECT round(42.4);
42
sql
SELECT round(42.5);
43
sql
SELECT round(1234.5678, 2);
1234.57

Negative s rounds left of decimal

sql
SELECT round(1234.5678, -2);
1200
Anti-PatternCalling round() without a precision argument on double precision and expecting .5 to round up

`round(0.5::double precision)` may return 0 or 1 depending on the platform's IEEE 754 "round half to even" implementation. This surprises developers who expect 0.5 to always round to 1.

✓ Instead: Cast to numeric for deterministic rounding: `round(0.5::numeric)` always returns 1 in PostgreSQL (half-up rounding).

For `double precision` inputs, `round` uses the platform's rounding, which may differ from numeric. Cast to `numeric` when you need exact rounding: `round(value::numeric, 2)`. This is critical for financial calculations.

example
SELECT round(2.5::numeric, 0), round(3.5::numeric, 0);
3, 4 — rounds half-up consistently

sign

PG 8.0+same as input

Returns the sign of the argument: -1, 0, or 1.

DeveloperData Eng

Signature

sign ( numeric_type ) → numeric_type

Parameters

ParameterTypeDescription
xnumeric_typeAny numeric value

Examples

sql
SELECT sign(-8.4);
-1
sql
SELECT sign(0);
0
sql
SELECT sign(8.4);
1
sql
SELECT transaction_id, amount, sign(amount) AS direction FROM ledger WHERE account_id = 42 ORDER BY created_at;
Direction of each ledger entry: -1 debit, 0 zero, 1 credit
Anti-PatternUsing sign() to replace a three-way CASE and then comparing the result as a boolean

`WHERE sign(balance) = true` does not work — sign returns a numeric (-1, 0, 1), not a boolean. Developers coming from languages where -1 is truthy sometimes write this and get a type error or wrong results.

✓ Instead: Compare explicitly: `WHERE sign(balance) > 0` for positive, `< 0` for negative, `= 0` for zero.

Use `sign(delta) * target_magnitude` to produce a vector in the same direction as delta but with a fixed magnitude — without a CASE expression. Also handy for encoding positive/negative trends as -1/0/1 labels.

example
SELECT sign(revenue - prev_revenue) AS trend FROM quarterly_results;
-1, 0, or 1 indicating decline, flat, or growth

sqrt

PG 8.0+same as input

Returns the square root of a non-negative number.

DeveloperData Eng

Signatures

sqrt ( numeric ) → numeric
sqrt ( double precision ) → double precision

Parameters

ParameterTypeDescription
xnumeric or double precisionNon-negative value

Examples

sql
SELECT sqrt(2);
1.4142135623730951
sql
SELECT sqrt(100);
10
sql
SELECT sqrt(0);
0
sql
SELECT sample_id, round(sqrt(sum(power(value - avg_val, 2)) / count(*))::numeric, 4) AS stddev_manual FROM (SELECT sample_id, value, avg(value) OVER (PARTITION BY sample_id) AS avg_val FROM readings) s GROUP BY sample_id;
Manually computed standard deviation per sample using sqrt of variance
Anti-PatternUsing sqrt() inside a WHERE clause to compare distances

`WHERE sqrt((x-px)^2 + (y-py)^2) < radius` evaluates sqrt for every row. For pure distance comparisons, this is unnecessary — the square root is monotonic, so you can skip it.

✓ Instead: Compare squared distances directly: `WHERE (x-px)^2 + (y-py)^2 < radius^2`. This avoids calling sqrt entirely and enables faster execution on large tables.

Use `sqrt((x2-x1)^2 + (y2-y1)^2)` for 2D distance. For integer inputs, cast to numeric first for exact results. Use `|/` as a shorthand operator: `|/ 2.0` = sqrt(2).

example
SELECT sqrt((x2-x1)^2 + (y2-y1)^2) AS distance FROM points;
Euclidean distance between points
See also:cbrtpowerexp

trunc

PG 8.0+same as input

Truncates a number toward zero to the nearest integer, or to s decimal places.

DeveloperData EngDBA

Signatures

trunc ( numeric ) → numeric
trunc ( double precision ) → double precision
trunc ( v numeric, s integer ) → numeric

Parameters

ParameterTypeDescription
xnumeric or double precisionValue to truncate
sintegerNumber of decimal places (optional)

Examples

sql
SELECT trunc(42.8);
42

Truncates toward zero, unlike floor

sql
SELECT trunc(-42.8);
-42
sql
SELECT trunc(42.4382, 2);
42.43
sql
SELECT invoice_id, amount_cents, trunc(amount_cents::numeric / 100, 2) AS amount_dollars FROM invoices;
Whole-cent dollar amounts without any rounding — safe for ledger display
Anti-PatternUsing trunc to extract the integer part and then adding it back to a fractional part computed separately

Splitting a number with `trunc(x)` for the integer and `x - trunc(x)` for the fraction and then recombining can accumulate floating-point error, especially for double precision values.

✓ Instead: If you need both integer and fractional components, use `trunc` and `x - trunc(x)`, but work in `numeric` type to avoid floating-point representation issues.

`trunc(-4.7)` = -4 (toward zero) while `floor(-4.7)` = -5 (toward negative infinity). Use `trunc` when you want to discard the fractional part regardless of sign, which is typical for currency truncation.

example
SELECT trunc(price * tax_rate, 2) AS tax_truncated FROM orders;
Tax truncated to 2 decimal places (never rounds up)

width_bucket

PG 8.0+integer

Assigns the argument to a bucket in an equal-width histogram between low and high, or at a custom threshold array.

DeveloperData Eng

Signatures

width_bucket ( operand numeric, low numeric, high numeric, count integer ) → integer
width_bucket ( operand dp, low dp, high dp, count integer ) → integer
width_bucket ( operand anycompatible, thresholds anycompatiblearray ) → integer

Parameters

ParameterTypeDescription
operandnumericValue to bucket
lownumericLower bound of the histogram range
highnumericUpper bound of the histogram range
countintegerNumber of equal-width buckets

Examples

sql
SELECT width_bucket(5.35, 0.024, 10.06, 5);
3
sql
SELECT width_bucket(score, 0, 100, 10) AS decile FROM scores;
Decile 1–10 for each score
sql
SELECT width_bucket(now(), low, high, 10) FROM date_ranges;
Time bucket (works with timestamps too)
sql
SELECT width_bucket(order_total, 0, 500, 5) AS price_tier, count(*) AS orders, round(avg(order_total), 2) AS avg_value FROM orders GROUP BY 1 ORDER BY 1;
Order count and average value across 5 equal price tiers
Anti-PatternNot accounting for the overflow buckets 0 and count+1

`width_bucket` returns 0 for values below `low` and `count+1` for values above `high`. If your chart code expects buckets 1–N only, out-of-range data silently appears as bucket 0 or N+1, distorting your histogram.

✓ Instead: Add a `WHERE operand BETWEEN low AND high` filter, or explicitly handle buckets 0 and `count+1` as an 'out of range' category in your query output.

Use `width_bucket` + `GROUP BY` to produce histogram data in one query. Buckets 0 and count+1 capture values outside the range, so filter them out if needed.

example
SELECT width_bucket(amount, 0, 1000, 10) AS bucket, count(*) FROM payments GROUP BY 1 ORDER BY 1;
Histogram of payment amounts in 10 bins

sin

PG 8.0+double precision

Returns the sine of the angle given in radians.

DeveloperData Eng

Signature

sin ( double precision ) → double precision

Parameters

ParameterTypeDescription
xdouble precisionAngle in radians

Examples

sql
SELECT sin(0);
0
sql
SELECT sin(pi()/2);
1
sql
SELECT sin(radians(30));
0.49999999999999994
sql
SELECT hour, sin(2 * pi() * hour / 24) AS sin_enc, cos(2 * pi() * hour / 24) AS cos_enc FROM generate_series(0, 23) AS hour;
Cyclical sine/cosine encoding of hour-of-day for ML feature tables
Anti-PatternPassing degree values directly to sin() without converting

`sin(90)` = ~0.894, not 1 — PostgreSQL interprets the argument as radians. This is a silent error that produces plausible-looking wrong numbers.

✓ Instead: Use `sin(radians(90))` or the degree-native `sind(90)` which returns exactly 1.

If your angles are in degrees, use `sind(x)` instead of `sin(radians(x))` — it's slightly more accurate for standard angles like 90° where floating-point conversion could introduce error.

example
SELECT sind(90), sin(radians(90));
1 | 1 (both correct here, but sind is more precise)

cos

PG 8.0+double precision

Returns the cosine of the angle given in radians.

DeveloperData Eng

Signature

cos ( double precision ) → double precision

Parameters

ParameterTypeDescription
xdouble precisionAngle in radians

Examples

sql
SELECT cos(0);
1
sql
SELECT cos(pi());
-1
sql
SELECT cos(radians(60));
0.5000000000000001
sql
SELECT station_id, round((cos(radians(a.lat)) * cos(radians(b.lat)) * cos(radians(b.lon - a.lon)) + sin(radians(a.lat)) * sin(radians(b.lat)))::numeric, 6) AS dot_product FROM stations a JOIN stations b ON a.id < b.id;
Dot product component of spherical law of cosines for station pairs
Anti-PatternUsing cos() for angle comparison without accounting for floating-point near ±1

`cos(pi())` returns `-0.9999999999999998` due to floating-point precision, not exactly -1. Code that checks `cos(angle) = -1` will always be false.

✓ Instead: Use a tolerance comparison: `abs(cos(angle) - (-1)) < 1e-9`, or use the degree-native `cosd(180)` which correctly returns exactly -1.

Encode circular values (hour of day 0–23, day of week 0–6) using both sin and cos so ML models see the cyclical relationship. `cos(2*pi()*hour/24)` and `sin(2*pi()*hour/24)` keep 0h and 23h close together.

example
SELECT cos(2*pi()*hour/24) AS cos_h, sin(2*pi()*hour/24) AS sin_h FROM logs;
Cyclical encoding of hour

tan

PG 8.0+double precision

Returns the tangent of the angle given in radians.

DeveloperData Eng

Signature

tan ( double precision ) → double precision

Parameters

ParameterTypeDescription
xdouble precisionAngle in radians

Examples

sql
SELECT tan(0);
0
sql
SELECT tan(radians(45));
0.9999999999999999
sql
SELECT tan(pi()/4);
1
sql
SELECT segment_id, height_m, base_m, degrees(atan(height_m::float8 / base_m)) AS angle_deg FROM slope_segments WHERE base_m > 0;
Slope angle in degrees for road or terrain segments
Anti-PatternUsing tan() near ±90° (π/2 radians) without checking for near-vertical angles

`tan(pi()/2)` approaches ±infinity. At exactly pi/2 in floating-point, the result is a very large finite number (~1.633e16) rather than an error, silently corrupting slope calculations.

✓ Instead: Before calling `tan(angle)`, check that `abs(cos(angle)) > 1e-9`. If near-vertical angles are expected, use `atan2(dy, dx)` instead of computing tan directly.

Don't use `atan(sin/cos)` to find an angle from coordinates — use `atan2(y, x)` which handles all quadrants correctly and avoids division by zero when cos=0.

example
SELECT degrees(atan2(dy, dx)) AS angle_deg FROM vectors;
Angle in degrees, -180 to 180

asin

PG 8.0+double precision

Returns the arc sine (inverse sine) of the argument, in radians. Input must be in [-1, 1].

DeveloperData Eng

Signature

asin ( double precision ) → double precision

Parameters

ParameterTypeDescription
xdouble precisionValue in range [-1, 1]

Examples

sql
SELECT asin(0);
0
sql
SELECT asin(1);
1.5707963267948966
sql
SELECT degrees(asin(0.5));
30.000000000000004
sql
SELECT route_id, round(degrees(2 * asin(sqrt(hav)))::numeric, 4) AS central_angle_deg FROM haversine_intermediates;
Central angle in degrees from pre-computed haversine intermediate values
Anti-PatternPassing a value outside [-1, 1] to asin without clamping

Floating-point arithmetic can produce values like 1.0000000000000002 that are just outside the valid domain. `asin(1.0000000000000002)` raises an error, crashing queries in Haversine or other geometric formulas.

✓ Instead: Clamp the input before calling: `asin(GREATEST(-1.0, LEAST(1.0, computed_value)))` to absorb floating-point overshoot.

Use `asind(x)` to get the arc sine directly in degrees, avoiding the manual `degrees(asin(x))` call and the small floating-point rounding it introduces.

example
SELECT asind(0.5) AS angle;
30

atan2

PG 8.0+double precision

Returns the arc tangent of y/x, using the signs of both arguments to determine the quadrant of the result (radians).

DeveloperData Eng

Signature

atan2 ( y double precision, x double precision ) → double precision

Parameters

ParameterTypeDescription
ydouble precisionY component (numerator)
xdouble precisionX component (denominator)

Examples

sql
SELECT atan2(1, 1);
0.7853981633974483
sql
SELECT degrees(atan2(1, 0));
90
sql
SELECT degrees(atan2(-1, -1));
-135
sql
SELECT device_id, degrees(atan2(accel_y, accel_x)) AS heading_deg FROM motion_events WHERE speed_ms > 0.5 ORDER BY recorded_at;
Device heading in degrees from accelerometer x/y components
Anti-PatternConfusing argument order in atan2(y, x)

`atan2(x, y)` and `atan2(y, x)` are both valid calls but produce different angles. Swapping the arguments is a frequent bug when porting code — the correct signature is always `atan2(y, x)`.

✓ Instead: Always name your arguments explicitly in comments: `atan2(/* y */ delta_lat, /* x */ delta_lon)` to make the order self-documenting.

For geographic bearing, use `degrees(atan2(lon2 - lon1, lat2 - lat1))` as a simplified 2D approximation. For accurate geodetic bearing on a sphere, incorporate cos/sin of latitudes.

example
SELECT degrees(atan2(x2 - x1, y2 - y1)) AS bearing FROM route_segments;
Bearing in degrees from north

sinh

PG 8.0+double precision

Returns the hyperbolic sine of the argument.

DeveloperData Eng

Signature

sinh ( double precision ) → double precision

Parameters

ParameterTypeDescription
xdouble precisionAny numeric value

Examples

sql
SELECT sinh(0);
0
sql
SELECT sinh(1);
1.1752011936438014
sql
SELECT sinh(asinh(5));
5
sql
SELECT feature_id, tanh(raw_value) AS squashed FROM ml_features WHERE model_version = 3;
Feature values squashed to (-1, 1) range using tanh activation
Anti-PatternUsing sinh for large arguments without overflow awareness

`sinh(711)` overflows double precision to infinity. If your data contains large exponents (e.g., model weights that escaped normalization), sinh will silently return Infinity.

✓ Instead: Clamp input to a safe range before calling: `sinh(LEAST(ABS(x), 700) * SIGN(x))`, and validate that upstream data is properly normalized.

The tanh function (`tanh(x)`) is commonly used as a neural network activation function and to squash values to [-1, 1]. Use it in PostgreSQL to apply the same transformation inline to stored data.

example
SELECT tanh(feature_value) AS squashed FROM ml_features;
Values squashed to range (-1, 1)

scale

PG 9.1+integer

Returns the scale of the argument, that is, the number of decimal digits in the fractional part.

DeveloperDBAData Eng

Signature

scale ( numeric ) → integer

Parameters

ParameterTypeDescription
xnumericNumeric value to inspect

Examples

sql
SELECT scale(8.41);
2

Includes trailing zeros in stored precision

sql
SELECT scale(8.4100);
4
sql
SELECT scale(42);
0
sql
SELECT col_name, min(scale(val)) AS min_scale, max(scale(val)) AS max_scale FROM import_staging GROUP BY col_name;
Scale audit of incoming numeric columns to detect inconsistent precision before loading
Anti-PatternUsing scale() to validate user-entered decimal precision on double precision columns

`scale` only works on the `numeric` type. Calling `scale(some_float8_column)` raises a type error. Developers sometimes try to use it to count decimal places on float columns.

✓ Instead: Cast to numeric first (`scale(value::numeric)`), but be aware that the cast may introduce representation artefacts — prefer storing currency and precision-sensitive values as `numeric` from the start.

`scale` returns the stored scale (may include trailing zeros), while `min_scale` returns the minimum needed. Use `scale` to inspect the declared precision of a stored value, `min_scale` to find its canonical form.

example
SELECT scale(1.2300), min_scale(1.2300);
4 | 2

trim_scale

PG 13+numeric

Reduces the scale of the value by removing trailing zeros.

DeveloperDBAData Eng

Signature

trim_scale ( numeric ) → numeric

Parameters

ParameterTypeDescription
xnumericNumeric value

Examples

sql
SELECT trim_scale(8.4100);
8.41
sql
SELECT trim_scale(1.0000);
1
sql
SELECT trim_scale(3.14000);
3.14
sql
SELECT measurement_id, trim_scale(raw_value) AS clean_value FROM sensor_readings WHERE trim_scale(raw_value) <> raw_value;
Sensor readings that have unnecessary trailing zeros in stored precision
Anti-PatternApplying trim_scale in a WHERE clause predicate instead of display layer

Wrapping a column in `trim_scale()` inside a WHERE clause — e.g., `WHERE trim_scale(price) = 9.99` — disables any index on `price` and adds per-row computation.

✓ Instead: Apply `trim_scale` only in the SELECT list for display purposes. For filtering, compare the raw stored value directly: `WHERE price = 9.99000`.

Numeric arithmetic in PostgreSQL can produce values with excessive trailing zeros (e.g. division results). Wrap the output in `trim_scale()` before displaying to users.

example
SELECT trim_scale(10.0 / 4.0) AS result;
2.5 instead of 2.50000000...

setseed

PG 8.0+void

Sets the seed for subsequent random() calls; argument must be in [-1, 1].

DeveloperData Eng

Signature

setseed ( dp double precision ) → void

Parameters

ParameterTypeDescription
seeddouble precisionSeed value in range [-1, 1]

Examples

sql
SELECT setseed(0.42);
sql
SELECT setseed(0.5); SELECT random();
0.6986... (reproducible)
sql
SELECT setseed(-0.5); SELECT random();
Different but reproducible
sql
SELECT setseed(0.99); SELECT id, name FROM users ORDER BY random() LIMIT 50;
Reproducible 50-user sample — same result every time seed is set before the query
Anti-PatternRelying on setseed for reproducibility across connections or parallel workers

`setseed` sets the PRNG state for the current backend session only. In a connection-pooled application or parallel query, different connections have independent seeds. Two calls with the same seed in different sessions will produce the same sequence — but concurrent sessions will not stay in sync.

✓ Instead: For reproducible distributed sampling, use a hash-based approach instead: `ORDER BY md5(id::text || '0.42')` gives a deterministic shuffle without relying on session state.

Call `SELECT setseed(0.42)` at the start of a session to make `ORDER BY random()` queries produce the same order every run. Essential for reproducible test data generation.

example
SELECT setseed(0.1); SELECT id FROM users ORDER BY random() LIMIT 100;
Same 100 users every time seed is set first