Applies to VAST Cluster 5.5.
Functions › Numeric and math functions › Sign and rounding. Returns -1 for a negative number, 0 for zero and 1 for a positive number, as a TINYINT.
Syntax
sign(x)
| Argument | Type | Description |
|---|---|---|
x |
any number: the signed and unsigned integer types, DECIMAL, FLOAT or DOUBLE |
The value to test. |
Returns: TINYINT: -1, 0 or 1.
Set up the examples
The examples connect with the ADBC driver, as in VAST Query Engine (ADBC), and work in a schema called docs_sql_fn_numeric_sign. Set QE_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and VASTDB_BUCKET (SQL reference), and run the blocks in order in one Python session. Below each block is the output you should see.
This block connects, defines the run() and plan() helpers, and creates a table of five price changes: a change (DOUBLE), a change in quantity (INTEGER) and an amount (DECIMAL). Row 4 holds a NaN change, and the last row has no values:
import os
import re
import adbc_driver_manager
import adbc_driver_manager.dbapi as dbapi
import adbc_driver_vastdb
BUCKET = os.environ["VASTDB_BUCKET"]
SCHEMA = f'"{BUCKET}/docs_sql_fn_numeric_sign"'
conn = dbapi.connect(
driver=adbc_driver_vastdb.get_driver_path(),
db_kwargs={
"vast.db.endpoint": os.environ["QE_ENDPOINT"],
"vast.db.access_key": os.environ["AWS_ACCESS_KEY_ID"],
"vast.db.secret_key": os.environ["AWS_SECRET_ACCESS_KEY"],
},
autocommit=True,
)
def server_message(e):
"""The server's message in a driver error, without the request ID or the echoed statement."""
text = re.sub(r"\[req=0x[0-9a-f]+\]: ", "", str(e))
found = (re.search(r'message: "(.*?)", request_id', text, re.S)
or re.search(r"description: (.*)\)", text, re.S))
message = found.group(1) if found else text.strip().splitlines()[-1]
message = re.sub(r"^Plan query: '.*?' failed with: ", "", message, flags=re.S)
message = re.split(r"\\n|\n", message)[0].replace('\\"', '"')
return message.replace(BUCKET, "<bucket>")
def run(sql):
"""Run one statement. Print the rows it returns, or the error."""
try:
with conn.cursor() as cur:
cur.execute(sql)
rows = cur.fetchall() if cur.description else []
except (adbc_driver_manager.Error, OSError) as e:
print(f"{type(e).__name__}: {server_message(e)}")
else:
print(rows)
def plan(sql):
"""Print the plan without its ~N Rows lines: the table's row count, which can still be 0 just after an insert."""
with conn.cursor() as cur:
cur.execute("EXPLAIN " + sql)
steps = cur.fetchall()[0][1]
print("\n".join(line for line in steps.splitlines() if "Rows" not in line))
TABLE = f"{SCHEMA}.changes"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, delta DOUBLE, qty INTEGER, amount DECIMAL(10, 2))")
run(f"INSERT INTO {TABLE} VALUES "
"(1, 2.5, 12, 2.45), "
"(2, -1.25, -18, -1.35), "
"(3, 0.0, 0, 0.00), "
"(4, 'nan'::DOUBLE, 5, 1.00), "
"(5, NULL, NULL, NULL)")
[]
[]
[(5,)]
What it does
sign(x) returns -1 when x is less than zero, 0 when it's zero, and 1 when it's greater than zero. The result is always a TINYINT, whatever the type of x: sign(-2.45) is the TINYINT -1.
On DOUBLE and FLOAT values:
sign('inf'::DOUBLE)is1andsign('-inf'::DOUBLE)is-1.- A negative zero is zero:
sign(-0.0::DOUBLE)is0. To tell-0.0from0.0, use signbit. - NaN gives
0.sign('nan'::DOUBLE)is0, sosign(x) = 0doesn't meanxis zero. Test for NaN with isnan. - The smallest values count:
sign(5e-324::DOUBLE)is1andsign(-1e-300)is-1.
NULLs and edge cases
| Input | Result |
|---|---|
x is NULL |
NULL |
sign(-0.0::DOUBLE) |
0 |
sign('nan'::DOUBLE) |
0 |
sign('inf'::DOUBLE), sign('-inf'::DOUBLE) |
1, -1 |
sign(5e-324::DOUBLE) |
1 |
sign(-2.45) |
-1, a TINYINT |
sign(0::UTINYINT) |
0 |
sign(-9223372036854775808::BIGINT) |
Error: the cast comes before the minus, and 9223372036854775808 doesn't fit a BIGINT |
A string, even '12' |
Error: Could not choose a best candidate function for the function call "sign(STRING_LITERAL)" |
A date, boolean, BLOB, list or interval |
Error: No function matches the given name and argument types |
The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:
run("SELECT sign(NULL), sign(-0.0::DOUBLE), sign('nan'::DOUBLE), sign('inf'::DOUBLE), sign('-inf'::DOUBLE)")
run("SELECT sign(5e-324::DOUBLE), sign(-2.45), typeof(sign(-2.45)), sign(0::UTINYINT)")
run("SELECT sign(-9223372036854775808::BIGINT)")
run("SELECT sign('12')")
run("SELECT sign(true)")
[(None, 0, 0, 1, -1)]
[(1, -1, 'TINYINT', 0)]
OSError: Type INT128 with value 9223372036854775808 can't be cast because the value is out of range for the destination type INT64
InternalError: Binder Error: Could not choose a best candidate function for the function call "sign(STRING_LITERAL)". In order to select one, please add explicit type casts.
InternalError: Binder Error: No function matches the given name and argument types 'sign(BOOLEAN)'. You might need to add explicit type casts.
Examples
The sign of each column. Row 4's NaN change gives 0, the same as row 3's zero:
run(f"SELECT id, sign(delta), sign(qty), sign(amount) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 1, 1, 1), (2, -1, -1, -1), (3, 0, 0, 0), (4, 0, 1, 1), (5, None, None, None)]
Label each change as up, down or flat, and tell NaN apart with isnan():
run(f"SELECT id, CASE sign(delta) WHEN 1 THEN 'up' WHEN -1 THEN 'down' ELSE 'flat' END, isnan(delta) "
f"FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 'up', False), (2, 'down', False), (3, 'flat', False), (4, 'flat', True), (5, 'flat', None)]
Keep the falling prices:
run(f"SELECT id, delta FROM {TABLE} WHERE sign(delta) = -1 ORDER BY id LIMIT 10")
[(2, -1.25)]
How it runs in VAST
The Query Engine runs on the cluster's CNodes, and a query on a table starts with a scan in VAST DataBase (VAST_SCAN). VAST DataBase can evaluate a comparison of a column with a constant, but not a function of a column, so sign() is always computed by the Query Engine:
- In the
SELECTlist, the scan reads the column and the Query Engine computessign()for each row it returns. - In
WHERE, the scan sends the column for every row, and aFILTERstep in the Query Engine computessign()and keeps the rows that match. No rows are skipped in storage.
The plan of the WHERE example shows the FILTER step above the scan:
plan(f"SELECT id FROM {TABLE} WHERE sign(delta) = -1")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (sign(delta) = -1) │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ delta │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Compare the raw column with zero.
sign(delta) = -1is the same asdelta < 0, and that form is pushed down to VAST DataBase, so rows that don't match never leave storage. Its plan has noFILTERstep:
plan(f"SELECT id FROM {TABLE} WHERE delta < 0")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
- Use
sign()in theSELECTlist, where it runs only on the rows the scan returns.
Limitations and errors
- NaN is reported as
0. Checkisnan(x)when aDOUBLEorFLOATcolumn can hold NaN. See What it does. - A cast binds more tightly than the minus sign.
sign(-9223372036854775808::BIGINT)casts 9223372036854775808 before the minus, and fails withType INT128 with value 9223372036854775808 can't be cast because the value is out of range for the destination type INT64. Put the negative number in parentheses before the cast. - Strings aren't converted.
sign('12')fails withCould not choose a best candidate function. Cast the string to a number first. - Numbers only. A date, a boolean, a
BLOB, a list or an interval fails withNo function matches the given name and argument types. - No
signumorsgn. Both fail withScalar Function with name ... does not exist!. Usesign.
run("SELECT signum(-2)")
run("SELECT sign(-2), sign(CAST('-12' AS INTEGER))")
InternalError: Catalog Error: Scalar Function with name signum does not exist!
[(-1, -1)]
Similar functions
| Function | Returns | Result for -5 |
Result for -0.0::DOUBLE |
|---|---|---|---|
sign(x) |
-1, 0 or 1, as a TINYINT |
-1 |
0 |
| signbit | whether the sign bit is set, as a BOOLEAN |
true |
true |
| abs | the value without its sign | 5 |
0.0 |
| isnan | whether a float is NaN | false |
false |
run("SELECT sign(-5), signbit(-5), abs(-5), sign(-0.0::DOUBLE), signbit(-0.0::DOUBLE), abs(-0.0::DOUBLE)")
run("SELECT isnan(-5), isnan(-0.0::DOUBLE), sign('nan'::DOUBLE), isnan('nan'::DOUBLE)")
[(-1, True, 5, 0, True, 0.0)]
[(False, False, 0, True)]
Clean up:
run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]
See also
- Numeric and math functions: all the numeric functions.
- abs, signbit and isnan.
- Where filters run and pushdowns.
- EXPLAIN: how to read a plan.