Arithmetic Operators

The five arithmetic operators work on numbers, dates and durations — not on text. + is addition, never string concatenation; use concat for that. Anything that is not a number, a date/time value or a duration is rejected when the query is checked, rather than being coerced into whatever that particular database happens to do with it.

FIND order_details od
FETCH od.unit_price * od.quantity gross

Precedence

* and / bind tighter than + and -, as in ordinary arithmetic, and parentheses override that:

a + b * c        →  a + (b * c)
(a + b) * c      →  grouped as written

The grouping you write is the grouping that reaches the database. Parentheses are preserved through the whole pipeline, so a query cannot be quietly re-associated on the way to SQL.

Division

Division always produces a decimal, including when both operands are whole numbers:

7 / 2   →   3.5

This is worth stating because SQL itself does not agree with it. Left to their own devices, PostgreSQL, SQL Server, Trino and SQLite answer 3 — integer division truncates — while Oracle, MariaDB and DuckDB answer 3.5. The same query would give two different numbers depending on where it ran, so KQL settles it: the result is decimal everywhere. Use floor or round when you want a whole number back.

Dividing by zero gives a blank, not an error. A row whose divisor is zero yields no value for that expression, and the rest of the report is unaffected:

FIND order_details od
FETCH od.unit_price / od.quantity unit_cost

If quantity is zero for some line, unit_cost is blank for that line only. Here too the databases disagree natively — four raise an error, two return a blank, one returns infinity — so KQL settles on the blank. That means a divide-by-zero will not fail a report, but it will not announce itself either: if a zero divisor means your data is wrong, filter for it explicitly (FILTER od.quantity > 0) rather than relying on the query to complain.

Writing / 0 literally is a different matter and is rejected outright, since it can never have been intended.

Dates and durations

Arithmetic on dates and times follows its own algebra, described in full in docs/TEMPORAL.md. The short version:

o.order_date + 30d           →  a date, 30 days later
o.shipped_date - o.order_date →  a duration in whole days
3 * 2h                        →  6h

Subtracting two dates gives whole days, and subtracting two timestamps gives an exact elapsed span in clock units — never calendar months, whose length is not fixed. When the calendar breakdown is what you want, that is calendar_distance, not -. Multiplying a duration by a whole number scales each of its components; dividing a duration is not defined.

Decimal places

How many decimal places come back is the database's decision, not KQL's: the same product may print as 77, 77.0 or 77.000000000000 depending on the engine's own scale rules. The value is the same. Use round(x, 2) when the presentation matters.

Not here

There is no % operator — the remainder is the mod function, on the Mathematical Functions page, along with abs, floor, ceil, round, power and sqrt.

negate

negate(value: ARITHMETIC) → argument-dependent

Arithmetic negation (-x): negates a number, or flips the sign of every component of a DURATION.

Argument Type Description
value ARITHMETIC the number or DURATION to negate

Sample query:

// negate (-): arithmetic negation of freight.
FIND orders o
FETCH -o.freight neg_freight

Generated SQL

all dialects

-- negate (-): arithmetic negation of freight.
SELECT
  -o.freight AS neg_freight
FROM
 orders o

add

add(left: ARITHMETIC, right: ARITHMETIC, ...) → argument-dependent

Addition (+): numeric addition, or temporal arithmetic per the result-type algebra — DATE/TIMESTAMP/TIME + DURATION, DATE + TIME, and DURATION + DURATION (see docs/TEMPORAL.md).

Argument Type Description
left ARITHMETIC the left-hand operand
right ARITHMETIC the right-hand operand

Sample query:

// add (+): 30 days after the order date.
FIND orders o
FETCH o.order_date + 30d due_date

Generated SQL

duckdb · snowflake · postgresql

-- add (+): 30 days after the order date.
SELECT
  o.order_date + INTERVAL '30 day' AS due_date
FROM
 orders o

The remaining dialects differ only in this expression:

Dialect Expression
mariadb · trino o.order_date + INTERVAL '30' DAY AS due_date
mssql DATEADD(DAY, 30, o.order_date) AS due_date
oracle o.order_date + NUMTODSINTERVAL(30, 'DAY') AS due_date
sqlite date(o.order_date, '+30 days') AS due_date

minus

minus(left: ARITHMETIC, right: ARITHMETIC, ...) → argument-dependent

Subtraction (-): numeric subtraction, or temporal — DATE − DATE and TIMESTAMP − TIMESTAMP yield a DURATION, and a temporal value − DURATION shifts it (see docs/TEMPORAL.md).

Argument Type Description
left ARITHMETIC the left-hand operand
right ARITHMETIC the right-hand operand

Sample query:

// minus (-): shipping delay in whole days.
FIND orders o
FETCH o.shipped_date - o.order_date ship_delay

Generated SQL

duckdb · snowflake · postgresql

-- minus (-): shipping delay in whole days.
SELECT
  o.shipped_date - o.order_date AS ship_delay
FROM
 orders o

The remaining dialects differ only in this expression:

Dialect Expression
mariadb TIMESTAMPDIFF(DAY, o.order_date, o.shipped_date) AS ship_delay
mssql DATEDIFF(DAY, o.order_date, o.shipped_date) AS ship_delay
oracle TRUNC(o.shipped_date - o.order_date) AS ship_delay
sqlite CAST(julianday(o.shipped_date) - julianday(o.order_date) AS INTEGER) AS ship_delay
trino date_diff('day', o.order_date, o.shipped_date) AS ship_delay

multiply

multiply(left: ARITHMETIC, right: ARITHMETIC, ...) → argument-dependent

Multiplication (*): numeric multiplication, or DURATION × integer scaled component-wise (see docs/TEMPORAL.md).

Argument Type Description
left ARITHMETIC the left-hand operand
right ARITHMETIC the right-hand operand

Sample query:

// multiply (*): gross amount of a line item.
FIND order_details od
FETCH round(od.unit_price * od.quantity, 2) gross

Generated SQL

duckdb · oracle · snowflake · mssql · mariadb · sqlite · trino

-- multiply (*): gross amount of a line item.
SELECT
  round(od.unit_price * od.quantity, 2) AS gross
FROM
 order_details od

The remaining dialects differ only in this expression:

Dialect Expression
postgresql round(CAST(od.unit_price * od.quantity AS numeric), 2) AS gross

divide

divide(left: ARITHMETIC, right: ARITHMETIC, ...) → argument-dependent

Division (/): the result is always decimal, even when both operands are whole numbers (7 / 2 is 3.5, never 3). A zero divisor yields no value rather than an error. Dividing a DURATION is not defined.

Argument Type Description
left ARITHMETIC the left-hand operand
right ARITHMETIC the right-hand operand

Sample query:

// divide (/): unit cost of a line item.
FIND order_details od
FETCH od.unit_price / od.quantity unit_cost

Generated SQL

duckdb · oracle · snowflake · mssql · postgresql · mariadb · trino

-- divide (/): unit cost of a line item.
SELECT
  od.unit_price / NULLIF(od.quantity, 0) AS unit_cost
FROM
 order_details od

The remaining dialects differ only in this expression:

Dialect Expression
sqlite CAST(od.unit_price AS REAL) / NULLIF(od.quantity, 0) AS unit_cost

Results differ from the other dialects on snowflake, sqlite.