A Calculation field evaluates a SQL expression row-by-row using values from other fields in the same record. The expression must be valid LabKey SQL, but is
not a full query — keywords like SELECT, FROM, WHERE, and GROUP BY do not apply here.
Common Expression Examples
| Goal | Expression |
|---|
| Add two numeric fields | numericField1 + numericField2 |
| Subtract two numeric fields | numericField1 - numericField2 |
| Multiply two numeric fields | numericField1 * numericField2 |
| Divide (denominator never zero) | numericField1 / nonZeroField |
| Divide (denominator might be zero) | CASE WHEN denominator <> 0 THEN (numerator / CAST(denominator AS NUMERIC) * 100) ELSE NULL END |
| Difference in days between two dates | TIMESTAMPDIFF('SQL_TSI_DAY', CURDATE(), ExpirationDate) |
| Date one week after a field value | TIMESTAMPADD('SQL_TSI_WEEK', 1, CollectionDate) |
| Conditional label based on a number | CASE WHEN FreezeThawCount < 2 THEN 'Viable' ELSE 'Questionable' END |
| Conditional label based on a text match | CASE WHEN ColorField = 'Blue' THEN 'Abnormal' ELSE 'Normal' END |
| Concatenate two text fields | City || ', ' || State |
| Concatenate, guarding against NULLs | City || ', ' || COALESCE(State, '') |
| Field names with special characters | "Volume (mL)" + "Buffer Amount" |
| Fixed text value (e.g. for use with a URL property) | 'clickMe' |
| Year extracted from a date field | YEAR(CollectionDate) |
| Round a result to 2 decimal places | ROUND(Mass / CAST(Volume AS NUMERIC), 2) |
Syntax Rules
| Rule | Details |
|---|
| Field names | Use the field name directly: Volume. Wrap names that contain spaces or special characters in double quotes: "Volume (mL)", "Field/Name & More". |
| String literals | Use single quotes: 'Viable'. Escape a single quote inside a string by doubling it: 'Jim''s Sample'. |
| Case sensitivity | Field names and function names are case-insensitive. |
| Date/time literals | Use JDBC escape syntax: {d '2024-01-15'} for a date, {ts '2024-01-15 08:30:00'} for a timestamp. |
Operators
Arithmetic Operators
| Operator | Description | Example |
|---|
| + | Add | Weight1 + Weight2 |
| - | Subtract | FinalWeight - TareWeight |
| * | Multiply | Concentration * Volume |
| / | Divide | Mass / Volume |
Integer division truncates. When both operands are integer fields, the result is also an integer. Use CAST to preserve decimal precision:
CAST(NumeratorField AS NUMERIC) / DenominatorField
Dividing by a field that might be zero — guard with CASE to avoid errors:
CASE WHEN Denominator <> 0 THEN (Numerator / CAST(Denominator AS NUMERIC) * 100) ELSE NULL END
String Concatenation
Use || to join text values:
If any operand may be NULL, the result will also be NULL. Wrap nullable fields in COALESCE:
City || ', ' || COALESCE(State, '')
Comparison and Logical Operators
These are used inside CASE expressions and similar conditionals.
| Operator | Description |
|---|
| = | Equals |
| != or <> | Does not equal |
| <, >, <=, >= | Less/greater than (or equal) |
| IS NULL / IS NOT NULL | Null check |
| BETWEEN | Between two values, inclusive. Example: Score BETWEEN 80 AND 90 |
| IN / NOT IN | Match against a list. Example: Status IN ('Active', 'Pending') |
| LIKE / NOT LIKE | Pattern match. Example: SampleId LIKE 'CTL%' |
| AND, OR, NOT | Logical operators |
Conditional Logic — CASE
CASE is the primary tool for branching logic in a calculation expression.
Searched CASE evaluates boolean conditions and returns the result for the first matching branch:
CASE WHEN condition1 THEN result1 WHEN condition2 THEN result2 ELSE defaultResult END
Example with multiple branches:
CASE WHEN Score >= 90 THEN 'A'
WHEN Score >= 80 THEN 'B'
WHEN Score >= 70 THEN 'C'
ELSE 'F'
END
Simple CASE matches one field against specific values:
CASE Status WHEN 'Active' THEN 1 WHEN 'Pending' THEN 2 ELSE 0 END
The LabKey SQL parser sometimes requires additional parentheses within a CASE statement.
Numeric Functions
| Function | Description | Example |
|---|
| ROUND(value, precision) | Round to N decimal places | ROUND(Mass / Volume, 2) |
| TRUNCATE(value, precision) | Truncate to N decimal places without rounding | TRUNCATE(CAST(Temp AS NUMERIC), 1) |
| ABS(value) | Absolute value | ABS(Delta) |
| CEILING(value) | Round up to nearest integer | CEILING(Volume) |
| FLOOR(value) | Round down to nearest integer | FLOOR(Volume) |
| POWER(base, exponent) | Raise to a power | POWER(Radius, 2) |
| SQRT(value) | Square root | SQRT(Area) |
| MOD(dividend, divisor) | Remainder after division | MOD(SampleCount, 96) |
| LOG(n) | Natural logarithm | LOG(Concentration) |
| LOG10(n) | Base-10 logarithm | LOG10(Titer) |
| EXP(n) | e raised to the nth power | EXP(LogValue) |
| SIGN(value) | Returns 1, -1, or 0 based on sign | SIGN(Delta) |
String Functions
| Function | Description | Example |
|---|
| CONCAT(a, b) | Concatenate two values | CONCAT(FirstName, LastName) |
| LENGTH(string) | Character count | LENGTH(Notes) |
| SUBSTRING(string, start, length) | Extract a substring; start position is 1-based | SUBSTRING(SampleId, 1, 4) |
| LEFT(string, n) | First N characters | LEFT(Barcode, 3) |
| LCASE(string) / LOWER(string) | Convert to lowercase | LCASE(Status) |
| UCASE(string) / UPPER(string) | Convert to uppercase | UCASE(SiteCode) |
| LTRIM(string) | Remove leading whitespace | LTRIM(Label) |
| RTRIM(string) | Remove trailing whitespace | RTRIM(Label) |
| REPLACE(string, match, replacement) | Replace all occurrences | REPLACE(Name, '_', ' ') |
| LOCATE(substr, string) | Position of first match, 1-based | LOCATE('-', SampleId) |
| STARTSWITH(string, prefix) | TRUE if string starts with prefix | STARTSWITH(SampleId, 'CTL') |
| REPEAT(string, count) | Repeat a string N times | REPEAT('X', PadCount) |
Date and Time Functions
Extracting Date/Time Parts
| Function | Description | Example |
|---|
| CURDATE() | Today's date | |
| NOW() | Current date and time | |
| YEAR(date) | Year as integer | YEAR(CollectionDate) |
| MONTH(date) | Month as integer (1–12) | MONTH(CollectionDate) |
| MONTHNAME(date) | Month name as text | MONTHNAME(CollectionDate) |
| DAYOFMONTH(date) | Day of month (1–31) | DAYOFMONTH(CollectionDate) |
| DAYOFWEEK(date) | Day of week, where 1=Sunday and 7=Saturday | DAYOFWEEK(CollectionDate) |
| DAYOFYEAR(date) | Day of year (1–365) | DAYOFYEAR(CollectionDate) |
| WEEK(date) | Week of year (1–52) | WEEK(CollectionDate) |
| QUARTER(date) | Quarter (1–4) | QUARTER(CollectionDate) |
| HOUR(time) | Hour component | HOUR(CollectionTime) |
| MINUTE(time) | Minute component | MINUTE(CollectionTime) |
| SECOND(time) | Second component | SECOND(CollectionTime) |
Date Arithmetic
TIMESTAMPDIFF(interval, timestamp1, timestamp2) finds the difference between two dates at a specified interval:
TIMESTAMPDIFF('SQL_TSI_DAY', CURDATE(), ExpirationDate)
TIMESTAMPDIFF('SQL_TSI_HOUR', StartTime, EndTime) TIMESTAMPADD(interval, n, timestamp) adds an interval to a date:
TIMESTAMPADD('SQL_TSI_WEEK', 1, CollectionDate)
TIMESTAMPADD('SQL_TSI_MONTH', 6, StorageDate)Available interval values for both functions:
| Interval | Unit |
|---|
| 'SQL_TSI_FRAC_SECOND' | Fractional seconds |
| 'SQL_TSI_SECOND' | Seconds |
| 'SQL_TSI_MINUTE' | Minutes |
| 'SQL_TSI_HOUR' | Hours |
| 'SQL_TSI_DAY' | Days |
| 'SQL_TSI_WEEK' | Weeks |
| 'SQL_TSI_MONTH' | Months |
| 'SQL_TSI_QUARTER' | Quarters |
| 'SQL_TSI_YEAR' | Years |
PostgreSQL note: TIMESTAMPDIFF does not support SQL_TSI_FRAC_SECOND, SQL_TSI_WEEK, SQL_TSI_MONTH, SQL_TSI_QUARTER, or SQL_TSI_YEAR. Use the age functions for month/year differences:
- AGE_IN_MONTHS(date1, date2)
- AGE_IN_YEARS(date1, date2)
NULL Handling
| Function | Description | Example |
|---|
| COALESCE(val1, val2, ...) | Returns the first non-NULL value in the list | COALESCE(FinalVolume, EstimatedVolume, 0) |
| IFNULL(value, default) | Returns the default if value is NULL | IFNULL(Units, 0) |
| NULLIF(a, b) | Returns NULL if a equals b, otherwise returns a | NULLIF(ErrorCode, 0) |
| ISEQUAL(a, b) | TRUE if a equals b, or if both are NULL | ISEQUAL(StatusA, StatusB) |
Type Casting
Use CAST to convert a value to a specific data type. This is especially important when dividing integer fields — without casting, LabKey SQL returns a truncated integer result.
CAST(TextField AS INTEGER)
CAST(IntegerField AS NUMERIC)
CAST(DateTimeField AS DATE)
CAST(NumberField AS VARCHAR)
CAST(Value AS NUMERIC(10, 2))
Common cast targets: INTEGER, NUMERIC, DECIMAL, FLOAT, DOUBLE, VARCHAR, DATE, TIMESTAMP, BIGINT, SMALLINT