Applies to VAST Cluster 5.5.
Functions › Text basics › Characters and code points. Returns the Unicode code point of the first character of a string, as an integer, or -1 for an empty string. unicode is the same function.
Syntax
ord(s)
unicode(s)
| Argument | Type | Description |
|---|---|---|
s |
VARCHAR |
The string whose first character to read. |
Returns: INTEGER.
Aliases: unicode.
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_ord. 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 labels: one in ASCII, one that starts with an accented letter, one that starts with a symbol, an empty label and a NULL:
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_ord"'
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}.labels"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, label VARCHAR)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, 'Invoice'), "
"(2, 'Échéance'), "
"(3, '€ amount'), "
"(4, ''), "
"(5, NULL)")
[]
[]
[(5,)]
What it does
ord returns the code point of the first character of s: the number that Unicode gives that character. For an empty string, which has no first character, it returns -1.
| Call | Result | Why |
|---|---|---|
ord('A') |
65 |
The code point of A, U+0041. |
ord('Hello') |
72 |
Only the first character counts: H. |
ord('AB') |
65 |
The same: A. |
unicode('é') |
233 |
U+00E9, the precomposed é. |
ord('€') |
8364 |
U+20AC. |
unicode('😀') |
128512 |
U+1F600, a character outside the Basic Multilingual Plane. |
ord('🇫🇷') |
127467 |
A flag is two code points; this is the first, U+1F1EB. |
ord('') |
-1 |
An empty string has no first character. |
A code point isn't always what a reader sees as the first character. The French flag is two regional-indicator code points, and 'e\u0301' (displayed as é) is an e followed by a combining accent, so ord returns 101, the code of e. See length for code points and graphemes.
How ord differs from ascii
ascii takes the same argument and, for each non-empty string on this page, returns the same code point as ord. The difference is the empty string:
| Input | ord and unicode |
ascii |
|---|---|---|
'' |
-1 |
0 |
| Any other string | its first code point | its first code point |
-1 can't be a code point, so ord(s) = -1 means that s is empty. The 0 that ascii returns for an empty string is also the code point of the NUL character, chr(0). Use ord when you need to tell the two apart.
NULLs and edge cases
| Input | Call | Result |
|---|---|---|
NULL |
ord(NULL) |
NULL |
'' (empty string) |
ord(''), unicode('') |
-1 |
| An ASCII character | ord('A') |
65 |
| A character outside Latin-1 | ord('€') |
8364 |
A precomposed é (U+00E9) |
unicode('é') |
233 |
| A character outside the Basic Multilingual Plane | unicode('😀') |
128512 |
e followed by U+0301 |
ord('e\u0301') |
101, the code point of e |
| A flag emoji | ord('🇫🇷') |
127467, the first of its two code points |
| A number | ord(123) |
Error: No function matches the given name and argument types |
A BLOB |
unicode('\xAA'::BLOB) |
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 ord(NULL), ord(''), unicode(''), ord('A'), ord('€'), unicode('é'), unicode('😀')")
run("SELECT ord('e\u0301'), ord('🇫🇷')")
run("SELECT ord(123)")
run("SELECT unicode('\\xAA'::BLOB)")
[(None, -1, -1, 65, 8364, 233, 128512)]
[(101, 127467)]
InternalError: Binder Error: No function matches the given name and argument types 'ord(INTEGER_LITERAL)'. You might need to add explicit type casts.
InternalError: Binder Error: No function matches the given name and argument types 'unicode(BLOB)'. You might need to add explicit type casts.
Examples
The first code point of each label, with ord and unicode, and with ascii for comparison. They differ only on the empty label:
run(f"SELECT id, label, ord(label), unicode(label), ascii(label) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 'Invoice', 73, 73, 73), (2, 'Échéance', 201, 201, 201), (3, '€ amount', 8364, 8364, 8364), (4, '', -1, -1, 0), (5, None, None, None, None)]
Keep the labels that start with a character outside ASCII. The empty label gives -1, so it isn't kept:
run(f"SELECT id, label FROM {TABLE} WHERE ord(label) > 127 ORDER BY id LIMIT 10")
[(2, 'Échéance'), (3, '€ amount')]
Show the first code point in the usual U+ notation, with printf. %04X is upper-case hexadecimal, at least four digits:
run(f"SELECT id, printf('U+%04X', ord(label)) FROM {TABLE} WHERE ord(label) >= 0 ORDER BY id LIMIT 10")
[(1, 'U+0049'), (2, 'U+00C9'), (3, 'U+20AC')]
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 ord() is always computed by the Query Engine:
- In the
SELECTlist, the scan reads the column and the Query Engine computesord()for each row it returns. - In
WHERE, the scan sends the column for every row, and aFILTERstep in the Query Engine computesord()and keeps the rows that match. No rows are skipped in storage.
The plan of the first WHERE example shows the FILTER step above the scan:
plan(f"SELECT id FROM {TABLE} WHERE ord(label) > 127")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (ord(label) > 127) │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ label │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Test for empty strings with
= '', notord(s) = -1. A comparison of the raw column with a constant is pushed down to VAST DataBase, so rows that don't match never leave storage. The plan forlabel = ''has noFILTERstep:
plan(f"SELECT id FROM {TABLE} WHERE label = ''")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
- Store the first code point if you filter on it often. Compute
ord(label)when you write the rows and keep it in its own column:first_code > 127is pushed down,ord(label) > 127isn't. - Combine it with a pushed-down condition. In
id > 100 AND ord(label) > 127,id > 100is still evaluated in VAST DataBase, soord()runs only on the rows that pass it.
Limitations and errors
- Strings only. A number, a decimal, a
BLOB, a date, a boolean or a list fails withNo function matches the given name and argument types. Cast it toVARCHARfirst. - The first code point, not the first character a reader sees. A flag or a letter with a combining accent starts with a code point that isn't the whole character. See What it does.
- -1 for an empty string, where ascii returns 0. Check for -1 before you pass the result to chr:
chr(-1)fails withInvalid UTF8 Codepoint -1.
run("SELECT ord(DATE '2026-10-16')")
run("SELECT chr(ord(''))")
InternalError: Binder Error: No function matches the given name and argument types 'ord(DATE)'. You might need to add explicit type casts.
OSError: Invalid UTF8 Codepoint -1
Similar functions
| Function | Returns | 'Hello' |
'é' |
'e\u0301' |
'' |
|---|---|---|---|---|---|
ord |
the first code point; -1 for an empty string | 72 |
233 |
101 |
-1 |
unicode |
the same as ord |
72 |
233 |
101 |
-1 |
| ascii | the first code point; 0 for an empty string | 72 |
233 |
101 |
0 |
| chr | the reverse: the character for a code point | chr(233) is 'é' |
The last block also shows the NUL character, chr(0): ord and ascii both return 0 for it. Only ord and unicode then give a different value, -1, for an empty string.
run("SELECT ord('Hello'), unicode('Hello'), ascii('Hello')")
run("SELECT ord('é'), unicode('é'), ascii('é'), chr(233)")
run("SELECT ord('e\u0301'), unicode('e\u0301'), ascii('e\u0301')")
run("SELECT ord(''), unicode(''), ascii(''), ord(chr(0)), ascii(chr(0))")
[(72, 72, 72)]
[(233, 233, 233, 'é')]
[(101, 101, 101)]
[(-1, -1, 0, 0, 0)]
Clean up:
run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]
See also
- Text basics: all the text functions.
- ascii and chr.
- length: code points and graphemes.
- Where filters run and pushdowns.
- EXPLAIN: how to read a plan.