Applies to VAST Cluster 5.5.
Functions › Conditional and conversion functions › Converting types. CAST(x AS type) converts a value to another type, and x::type is a shorter way to write the same thing. A value that doesn't convert stops the query; to get NULL instead, use TRY_CAST.
Syntax
CAST(x AS type)
x::type
type 'text'
| Argument | Type | Description |
|---|---|---|
x |
any | The value to convert. |
type |
a type name | The target type, such as INTEGER, DECIMAL(10,2), DATE or INTEGER[] (data types in SQL). |
Returns: a value of type. NULL converts to NULL of type.
type 'text', such as DATE '2026-10-16' or INTEGER '42', is a typed literal: the same as CAST('text' AS type).
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_conditional_and_cast_cast. 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 orders loaded from text. qty holds the quantity as text, and row 4 has a value that isn't a number. 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_conditional_and_cast_cast"'
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}.orders"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, qty VARCHAR, units INTEGER, price DOUBLE, "
"amount DECIMAL(10,2), shipped DATE, at TIMESTAMP)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, '42', 3, 2.5, 2.50, DATE '2026-10-01', TIMESTAMP '2026-10-01 12:34:56.789'), "
"(2, ' 12 ', 0, 3.5, 3.50, DATE '2026-10-02', TIMESTAMP '2026-10-02 00:00:00'), "
"(3, '+5', -1, -2.5, -2.50, DATE '2026-10-03', TIMESTAMP '2026-10-03 23:59:59'), "
"(4, 'ten', 7, 1.0, 1.00, DATE '2026-10-04', TIMESTAMP '2026-10-04 08:00:00'), "
"(5, NULL, NULL, NULL, NULL, NULL, NULL)")
[]
[]
[(5,)]
What it does
CAST(x AS type) and x::type return x as a value of type. They're the same operation: use whichever reads better. The main rules:
- Text to a number: spaces, tabs and newlines around the number are ignored, and a leading
+is allowed. Exponents ('1e3'), hexadecimal ('0x1F'), binary ('0b101') and_between digits ('1_000') are accepted. Thousands separators ('1,000') and decimal commas ('1,5') aren't. - Rounding to an integer depends on the source type. A
DECIMAL, which is what a literal such as2.5is, and text such as'2.5'round half away from zero: 2.5 becomes 3, -2.5 becomes -3. ADOUBLEorFLOATrounds half to even: 2.5 becomes 2, 3.5 becomes 4. - Out of range is an error, not a wrap-around:
300::TINYINTfails withType INT32 with value 300 can't be cast because the value is out of range for the destination type INT8. - Text to
BOOLEAN:'true','t','yes','y'and'1'are true,'false','f','no'and'0'false ('TRUE'works too). Other text, such as'on', fails, and so does text with spaces around it. A number is true unless it's 0. - Text to
DATE: year, month and day in that order, separated by-or/('2026-10-16','2026/10/16','2026-1-6'). A time after the date is dropped. Dates that don't exist, such as'2026-02-29', fail. - Text to
TIMESTAMP:'2026-10-16 12:34:56', withTor a space between date and time, optional fractions of a second (kept to the microsecond) and an optional UTC offset. A value with an offset, such as'+02', is converted to UTC. - Text to a list:
'[1, 2, 3]'::INTEGER[], with brackets. VARCHAR(n)doesn't truncate or check the length:'abc'::VARCHAR(1)is'abc'.
Precedence of ::
:: binds more tightly than arithmetic, and also more tightly than a leading minus sign. Put parentheses around an expression you want to cast:
(1 + 1)::VARCHARis'2', but1 + 1::VARCHARadds a number to text and fails.-1::UTINYINTis-(1::UTINYINT), which gives255;(-1)::UTINYINTfails as out of range.- Casts chain from left to right:
'2.5'::DOUBLE::INTEGERconverts toDOUBLE, then rounds half to even, giving 2.
NULLs and edge cases
| Input | Result |
|---|---|
CAST(NULL AS INTEGER), typeof(CAST(NULL AS INTEGER)) |
NULL, 'INTEGER' |
' 42 '::INTEGER, '1e3'::INTEGER, '0x1F'::INTEGER, '1_000'::INTEGER |
42, 1000, 31, 1000 |
2.5::INTEGER, 2.5::DOUBLE::INTEGER, -2.5::DOUBLE::INTEGER |
3, 2, -2 |
1.005::DECIMAL(10,2), 1.005::DOUBLE::DECIMAL(10,2) |
1.01, 1.00 (the DOUBLE is slightly below 1.005) |
'yes'::BOOLEAN, '0'::BOOLEAN, 2::BOOLEAN |
true, false, true |
'2026/10/16'::DATE, '2026-10-16 12:00:00'::DATE |
2026-10-16, 2026-10-16 |
'2026-10-16 12:34:56+02'::TIMESTAMP |
2026-10-16 10:34:56 |
'nan'::DOUBLE, '-Infinity'::DOUBLE |
nan, -inf |
'[1,2,3]'::INTEGER[], 'abc'::VARCHAR(1) |
[1, 2, 3], 'abc' |
(0.1::DOUBLE + 0.2::DOUBLE)::VARCHAR |
'0.30000000000000004' |
''::INTEGER, '1,000'::INTEGER |
Error: Could not convert string '' to INT32 (and the same for '1,000') |
300::TINYINT, '300'::TINYINT |
Error: out of range / Could not convert string '300' to INT8 |
' true '::BOOLEAN, 'on'::BOOLEAN |
Error: Could not convert string ' true ' to BOOL |
'20261016'::DATE, '2026-02-29'::DATE |
Error: invalid date field format / date field value out of range |
'nan'::DOUBLE::INTEGER |
Error: out of range for INT32 |
The same cases, as the cluster returns them. They use the helpers from Set up the examples:
run("SELECT CAST(NULL AS INTEGER), typeof(CAST(NULL AS INTEGER)), ' 42 '::INTEGER, '1e3'::INTEGER, "
"'0x1F'::INTEGER, '1_000'::INTEGER")
run("SELECT 2.5::INTEGER, 2.5::DOUBLE::INTEGER, -2.5::DOUBLE::INTEGER, 1.005::DECIMAL(10,2), "
"1.005::DOUBLE::DECIMAL(10,2)")
run("SELECT 'yes'::BOOLEAN, '0'::BOOLEAN, 2::BOOLEAN, '2026/10/16'::DATE, '2026-10-16 12:00:00'::DATE, "
"'2026-10-16 12:34:56+02'::TIMESTAMP")
run("SELECT 'nan'::DOUBLE, '-Infinity'::DOUBLE, '[1,2,3]'::INTEGER[], 'abc'::VARCHAR(1), "
"(0.1::DOUBLE + 0.2::DOUBLE)::VARCHAR")
for bad in ["''::INTEGER", "'1,000'::INTEGER", "300::TINYINT", "'300'::TINYINT", "' true '::BOOLEAN",
"'on'::BOOLEAN", "'20261016'::DATE", "'2026-02-29'::DATE", "'nan'::DOUBLE::INTEGER"]:
run(f"SELECT {bad}")
[(None, 'INTEGER', 42, 1000, 31, 1000)]
[(3, 2, -2, Decimal('1.01'), Decimal('1.00'))]
[(True, False, True, datetime.date(2026, 10, 16), datetime.date(2026, 10, 16), datetime.datetime(2026, 10, 16, 10, 34, 56))]
[(nan, -inf, [1, 2, 3], 'abc', '0.30000000000000004')]
OSError: Could not convert string '' to INT32
OSError: Could not convert string '1,000' to INT32
OSError: Type INT32 with value 300 can't be cast because the value is out of range for the destination type INT8
OSError: Could not convert string '300' to INT8
OSError: Could not convert string ' true ' to BOOL
OSError: Could not convert string 'on' to BOOL
OSError: invalid date field format: "20261016", expected format is (YYYY-MM-DD)
OSError: date field value out of range: "2026-02-29"
OSError: Type DOUBLE with value nan can't be cast because the value is out of range for the destination type INT32
The precedence cases:
run("SELECT (1 + 1)::VARCHAR, -1::UTINYINT, typeof(-1::UTINYINT), '2.5'::DOUBLE::INTEGER")
run("SELECT 1 + 1::VARCHAR")
run("SELECT (-1)::UTINYINT")
[('2', 255, 'UTINYINT', 2)]
InternalError: Binder Error: No function matches the given name and argument types '+(INTEGER_LITERAL, VARCHAR)'. You might need to add explicit type casts.
OSError: Type INT32 with value -1 can't be cast because the value is out of range for the destination type UINT8
Examples
Convert the text quantities to integers. Spaces and a leading + are accepted:
run(f"SELECT id, qty, CAST(qty AS INTEGER), qty::INTEGER FROM {TABLE} WHERE id IN (1, 2, 3) ORDER BY id LIMIT 10")
[(1, '42', 42, 42), (2, ' 12 ', 12, 12), (3, '+5', 5, 5)]
Round prices to integers: the DOUBLE column rounds half to even, the DECIMAL column half away from zero:
run(f"SELECT id, price, CAST(price AS INTEGER), amount, CAST(amount AS INTEGER) FROM {TABLE} "
"WHERE id <= 3 ORDER BY id LIMIT 10")
[(1, 2.5, 2, Decimal('2.50'), 3), (2, 3.5, 4, Decimal('3.50'), 4), (3, -2.5, -2, Decimal('-2.50'), -3)]
Convert between date, time and text types:
run(f"SELECT id, CAST(at AS DATE), at::TIME, CAST(at AS VARCHAR), shipped::TIMESTAMP FROM {TABLE} "
"WHERE id <= 2 ORDER BY id LIMIT 10")
[(1, datetime.date(2026, 10, 1), datetime.time(12, 34, 56, 789000), '2026-10-01 12:34:56.789', datetime.datetime(2026, 10, 1, 0, 0)), (2, datetime.date(2026, 10, 2), datetime.time(0, 0), '2026-10-02 00:00:00', datetime.datetime(2026, 10, 2, 0, 0))]
One value that doesn't convert stops the whole query, also when the cast is in WHERE. Use TRY_CAST to get NULL for that row instead:
run(f"SELECT id, CAST(qty AS INTEGER) FROM {TABLE} ORDER BY id LIMIT 10")
run(f"SELECT count(*) FROM {TABLE} WHERE CAST(qty AS INTEGER) > 0")
OSError: Could not convert string 'ten' to INT32
OSError: Could not convert string 'ten' to INT32
Text in an INSERT is converted to the column's type, with the same rules; a value that doesn't convert stops the INSERT. The last statement removes the added row:
run(f"INSERT INTO {TABLE} (id, units, shipped) VALUES (6, '12', '2026-10-16')")
run(f"SELECT id, units, shipped FROM {TABLE} WHERE id = 6")
run(f"INSERT INTO {TABLE} (id, shipped) VALUES (7, '2026-02-30')")
run(f"DELETE FROM {TABLE} WHERE id IN (6, 7)")
[(1,)]
[(6, 12, datetime.date(2026, 10, 16))]
InternalError: Conversion Error: date field value out of range: "2026-02-30"
[(1,)]
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), which evaluates the WHERE conditions it can push down. Where a cast runs depends on what it's applied to:
- A cast of a constant is computed once, not for each row.
units = CAST('1' AS INTEGER)andunits = '1'::INTEGERare still comparisons of the column with a constant, and are pushed down. - A widening cast of a column keeps the condition pushed down:
CAST(units AS BIGINT) > 1andCAST(at AS DATE) = DATE '2026-10-01'have noFILTERstep. - Other casts of a column in
WHERE, such as text to a number or a number to text, are aFILTERstep in the Query Engine: the scan sends every row and the Query Engine converts each value. That is also why one bad value stops the query. - In the
SELECTlist, the Query Engine converts the values of the rows the scan returns.
The plans of a widening cast and of a cast from text:
plan(f"SELECT id FROM {TABLE} WHERE CAST(units AS BIGINT) > 1")
plan(f"SELECT id FROM {TABLE} WHERE CAST(qty AS INTEGER) > 5")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (CAST(qty AS INTEGER) > 5)│
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ qty │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Cast the constant, not the column.
units = CAST('1' AS INTEGER)is pushed down;CAST(units AS VARCHAR) = '1'is aFILTERin the Query Engine:
plan(f"SELECT id FROM {TABLE} WHERE units = CAST('1' AS INTEGER)")
plan(f"SELECT id FROM {TABLE} WHERE CAST(units AS VARCHAR) = '1'")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (CAST(units AS VARCHAR) = │
│ '1') │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ units │
│ id │
│ │
└───────────────────────────┘
- Store values in the type you filter on. A number kept as text, like
qty, is converted on every row of every query, and one bad value stops the query. Convert it once when you load the data.
Limitations and errors
- Some conversions don't exist.
DATEorTIMESTAMPto a number and back,TIMEtoTIMESTAMP, a number toINTERVAL, and aUUIDto anything but text fail withUnimplemented type for cast, followed by the two types. Use the date and time functions for dates and times. - Text to
BLOBtakes ASCII only. Non-ASCII text fails withInvalid byte encountered in STRING -> BLOB conversion; use encode for the UTF-8 bytes. In the other direction,CAST(b AS VARCHAR)shows bytes that aren't ASCII as\xC3\xA9-style escapes; use decode to read UTF-8. - Dates in other orders don't convert.
'16/10/2026'and'20261016'fail withinvalid date field format. Parse them with strptime and a format. - Time zone names don't convert to
TIMESTAMP.'2026-10-16 12:34:56 Europe/Paris'::TIMESTAMPfails with... has a timestamp that is not UTC.Use a UTC offset such as+02. - Unknown type names fail before the query runs, with
Catalog Error: Type with name NOSUCHTYPE does not exist!. Other names are accepted for many types:INT,INT4,SIGNED(INTEGER),INT8,LONG(BIGINT),INT2(SMALLINT),INT1(TINYINT),REAL,FLOAT4(FLOAT),FLOAT8,DOUBLE PRECISION(DOUBLE),NUMERIC(DECIMAL(18,3)),STRING,TEXT,CHAR,BPCHAR(VARCHAR),BOOL,LOGICAL(BOOLEAN),BYTEA,BINARY,VARBINARY(BLOB) andDATETIME(TIMESTAMP). - Implicit conversions are limited.
'1' = 1and'a' || 1work, but1 + '1'fails withCould not choose a best candidate function: cast one side.
run("SELECT DATE '2026-10-16'::INTEGER")
run("SELECT '2026-10-16 12:34:56'::TIMESTAMP::BIGINT")
run("SELECT '1'::NOSUCHTYPE")
run("SELECT 1 + '1'")
OSError: Unimplemented type for cast (DATE -> INTEGER)
OSError: Unimplemented type for cast (TIMESTAMP -> BIGINT)
InternalError: Catalog Error: Type with name NOSUCHTYPE does not exist!
InternalError: Binder Error: Could not choose a best candidate function for the function call "+(INTEGER_LITERAL, STRING_LITERAL)". In order to select one, please add explicit type casts.
Similar functions
| Expression | Gives for '42' |
For text that isn't a number |
|---|---|---|
CAST('42' AS INTEGER), '42'::INTEGER |
42 |
an error that stops the query |
INTEGER '42' |
42 |
an error |
TRY_CAST('42' AS INTEGER) |
42 |
NULL |
typeof('42'::INTEGER) |
'INTEGER': the type, not the value |
|
can_cast_implicitly(1::INTEGER, 1::BIGINT) |
true: whether one type converts to the other without a cast |
run("SELECT CAST('42' AS INTEGER), '42'::INTEGER, INTEGER '42', TRY_CAST('42' AS INTEGER), TRY_CAST('x' AS INTEGER), "
"typeof('42'::INTEGER), can_cast_implicitly(1::INTEGER, 1::BIGINT)")
run("SELECT CAST('x' AS INTEGER)")
[(42, 42, 42, 42, None, 'INTEGER', True)]
OSError: Could not convert string 'x' to INT32
Clean up:
run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]
See also
- Conditional and conversion functions: all the conversion functions.
- Data types in SQL: type names and conversion rules.
- UUID values and casts: converting text to
UUID. - Where filters run and pushdowns.
- EXPLAIN: how to read a plan.